1 /*
2 * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.incubator.code.bytecode;
27
28 import java.lang.classfile.ClassBuilder;
29 import java.lang.classfile.ClassFile;
30 import java.lang.classfile.CodeBuilder;
31 import java.lang.classfile.Label;
32 import java.lang.classfile.Opcode;
33 import java.lang.classfile.TypeKind;
34 import java.lang.classfile.instruction.SwitchCase;
35 import java.lang.constant.ClassDesc;
36 import java.lang.constant.Constable;
37 import java.lang.constant.ConstantDescs;
38 import java.lang.constant.DirectMethodHandleDesc;
39 import java.lang.constant.DynamicCallSiteDesc;
40 import java.lang.constant.MethodHandleDesc;
41 import java.lang.constant.MethodTypeDesc;
42 import java.lang.invoke.LambdaMetafactory;
43 import java.lang.invoke.MethodHandle;
44 import java.lang.invoke.MethodHandles;
45 import java.lang.invoke.MethodType;
46 import java.lang.invoke.StringConcatFactory;
47 import java.lang.reflect.Method;
48 import java.lang.reflect.Modifier;
49 import java.util.*;
50 import java.util.stream.Stream;
51
52 import jdk.incubator.code.*;
53 import jdk.incubator.code.bytecode.impl.BranchCompactor;
54 import jdk.incubator.code.bytecode.impl.ConstantLabelSwitchOp;
55 import jdk.incubator.code.bytecode.impl.ExceptionTableCompactor;
56 import jdk.incubator.code.bytecode.impl.LocalsCompactor;
57 import jdk.incubator.code.bytecode.impl.LoweringTransform;
58 import jdk.incubator.code.dialect.core.CoreOp.*;
59 import jdk.incubator.code.dialect.java.*;
60 import jdk.incubator.code.extern.OpParser;
61 import jdk.incubator.code.dialect.core.FunctionType;
62 import jdk.incubator.code.dialect.core.VarType;
63 import jdk.incubator.code.runtime.ReflectableLambdaMetafactory;
64
65 import static java.lang.constant.ConstantDescs.*;
66 import static jdk.incubator.code.dialect.java.JavaOp.*;
67
68 /**
69 * Transformer of code models to bytecode.
70 */
71 public final class BytecodeGenerator {
72
73 private static final DirectMethodHandleDesc DMHD_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
74 LambdaMetafactory.class.describeConstable().orElseThrow(),
75 "metafactory",
76 CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
77
78 private static final DirectMethodHandleDesc DMHD_REFLECTABLE_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
79 ReflectableLambdaMetafactory.class.describeConstable().orElseThrow(),
80 "metafactory",
81 CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
82
83 private static final DirectMethodHandleDesc DMHD_STRING_CONCAT = ofCallsiteBootstrap(
84 StringConcatFactory.class.describeConstable().orElseThrow(),
85 "makeConcat",
86 CD_CallSite);
87
88 private static final MethodTypeDesc OP_METHOD_DESC = MethodTypeDesc.of(Op.class.describeConstable().get());
89
90 /**
91 * Transforms the invokable operation to bytecode encapsulated in a method of hidden class and exposed
92 * for invocation via a method handle.
93 *
94 * @param l the lookup
95 * @param iop the invokable operation to transform to bytecode
96 * @return the invoking method handle
97 * @param <O> the type of the invokable operation
98 */
99 public static <O extends Op & Op.Invokable> MethodHandle generate(MethodHandles.Lookup l, O iop) {
100 String name = iop instanceof FuncOp fop ? fop.funcName() : "m";
101 byte[] classBytes = generateClassData(l, name, iop);
102
103 MethodHandles.Lookup hcl;
104 try {
105 hcl = l.defineHiddenClass(classBytes, true, MethodHandles.Lookup.ClassOption.NESTMATE);
106 } catch (IllegalAccessException e) {
107 throw new RuntimeException(e);
108 }
109
110 try {
111 FunctionType ft = iop.invokableType();
112 MethodType mt = MethodRef.toNominalDescriptor(ft).resolveConstantDesc(hcl);
113 return hcl.findStatic(hcl.lookupClass(), name, mt);
114 } catch (ReflectiveOperationException e) {
115 throw new RuntimeException(e);
116 }
117 }
118
119 /**
120 * Transforms the function operation to bytecode encapsulated in a method of a class file.
121 * <p>
122 * The name of the method is the function operation's {@link FuncOp#funcName() function name}.
123 *
124 * @param lookup the lookup
125 * @param fop the function operation to transform to bytecode
126 * @return the class file bytes
127 */
128 public static byte[] generateClassData(MethodHandles.Lookup lookup, FuncOp fop) {
129 return generateClassData(lookup, fop.funcName(), fop);
130 }
131
132 /**
133 * Transforms the module operation to bytecode encapsulated in methods of a class file.
134 *
135 * @param lookup the lookup
136 * @param clName the name of the generated class file
137 * @param mop the module operation to transform to bytecode
138 * @return the class file bytes
139 */
140 public static byte[] generateClassData(MethodHandles.Lookup lookup,
141 ClassDesc clName,
142 ModuleOp mop) {
143 return generateClassData(lookup, clName, mop.functionTable());
144 }
145
146 /**
147 * Transforms the invokable operation to bytecode encapsulated in a method of a class file.
148 *
149 * @param lookup the lookup
150 * @param name the name to use for the method of the class file
151 * @param iop the invokable operation to transform to bytecode
152 * @return the class file bytes
153 * @param <O> the type of the invokable operation
154 */
155 public static <O extends Op & Op.Invokable> byte[] generateClassData(MethodHandles.Lookup lookup,
156 String name,
157 O iop) {
158 String packageName = lookup.lookupClass().getPackageName();
159 ClassDesc clsName = ClassDesc.of(packageName.isEmpty()
160 ? name
161 : packageName + "." + name);
162 return generateClassData(lookup, clsName, new LinkedHashMap<>(Map.of(name, iop)));
163 }
164
165 @SuppressWarnings("unchecked")
166 private static <O extends Op & Op.Invokable> byte[] generateClassData(MethodHandles.Lookup lookup,
167 ClassDesc clName,
168 SequencedMap<String, ? extends O> ops) {
169 byte[] classBytes = ClassFile.of().build(clName, clb -> {
170 List<LambdaOp> lambdaSink = new ArrayList<>();
171 BitSet quotable = new BitSet();
172 CodeTransformer lowering = LoweringTransform.getInstance(lookup);
173 for (var e : ops.sequencedEntrySet()) {
174 O lowered = (O)e.getValue().transform(CodeContext.create(), lowering);
175 generateMethod(lookup, clName, e.getKey(), lowered, clb, ops, lambdaSink, quotable);
176 }
177 for (int i = 0; i < lambdaSink.size(); i++) {
178 LambdaOp lop = lambdaSink.get(i);
179 if (quotable.get(i)) {
180 // return (FuncOp) OpParser.fromOpString(opText)
181 clb.withMethod("op$lambda$" + i, OP_METHOD_DESC,
182 ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC | ClassFile.ACC_SYNTHETIC, mb -> mb.withCode(cb -> cb
183 .loadConstant(Quoted.embedOp(lop).toText())
184 .invoke(Opcode.INVOKESTATIC, OpParser.class.describeConstable().get(),
185 "fromStringOfJavaCodeModel",
186 MethodTypeDesc.of(Op.class.describeConstable().get(), CD_String), false)
187 .areturn()));
188 }
189 generateMethod(lookup, clName, "lambda$" + i, lop, clb, ops, lambdaSink, quotable);
190 }
191 });
192
193 // Compact locals of the generated bytecode
194 return LocalsCompactor.transform(classBytes);
195 }
196
197 private static <O extends Op & Op.Invokable> void generateMethod(MethodHandles.Lookup lookup,
198 ClassDesc className,
199 String methodName,
200 O iop,
201 ClassBuilder clb,
202 SequencedMap<String, ? extends O> functionTable,
203 List<LambdaOp> lambdaSink,
204 BitSet quotable) {
205 List<Value> capturedValues = iop instanceof LambdaOp lop ? lop.capturedValues() : List.of();
206 MethodTypeDesc mtd = MethodRef.toNominalDescriptor(
207 iop.invokableType()).insertParameterTypes(0, capturedValues.stream()
208 .map(Value::type).map(BytecodeGenerator::toClassDesc).toArray(ClassDesc[]::new));
209 clb.withMethodBody(methodName, mtd, ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
210 cb -> cb.transforming(new BranchCompactor().andThen(new ExceptionTableCompactor()), cob ->
211 new BytecodeGenerator(lookup, className, capturedValues, TypeKind.from(mtd.returnType()),
212 iop.body().blocks(), cob, functionTable, lambdaSink, quotable).generate()));
213 }
214
215 private record Slot(int slot, TypeKind typeKind) {}
216
217 private final MethodHandles.Lookup lookup;
218 private final ClassDesc className;
219 private final List<Value> capturedValues;
220 private final TypeKind returnType;
221 private final List<Block> blocks;
222 private final CodeBuilder cob;
223 private final Label[] blockLabels;
224 private final Block[][] blocksCatchMap;
225 private final BitSet allCatchBlocks;
226 private final Label[] tryStartLabels;
227 private final Map<Value, Slot> slots;
228 private final Map<Block.Parameter, Value> singlePredecessorsValues;
229 private final Map<String, ? extends Invokable> functionMap;
230 private final List<LambdaOp> lambdaSink;
231 private final BitSet quotable;
232 private final Map<Op, Boolean> deferCache;
233 private Value oprOnStack;
234 private Block[] recentCatchBlocks;
235
236 private BytecodeGenerator(MethodHandles.Lookup lookup,
237 ClassDesc className,
238 List<Value> capturedValues,
239 TypeKind returnType,
240 List<Block> blocks,
241 CodeBuilder cob,
242 Map<String, ? extends Invokable> functionMap,
243 List<LambdaOp> lambdaSink,
244 BitSet quotable) {
245 this.lookup = lookup;
246 this.className = className;
247 this.capturedValues = capturedValues;
248 this.returnType = returnType;
249 this.blocks = blocks;
250 this.cob = cob;
251 this.blockLabels = new Label[blocks.size()];
252 this.blocksCatchMap = new Block[blocks.size()][];
253 this.allCatchBlocks = new BitSet();
254 this.tryStartLabels = new Label[blocks.size()];
255 this.slots = new IdentityHashMap<>();
256 this.singlePredecessorsValues = new IdentityHashMap<>();
257 this.functionMap = functionMap;
258 this.lambdaSink = lambdaSink;
259 this.quotable = quotable;
260 this.deferCache = new IdentityHashMap<>();
261 }
262
263 private void setCatchStack(Block.Reference target, Block[] activeCatchBlocks) {
264 setCatchStack(target.targetBlock().index(), activeCatchBlocks);
265 }
266
267 private void setCatchStack(int blockIndex, Block[] activeCatchBlocks) {
268 Block[] prevStack = blocksCatchMap[blockIndex];
269 if (prevStack == null) {
270 blocksCatchMap[blockIndex] = activeCatchBlocks;
271 } else {
272 assert Arrays.equals(prevStack, activeCatchBlocks);
273 }
274 }
275
276 private Label getLabel(Block.Reference target) {
277 return getLabel(target.targetBlock().index());
278 }
279
280 private Label getLabel(int blockIndex) {
281 if (blockIndex == blockLabels.length) {
282 return cob.endLabel();
283 }
284 Label l = blockLabels[blockIndex];
285 if (l == null) {
286 blockLabels[blockIndex] = l = cob.newLabel();
287 }
288 return l;
289 }
290
291 private Slot allocateSlot(Value v) {
292 return slots.computeIfAbsent(v, _ -> {
293 TypeKind tk = toTypeKind(v.type());
294 return new Slot(cob.allocateLocal(tk), tk);
295 });
296 }
297
298 private void storeIfUsed(Value v) {
299 if (!v.uses().isEmpty()) {
300 Slot slot = allocateSlot(v);
301 cob.storeLocal(slot.typeKind(), slot.slot());
302 } else {
303 // Only pop results from stack if the value has no further use (no valid slot)
304 switch (toTypeKind(v.type()).slotSize()) {
305 case 1 -> cob.pop();
306 case 2 -> cob.pop2();
307 }
308 }
309 }
310
311 private void load(Value v) {
312 v = singlePredecessorsValues.getOrDefault(v, v);
313 if (v instanceof Op.Result or &&
314 or.op() instanceof ConstantOp constantOp &&
315 !constantOp.resultType().equals(JavaType.J_L_CLASS)) {
316 cob.loadConstant(switch (constantOp.value()) {
317 case null -> null;
318 case Boolean b -> {
319 yield b ? 1 : 0;
320 }
321 case Byte b -> (int)b;
322 case Character ch -> (int)ch;
323 case Short s -> (int)s;
324 case Constable c -> c.describeConstable().orElseThrow();
325 default -> throw new IllegalArgumentException("Unexpected constant value: " + constantOp.value());
326 });
327 } else {
328 Slot slot = slots.get(v);
329 if (slot == null) {
330 if (v instanceof Op.Result or) {
331 // Handling of deferred variables
332 switch (or.op()) {
333 case VarOp vop ->
334 load(vop.initOperand());
335 case VarAccessOp.VarLoadOp vlop ->
336 load(vlop.varOperand());
337 default ->
338 throw new IllegalStateException("Missing slot for: " + or.op());
339 }
340 } else {
341 throw new IllegalStateException("Missing slot for: " + v);
342 }
343 } else {
344 cob.loadLocal(slot.typeKind(), slot.slot());
345 }
346 }
347 }
348
349 private void processFirstOperand(Op op) {
350 processOperand(op.operands().getFirst());
351 }
352
353 private void processOperand(Value operand) {
354 if (oprOnStack == null) {
355 load(operand);
356 } else {
357 assert oprOnStack == operand;
358 oprOnStack = null;
359 }
360 }
361
362 private void processOperands(Op op) {
363 processOperands(op.operands());
364 }
365
366 private void processOperands(List<Value> operands) {
367 if (oprOnStack == null) {
368 operands.forEach(this::load);
369 } else {
370 assert !operands.isEmpty() && oprOnStack == operands.getFirst();
371 oprOnStack = null;
372 for (int i = 1; i < operands.size(); i++) {
373 load(operands.get(i));
374 }
375 }
376 }
377
378 // Some of the operations can be deferred
379 private boolean canDefer(Op op) {
380 Boolean can = deferCache.get(op);
381 if (can == null) {
382 can = switch (op) {
383 case ConstantOp cop -> canDefer(cop);
384 case VarOp vop -> canDefer(vop);
385 case VarAccessOp.VarLoadOp vlop -> canDefer(vlop);
386 default -> false;
387 };
388 deferCache.put(op, can);
389 }
390 return can;
391 }
392
393 // Constant can be deferred, except for loading of a class constant, which may throw an exception
394 private static boolean canDefer(ConstantOp op) {
395 return !op.resultType().equals(JavaType.J_L_CLASS);
396 }
397
398 // Single-use var or var with a single-use entry block parameter operand can be deferred
399 private static boolean canDefer(VarOp op) {
400 return op.isUninitialized()
401 || !moreThanOneUse(op.result())
402 || op.initOperand() instanceof Block.Parameter bp && bp.declaringBlock().isEntryBlock() && !moreThanOneUse(bp);
403 }
404
405 // Var load can be deferred when not used as immediate operand
406 private boolean canDefer(VarAccessOp.VarLoadOp op) {
407 return !isNextUse(op.result());
408 }
409
410 // This method narrows the first operand inconveniences of some operations
411 private static boolean isFirstOperand(Op nextOp, Value opr) {
412 List<Value> values;
413 return switch (nextOp) {
414 // When there is no next operation
415 case null -> false;
416 // New object cannot use first operand from stack, new array fall through to the default
417 case NewOp op when !(op.constructorDescriptor().type().returnType() instanceof ArrayType) ->
418 false;
419 // For lambda the effective operands are captured values
420 case LambdaOp op ->
421 !(values = op.capturedValues()).isEmpty() && values.getFirst() == opr;
422 // Conditional branch may delegate to its binary test operation
423 case ConditionalBranchOp op when getConditionForCondBrOp(op) instanceof BinaryTestOp bto ->
424 isFirstOperand(bto, opr);
425 // Var store effective first operand is not the first one
426 case VarAccessOp.VarStoreOp op ->
427 op.operands().get(1) == opr;
428 // Unconditional branch first target block argument
429 case BranchOp op ->
430 !(values = op.branch().arguments()).isEmpty() && values.getFirst() == opr;
431 // regular check of the first operand
432 default ->
433 !(values = nextOp.operands()).isEmpty() && values.getFirst() == opr;
434 };
435 }
436
437 // Determines if the operation result is immediatelly used by the next operation and so can stay on stack
438 private boolean isNextUse(Value opr) {
439 Op nextOp = switch (opr) {
440 case Block.Parameter p -> p.declaringBlock().firstOp();
441 case Op.Result r -> r.declaringBlock().nextOp(r.op());
442 };
443 // Pass over deferred operations
444 while (canDefer(nextOp)) {
445 nextOp = nextOp.ancestorBlock().nextOp(nextOp);
446 }
447 return isFirstOperand(nextOp, opr);
448 }
449
450 private static boolean isConditionForCondBrOp(BinaryTestOp op) {
451 // Result of op has one use as the operand of a CondBrOp op,
452 // and both ops are in the same block
453
454 Set<Op.Result> uses = op.result().uses();
455 if (uses.size() != 1) {
456 return false;
457 }
458 Op.Result use = uses.iterator().next();
459
460 if (use.declaringBlock() != op.ancestorBlock()) {
461 return false;
462 }
463
464 // Check if used in successor
465 for (Block.Reference s : use.op().successors()) {
466 if (s.arguments().contains(op.result())) {
467 return false;
468 }
469 }
470
471 return use.op() instanceof ConditionalBranchOp;
472 }
473
474 static ClassDesc toClassDesc(TypeElement t) {
475 return switch (t) {
476 case VarType vt -> toClassDesc(vt.valueType());
477 case JavaType jt -> jt.toNominalDescriptor();
478 default ->
479 throw new IllegalArgumentException("Bad type: " + t);
480 };
481 }
482
483 static TypeKind toTypeKind(TypeElement t) {
484 return switch (t) {
485 case VarType vt -> toTypeKind(vt.valueType());
486 case PrimitiveType pt -> TypeKind.from(pt.toNominalDescriptor());
487 case JavaType _ -> TypeKind.REFERENCE;
488 default ->
489 throw new IllegalArgumentException("Bad type: " + t);
490 };
491 }
492
493 private void generate() {
494 recentCatchBlocks = new Block[0];
495
496 Block entryBlock = blocks.getFirst();
497 assert entryBlock.isEntryBlock();
498
499 // Entry block parameters conservatively require slots
500 // Some unused parameters might be declared before others that are used
501 List<Block.Parameter> parameters = entryBlock.parameters();
502 int paramSlot = 0;
503 // Captured values prepend parameters in lambda impl methods
504 for (Value cv : capturedValues) {
505 slots.put(cv, new Slot(cob.parameterSlot(paramSlot++), toTypeKind(cv.type())));
506 }
507 for (Block.Parameter bp : parameters) {
508 slots.put(bp, new Slot(cob.parameterSlot(paramSlot++), toTypeKind(bp.type())));
509 }
510
511 blocksCatchMap[entryBlock.index()] = new Block[0];
512
513 // Process blocks in topological order
514 // A jump instruction assumes the false successor block is
515 // immediately after, in sequence, to the predecessor
516 // since the jump instructions branch on a true condition
517 // Conditions are inverted when lowered to bytecode
518 for (Block b : blocks) {
519
520 Block[] catchBlocks = blocksCatchMap[b.index()];
521
522 // Ignore inaccessible blocks
523 if (catchBlocks == null) {
524 continue;
525 }
526
527 Label blockLabel = getLabel(b.index());
528 cob.labelBinding(blockLabel);
529
530 oprOnStack = null;
531
532 // If b is a catch block then the exception argument will be represented on the stack
533 if (allCatchBlocks.get(b.index())) {
534 // Retain block argument for exception table generation
535 push(b.parameters().getFirst());
536 }
537
538 exceptionRegionsChange(catchBlocks);
539
540 List<Op> ops = b.ops();
541 for (int i = 0; i < ops.size() - 1; i++) {
542 final Op o = ops.get(i);
543 final TypeElement oprType = o.resultType();
544 final TypeKind rvt = toTypeKind(oprType);
545 switch (o) {
546 case ConstantOp op -> {
547 if (!canDefer(op)) {
548 // Constant can be deferred, except for a class constant, which may throw an exception
549 Object v = op.value();
550 if (v == null) {
551 cob.aconst_null();
552 } else {
553 cob.ldc(((JavaType)v).toNominalDescriptor());
554 }
555 push(op.result());
556 }
557 }
558 case VarOp op when op.isUninitialized() -> {
559 // Do nothing
560 }
561 case VarOp op -> {
562 // %1 : Var<int> = var %0 @"i";
563 if (canDefer(op)) {
564 Slot s = slots.get(op.operands().getFirst());
565 if (s != null) {
566 // Var with a single-use entry block parameter can reuse its slot
567 slots.put(op.result(), s);
568 }
569 } else {
570 processFirstOperand(op);
571 storeIfUsed(op.result());
572 }
573 }
574 case VarAccessOp.VarLoadOp op -> {
575 if (canDefer(op)) {
576 // Var load can be deferred when not used as immediate operand
577 slots.computeIfAbsent(op.result(), r -> slots.get(op.operands().getFirst()));
578 } else {
579 load(op.operands().getFirst());
580 push(op.result());
581 }
582 }
583 case VarAccessOp.VarStoreOp op -> {
584 processOperand(op.operands().get(1));
585 Slot slot = allocateSlot(op.operands().getFirst());
586 cob.storeLocal(slot.typeKind(), slot.slot());
587 }
588 case ConvOp op -> {
589 Value first = op.operands().getFirst();
590 processOperand(first);
591 cob.conversion(toTypeKind(first.type()), rvt);
592 push(op.result());
593 }
594 case NegOp op -> {
595 processFirstOperand(op);
596 switch (rvt) { //this can be moved to CodeBuilder::neg(TypeKind)
597 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.ineg();
598 case LONG -> cob.lneg();
599 case FLOAT -> cob.fneg();
600 case DOUBLE -> cob.dneg();
601 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
602 }
603 push(op.result());
604 }
605 case ComplOp op -> {
606 // Lower to x ^ -1
607 processFirstOperand(op);
608 switch (rvt) {
609 case INT, BOOLEAN, BYTE, SHORT, CHAR -> {
610 cob.iconst_m1();
611 cob.ixor();
612 }
613 case LONG -> {
614 cob.ldc(-1L);
615 cob.lxor();
616 }
617 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
618 }
619 push(op.result());
620 }
621 case NotOp op -> {
622 processFirstOperand(op);
623 cob.ifThenElse(CodeBuilder::iconst_0, CodeBuilder::iconst_1);
624 push(op.result());
625 }
626 case AddOp op -> {
627 processOperands(op);
628 switch (rvt) { //this can be moved to CodeBuilder::add(TypeKind)
629 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.iadd();
630 case LONG -> cob.ladd();
631 case FLOAT -> cob.fadd();
632 case DOUBLE -> cob.dadd();
633 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
634 }
635 push(op.result());
636 }
637 case SubOp op -> {
638 processOperands(op);
639 switch (rvt) { //this can be moved to CodeBuilder::sub(TypeKind)
640 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.isub();
641 case LONG -> cob.lsub();
642 case FLOAT -> cob.fsub();
643 case DOUBLE -> cob.dsub();
644 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
645 }
646 push(op.result());
647 }
648 case MulOp op -> {
649 processOperands(op);
650 switch (rvt) { //this can be moved to CodeBuilder::mul(TypeKind)
651 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.imul();
652 case LONG -> cob.lmul();
653 case FLOAT -> cob.fmul();
654 case DOUBLE -> cob.dmul();
655 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
656 }
657 push(op.result());
658 }
659 case DivOp op -> {
660 processOperands(op);
661 switch (rvt) { //this can be moved to CodeBuilder::div(TypeKind)
662 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.idiv();
663 case LONG -> cob.ldiv();
664 case FLOAT -> cob.fdiv();
665 case DOUBLE -> cob.ddiv();
666 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
667 }
668 push(op.result());
669 }
670 case ModOp op -> {
671 processOperands(op);
672 switch (rvt) { //this can be moved to CodeBuilder::rem(TypeKind)
673 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.irem();
674 case LONG -> cob.lrem();
675 case FLOAT -> cob.frem();
676 case DOUBLE -> cob.drem();
677 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
678 }
679 push(op.result());
680 }
681 case AndOp op -> {
682 processOperands(op);
683 switch (rvt) { //this can be moved to CodeBuilder::and(TypeKind)
684 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.iand();
685 case LONG -> cob.land();
686 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
687 }
688 push(op.result());
689 }
690 case OrOp op -> {
691 processOperands(op);
692 switch (rvt) { //this can be moved to CodeBuilder::or(TypeKind)
693 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.ior();
694 case LONG -> cob.lor();
695 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
696 }
697 push(op.result());
698 }
699 case XorOp op -> {
700 processOperands(op);
701 switch (rvt) { //this can be moved to CodeBuilder::xor(TypeKind)
702 case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.ixor();
703 case LONG -> cob.lxor();
704 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
705 }
706 push(op.result());
707 }
708 case LshlOp op -> {
709 processOperands(op);
710 adjustRightTypeToInt(op);
711 switch (rvt) { //this can be moved to CodeBuilder::shl(TypeKind)
712 case BYTE, CHAR, INT, SHORT -> cob.ishl();
713 case LONG -> cob.lshl();
714 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
715 }
716 push(op.result());
717 }
718 case AshrOp op -> {
719 processOperands(op);
720 adjustRightTypeToInt(op);
721 switch (rvt) { //this can be moved to CodeBuilder::shr(TypeKind)
722 case INT, BYTE, SHORT, CHAR -> cob.ishr();
723 case LONG -> cob.lshr();
724 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
725 }
726 push(op.result());
727 }
728 case LshrOp op -> {
729 processOperands(op);
730 adjustRightTypeToInt(op);
731 switch (rvt) { //this can be moved to CodeBuilder::ushr(TypeKind)
732 case INT, BYTE, SHORT, CHAR -> cob.iushr();
733 case LONG -> cob.lushr();
734 default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
735 }
736 push(op.result());
737 }
738 case ArrayAccessOp.ArrayLoadOp op -> {
739 processOperands(op);
740 cob.arrayLoad(rvt);
741 push(op.result());
742 }
743 case ArrayAccessOp.ArrayStoreOp op -> {
744 processOperands(op);
745 cob.arrayStore(toTypeKind(((ArrayType)op.operands().getFirst().type()).componentType()));
746 push(op.result());
747 }
748 case ArrayLengthOp op -> {
749 processFirstOperand(op);
750 cob.arraylength();
751 push(op.result());
752 }
753 case BinaryTestOp op -> {
754 if (!isConditionForCondBrOp(op)) {
755 cob.ifThenElse(prepareConditionalBranch(op), CodeBuilder::iconst_0, CodeBuilder::iconst_1);
756 push(op.result());
757 }
758 // Processing is deferred to the CondBrOp, do not process the op result
759 }
760 case NewOp op -> {
761 switch (op.constructorDescriptor().type().returnType()) {
762 case ArrayType at -> {
763 processOperands(op);
764 if (at.dimensions() == 1) {
765 ClassDesc ctd = at.componentType().toNominalDescriptor();
766 if (ctd.isPrimitive()) {
767 cob.newarray(TypeKind.from(ctd));
768 } else {
769 cob.anewarray(ctd);
770 }
771 } else {
772 cob.multianewarray(at.toNominalDescriptor(), op.operands().size());
773 }
774 }
775 case JavaType jt -> {
776 cob.new_(jt.toNominalDescriptor())
777 .dup();
778 processOperands(op);
779 cob.invokespecial(
780 ((JavaType) op.resultType()).toNominalDescriptor(),
781 ConstantDescs.INIT_NAME,
782 MethodRef.toNominalDescriptor(op.constructorDescriptor().type())
783 .changeReturnType(ConstantDescs.CD_void));
784 }
785 default ->
786 throw new IllegalArgumentException("Invalid return type: "
787 + op.constructorDescriptor().type().returnType());
788 }
789 push(op.result());
790 }
791 case InvokeOp op -> {
792 if (op.isVarArgs()) {
793 processOperands(op.argOperands());
794 var varArgOperands = op.varArgOperands();
795 cob.loadConstant(varArgOperands.size());
796 var compType = ((ArrayType) op.invokeDescriptor().type().parameterTypes().getLast()).componentType();
797 var compTypeDesc = compType.toNominalDescriptor();
798 var typeKind = TypeKind.from(compTypeDesc);
799 if (compTypeDesc.isPrimitive()) {
800 cob.newarray(typeKind);
801 } else {
802 cob.anewarray(compTypeDesc);
803 }
804 for (int j = 0; j < varArgOperands.size(); j++) {
805 // we duplicate array value on the stack to be consumed by arrayStore
806 // after completion of this loop the array value will be on top of the stack
807 cob.dup();
808 cob.loadConstant(j);
809 load(varArgOperands.get(j));
810 cob.arrayStore(typeKind);
811 }
812 } else {
813 processOperands(op);
814 }
815 // Resolve referenced class to determine if interface
816 MethodRef md = op.invokeDescriptor();
817 JavaType refType = (JavaType)md.refType();
818 Class<?> refClass;
819 try {
820 refClass = (Class<?>)refType.erasure().resolve(lookup);
821 } catch (ReflectiveOperationException e) {
822 throw new IllegalArgumentException(e);
823 }
824 // Determine invoke opcode
825 boolean isInterface = refClass.isInterface();
826 Opcode invokeOpcode = switch (op.invokeKind()) {
827 case STATIC ->
828 Opcode.INVOKESTATIC;
829 case INSTANCE ->
830 isInterface ? Opcode.INVOKEINTERFACE : Opcode.INVOKEVIRTUAL;
831 case SUPER ->
832 // @@@ We cannot generate an invokespecial as it will result in a verify error,
833 // since the owner is not assignable to generated hidden class
834 // @@@ Construct method handle via lookup.findSpecial
835 // using the lookup's class as the specialCaller and
836 // add that method handle to the to be defined hidden class's constant data
837 // Use and ldc+constant dynamic to access the class data,
838 // extract the method handle and then invoke it
839 throw new UnsupportedOperationException("invoke super unsupported: " + op.invokeDescriptor());
840 };
841 MethodTypeDesc mDesc = MethodRef.toNominalDescriptor(md.type());
842 cob.invoke(
843 invokeOpcode,
844 refType.toNominalDescriptor(),
845 md.name(),
846 mDesc,
847 isInterface);
848 ClassDesc ret = toClassDesc(op.resultType());
849 if (ret.isClassOrInterface() && !ret.equals(mDesc.returnType())) {
850 // Explicit cast if method return type differs
851 cob.checkcast(ret);
852 }
853 push(op.result());
854 }
855 case FuncCallOp op -> {
856 Invokable fop = functionMap.get(op.funcName());
857 if (fop == null) {
858 throw new IllegalArgumentException("Could not resolve function: " + op.funcName());
859 }
860 processOperands(op);
861 MethodTypeDesc mDesc = MethodRef.toNominalDescriptor(fop.invokableType());
862 cob.invoke(
863 Opcode.INVOKESTATIC,
864 className,
865 op.funcName(),
866 mDesc,
867 false);
868 ClassDesc ret = toClassDesc(op.resultType());
869 if (ret.isClassOrInterface() && !ret.equals(mDesc.returnType())) {
870 // Explicit cast if method return type differs
871 cob.checkcast(ret);
872 }
873 push(op.result());
874 }
875 case FieldAccessOp.FieldLoadOp op -> {
876 processOperands(op);
877 FieldRef fd = op.fieldDescriptor();
878 if (op.operands().isEmpty()) {
879 cob.getstatic(
880 ((JavaType) fd.refType()).toNominalDescriptor(),
881 fd.name(),
882 ((JavaType) fd.type()).toNominalDescriptor());
883 } else {
884 cob.getfield(
885 ((JavaType) fd.refType()).toNominalDescriptor(),
886 fd.name(),
887 ((JavaType) fd.type()).toNominalDescriptor());
888 }
889 push(op.result());
890 }
891 case FieldAccessOp.FieldStoreOp op -> {
892 processOperands(op);
893 FieldRef fd = op.fieldDescriptor();
894 if (op.operands().size() == 1) {
895 cob.putstatic(
896 ((JavaType) fd.refType()).toNominalDescriptor(),
897 fd.name(),
898 ((JavaType) fd.type()).toNominalDescriptor());
899 } else {
900 cob.putfield(
901 ((JavaType) fd.refType()).toNominalDescriptor(),
902 fd.name(),
903 ((JavaType) fd.type()).toNominalDescriptor());
904 }
905 }
906 case InstanceOfOp op -> {
907 processFirstOperand(op);
908 cob.instanceOf(((JavaType) op.type()).toNominalDescriptor());
909 push(op.result());
910 }
911 case CastOp op -> {
912 processFirstOperand(op);
913 cob.checkcast(((JavaType) op.type()).toNominalDescriptor());
914 push(op.result());
915 }
916 case LambdaOp op -> {
917 JavaType intfType = (JavaType)op.functionalInterface();
918 MethodTypeDesc mtd = MethodRef.toNominalDescriptor(op.invokableType());
919 try {
920 Class<?> intfClass = (Class<?>)intfType.erasure().resolve(lookup);
921 Method intfMethod = funcIntfMethod(intfClass, mtd);
922 processOperands(op.capturedValues());
923 ClassDesc[] captureTypes = op.capturedValues().stream()
924 .map(Value::type).map(BytecodeGenerator::toClassDesc).toArray(ClassDesc[]::new);
925 int lambdaIndex = lambdaSink.size();
926 DirectMethodHandleDesc lambdaMetafactory = DMHD_LAMBDA_METAFACTORY;
927 String intfMethodName = intfMethod.getName();
928 if (op.isQuotable()) {
929 lambdaMetafactory = DMHD_REFLECTABLE_LAMBDA_METAFACTORY;
930 intfMethodName = intfMethodName + "=" + "op$lambda$" + lambdaIndex;
931 quotable.set(lambdaSink.size());
932 }
933 cob.invokedynamic(DynamicCallSiteDesc.of(
934 lambdaMetafactory,
935 intfMethodName,
936 MethodTypeDesc.of(intfType.toNominalDescriptor(), captureTypes),
937 toMTD(intfMethod),
938 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
939 className,
940 "lambda$" + lambdaIndex,
941 mtd.insertParameterTypes(0, captureTypes)),
942 mtd));
943 lambdaSink.add(op);
944 } catch (ReflectiveOperationException e) {
945 throw new IllegalArgumentException(e);
946 }
947 push(op.result());
948 }
949 case ConcatOp op -> {
950 processOperands(op);
951 cob.invokedynamic(DynamicCallSiteDesc.of(DMHD_STRING_CONCAT, MethodTypeDesc.of(CD_String,
952 toClassDesc(op.operands().get(0).type()),
953 toClassDesc(op.operands().get(1).type()))));
954 push(op.result());
955 }
956 case MonitorOp.MonitorEnterOp op -> {
957 processFirstOperand(op);
958 cob.monitorenter();
959 }
960 case MonitorOp.MonitorExitOp op -> {
961 processFirstOperand(op);
962 cob.monitorexit();
963 }
964 default ->
965 throw new UnsupportedOperationException("Unsupported operation: " + ops.get(i));
966 }
967 }
968 Op top = b.terminatingOp();
969 switch (top) {
970 case ReturnOp op -> {
971 if (returnType != TypeKind.VOID) {
972 processFirstOperand(op);
973 // @@@ box, unbox, cast here ?
974 }
975 cob.return_(returnType);
976 }
977 case ThrowOp op -> {
978 processFirstOperand(op);
979 cob.athrow();
980 }
981 case BranchOp op -> {
982 setCatchStack(op.branch(), recentCatchBlocks);
983
984 assignBlockArguments(op.branch());
985 cob.goto_(getLabel(op.branch()));
986 }
987 case ConditionalBranchOp op -> {
988 setCatchStack(op.trueBranch(), recentCatchBlocks);
989 setCatchStack(op.falseBranch(), recentCatchBlocks);
990
991 if (getConditionForCondBrOp(op) instanceof BinaryTestOp btop) {
992 // Processing of the BinaryTestOp was deferred, so it can be merged with CondBrOp
993 conditionalBranch(prepareConditionalBranch(btop), op.trueBranch(), op.falseBranch());
994 } else {
995 processFirstOperand(op);
996 conditionalBranch(Opcode.IFEQ, op.trueBranch(), op.falseBranch());
997 }
998 }
999 case ConstantLabelSwitchOp op -> {
1000 op.successors().forEach(t -> setCatchStack(t, recentCatchBlocks));
1001 var cases = new ArrayList<SwitchCase>();
1002 int lo = Integer.MAX_VALUE;
1003 int hi = Integer.MIN_VALUE;
1004 Label defTarget = null;
1005 for (int i = 0; i < op.labels().size(); i++) {
1006 Integer val = op.labels().get(i);
1007 Label target = getLabel(op.successors().get(i));
1008 if (val == null) { // default target has null label value
1009 defTarget = target;
1010 } else {
1011 cases.add(SwitchCase.of(val, target));
1012 if (val < lo) lo = val;
1013 if (val > hi) hi = val;
1014 }
1015 }
1016 if (defTarget == null) {
1017 throw new IllegalArgumentException("Missing default target");
1018 }
1019 processFirstOperand(op);
1020 if (tableSwitchOverLookupSwitch(lo, hi, cases.size())) {
1021 cob.tableswitch(defTarget, cases);
1022 } else {
1023 cob.lookupswitch(defTarget, cases);
1024 }
1025 }
1026 case ExceptionRegionEnter op -> {
1027 List<Block.Reference> enteringCatchBlocks = op.catchBlocks();
1028 Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length + enteringCatchBlocks.size());
1029 int i = recentCatchBlocks.length;
1030 for (Block.Reference catchRef : enteringCatchBlocks) {
1031 allCatchBlocks.set(catchRef.targetBlock().index());
1032 activeCatchBlocks[i++] = catchRef.targetBlock();
1033 setCatchStack(catchRef, recentCatchBlocks);
1034 }
1035 setCatchStack(op.start(), activeCatchBlocks);
1036
1037 assignBlockArguments(op.start());
1038 cob.goto_(getLabel(op.start()));
1039 }
1040 case ExceptionRegionExit op -> {
1041 List<Block.Reference> exitingCatchBlocks = op.catchBlocks();
1042 Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length - exitingCatchBlocks.size());
1043 setCatchStack(op.end(), activeCatchBlocks);
1044
1045 // Assert block exits in reverse order
1046 int i = recentCatchBlocks.length;
1047 for (Block.Reference catchRef : exitingCatchBlocks) {
1048 assert catchRef.targetBlock() == recentCatchBlocks[--i];
1049 }
1050
1051 assignBlockArguments(op.end());
1052 cob.goto_(getLabel(op.end()));
1053 }
1054 default ->
1055 throw new UnsupportedOperationException("Terminating operation not supported: " + top);
1056 }
1057 }
1058 exceptionRegionsChange(new Block[0]);
1059 }
1060
1061 private void exceptionRegionsChange(Block[] newCatchBlocks) {
1062 if (!Arrays.equals(recentCatchBlocks, newCatchBlocks)) {
1063 int i = recentCatchBlocks.length - 1;
1064 Label currentLabel = cob.newBoundLabel();
1065 // Exit catch blocks missing in the newCatchBlocks
1066 while (i >=0 && (i >= newCatchBlocks.length || recentCatchBlocks[i] != newCatchBlocks[i])) {
1067 Block catchBlock = recentCatchBlocks[i--];
1068 List<Block.Parameter> params = catchBlock.parameters();
1069 if (!params.isEmpty()) {
1070 JavaType jt = (JavaType) params.get(0).type();
1071 cob.exceptionCatch(tryStartLabels[catchBlock.index()], currentLabel, getLabel(catchBlock.index()), jt.toNominalDescriptor());
1072 } else {
1073 cob.exceptionCatchAll(tryStartLabels[catchBlock.index()], currentLabel, getLabel(catchBlock.index()));
1074 }
1075 tryStartLabels[catchBlock.index()] = null;
1076 }
1077 // Fill tryStartLabels for new entries
1078 while (++i < newCatchBlocks.length) {
1079 tryStartLabels[newCatchBlocks[i].index()] = currentLabel;
1080 }
1081 recentCatchBlocks = newCatchBlocks;
1082 }
1083 }
1084
1085 // Determine whether to issue a tableswitch or a lookupswitch
1086 // instruction.
1087 private static boolean tableSwitchOverLookupSwitch(long lo, long hi, long nlabels) {
1088 long table_space_cost = 4 + (hi - lo + 1); // words
1089 long table_time_cost = 3; // comparisons
1090 long lookup_space_cost = 3 + 2 * nlabels;
1091 long lookup_time_cost = nlabels;
1092 return
1093 nlabels > 0 &&
1094 table_space_cost + 3 * table_time_cost <=
1095 lookup_space_cost + 3 * lookup_time_cost;
1096 }
1097
1098 // Checks if the Op.Result is used more than once in operands and block arguments
1099 private static boolean moreThanOneUse(Value val) {
1100 return val.uses().stream().flatMap(u ->
1101 Stream.concat(
1102 u.op().operands().stream(),
1103 u.op().successors().stream()
1104 .flatMap(r -> r.arguments().stream())))
1105 .filter(val::equals).limit(2).count() > 1;
1106 }
1107
1108 private void push(Value res) {
1109 assert oprOnStack == null;
1110 if (res.type().equals(JavaType.VOID)) return;
1111 if (isNextUse(res)) {
1112 if (moreThanOneUse(res)) {
1113 switch (toTypeKind(res.type()).slotSize()) {
1114 case 1 -> cob.dup();
1115 case 2 -> cob.dup2();
1116 }
1117 storeIfUsed(res);
1118 }
1119 oprOnStack = res;
1120 } else {
1121 storeIfUsed(res);
1122 oprOnStack = null;
1123 }
1124 }
1125
1126 // the rhs of any shift instruction must be int or smaller -> convert longs
1127 private void adjustRightTypeToInt(Op op) {
1128 TypeElement right = op.operands().getLast().type();
1129 if (right.equals(JavaType.LONG)) {
1130 cob.conversion(toTypeKind(right), TypeKind.INT);
1131 }
1132 }
1133
1134 private static Op getConditionForCondBrOp(ConditionalBranchOp op) {
1135 Value p = op.predicate();
1136 if (p.uses().size() != 1) {
1137 return null;
1138 }
1139
1140 if (p.declaringBlock() != op.ancestorBlock()) {
1141 return null;
1142 }
1143
1144 // Check if used in successor
1145 for (Block.Reference s : op.successors()) {
1146 if (s.arguments().contains(p)) {
1147 return null;
1148 }
1149 }
1150
1151 if (p instanceof Op.Result or) {
1152 return or.op();
1153 } else {
1154 return null;
1155 }
1156 }
1157
1158 private Method funcIntfMethod(Class<?> intfc, MethodTypeDesc mtd) {
1159 Method intfM = null;
1160 for (Method m : intfc.getMethods()) {
1161 // ensure it's SAM interface
1162 String methodName = m.getName();
1163 if (Modifier.isAbstract(m.getModifiers())
1164 && (m.getReturnType() != String.class
1165 || m.getParameterCount() != 0
1166 || !methodName.equals("toString"))
1167 && (m.getReturnType() != int.class
1168 || m.getParameterCount() != 0
1169 || !methodName.equals("hashCode"))
1170 && (m.getReturnType() != boolean.class
1171 || m.getParameterCount() != 1
1172 || m.getParameterTypes()[0] != Object.class
1173 || !methodName.equals("equals"))) {
1174 if (intfM == null && isAdaptable(m, mtd)) {
1175 intfM = m;
1176 } else if (!intfM.getName().equals(methodName)) {
1177 // too many abstract methods
1178 throw new IllegalArgumentException("Not a single-method interface: " + intfc.getName());
1179 }
1180 }
1181 }
1182 if (intfM == null) {
1183 throw new IllegalArgumentException("No method in: " + intfc.getName() + " matching: " + mtd);
1184 }
1185 return intfM;
1186 }
1187
1188 private static boolean isAdaptable(Method m, MethodTypeDesc mdesc) {
1189 // @@@ filter overrides
1190 return true;
1191 }
1192
1193 private static MethodTypeDesc toMTD(Method m) {
1194 return MethodTypeDesc.of(
1195 m.getReturnType().describeConstable().get(),
1196 Stream.of(m.getParameterTypes()).map(t -> t.describeConstable().get()).toList());
1197 }
1198
1199 private void conditionalBranch(Opcode reverseOpcode, Block.Reference trueBlock, Block.Reference falseBlock) {
1200 if (!needToAssignBlockArguments(falseBlock)) {
1201 cob.branch(reverseOpcode, getLabel(falseBlock));
1202 } else {
1203 cob.ifThen(reverseOpcode,
1204 bb -> {
1205 assignBlockArguments(falseBlock);
1206 bb.goto_(getLabel(falseBlock));
1207 });
1208 }
1209 assignBlockArguments(trueBlock);
1210 cob.goto_(getLabel(trueBlock));
1211 }
1212
1213 private Opcode prepareConditionalBranch(BinaryTestOp op) {
1214 Value firstOperand = op.operands().get(0);
1215 TypeKind typeKind = toTypeKind(firstOperand.type());
1216 Value secondOperand = op.operands().get(1);
1217 processOperand(firstOperand);
1218 if (isZeroIntOrNullConstant(secondOperand)) {
1219 return switch (typeKind) {
1220 case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1221 switch (op) {
1222 case EqOp _ -> Opcode.IFNE;
1223 case NeqOp _ -> Opcode.IFEQ;
1224 case GtOp _ -> Opcode.IFLE;
1225 case GeOp _ -> Opcode.IFLT;
1226 case LtOp _ -> Opcode.IFGE;
1227 case LeOp _ -> Opcode.IFGT;
1228 default ->
1229 throw new UnsupportedOperationException(op + " on int");
1230 };
1231 case REFERENCE ->
1232 switch (op) {
1233 case EqOp _ -> Opcode.IFNONNULL;
1234 case NeqOp _ -> Opcode.IFNULL;
1235 default ->
1236 throw new UnsupportedOperationException(op + " on Object");
1237 };
1238 default ->
1239 throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1240 };
1241 }
1242 processOperand(secondOperand);
1243 return switch (typeKind) {
1244 case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1245 switch (op) {
1246 case EqOp _ -> Opcode.IF_ICMPNE;
1247 case NeqOp _ -> Opcode.IF_ICMPEQ;
1248 case GtOp _ -> Opcode.IF_ICMPLE;
1249 case GeOp _ -> Opcode.IF_ICMPLT;
1250 case LtOp _ -> Opcode.IF_ICMPGE;
1251 case LeOp _ -> Opcode.IF_ICMPGT;
1252 default ->
1253 throw new UnsupportedOperationException(op + " on int");
1254 };
1255 case REFERENCE ->
1256 switch (op) {
1257 case EqOp _ -> Opcode.IF_ACMPNE;
1258 case NeqOp _ -> Opcode.IF_ACMPEQ;
1259 default ->
1260 throw new UnsupportedOperationException(op + " on Object");
1261 };
1262 case FLOAT -> {
1263 cob.fcmpg(); // FCMPL?
1264 yield reverseIfOpcode(op);
1265 }
1266 case LONG -> {
1267 cob.lcmp();
1268 yield reverseIfOpcode(op);
1269 }
1270 case DOUBLE -> {
1271 cob.dcmpg(); //CMPL?
1272 yield reverseIfOpcode(op);
1273 }
1274 default ->
1275 throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1276 };
1277 }
1278
1279 private boolean isZeroIntOrNullConstant(Value v) {
1280 return v instanceof Op.Result or
1281 && or.op() instanceof ConstantOp cop
1282 && switch (cop.value()) {
1283 case null -> true;
1284 case Integer i -> i == 0;
1285 case Boolean b -> !b;
1286 case Byte b -> b == 0;
1287 case Short s -> s == 0;
1288 case Character ch -> ch == 0;
1289 default -> false;
1290 };
1291 }
1292
1293 private static Opcode reverseIfOpcode(BinaryTestOp op) {
1294 return switch (op) {
1295 case EqOp _ -> Opcode.IFNE;
1296 case NeqOp _ -> Opcode.IFEQ;
1297 case GtOp _ -> Opcode.IFLE;
1298 case GeOp _ -> Opcode.IFLT;
1299 case LtOp _ -> Opcode.IFGE;
1300 case LeOp _ -> Opcode.IFGT;
1301 default ->
1302 throw new UnsupportedOperationException(op.toString());
1303 };
1304 }
1305
1306 private boolean needToAssignBlockArguments(Block.Reference ref) {
1307 List<Value> sargs = ref.arguments();
1308 List<Block.Parameter> bargs = ref.targetBlock().parameters();
1309 boolean need = false;
1310 for (int i = 0; i < bargs.size(); i++) {
1311 Block.Parameter barg = bargs.get(i);
1312 if (!barg.uses().isEmpty() && !barg.equals(sargs.get(i))) {
1313 need = true;
1314 allocateSlot(barg);
1315 }
1316 }
1317 return need;
1318 }
1319
1320 private void assignBlockArguments(Block.Reference ref) {
1321 Block target = ref.targetBlock();
1322 List<Value> sargs = ref.arguments();
1323 if (allCatchBlocks.get(target.index())) {
1324 // Jumping to an exception handler, exception parameter is expected on stack
1325 Value value = sargs.getFirst();
1326 if (oprOnStack == value) {
1327 oprOnStack = null;
1328 } else {
1329 load(value);
1330 }
1331 } else if (target.predecessors().size() > 1) {
1332 List<Block.Parameter> bargs = target.parameters();
1333 // First push successor arguments on the stack, then pop and assign
1334 // so as not to overwrite slots that are reused slots at different argument positions
1335 for (int i = 0; i < bargs.size(); i++) {
1336 Block.Parameter barg = bargs.get(i);
1337 Value value = sargs.get(i);
1338 if (!barg.uses().isEmpty() && !barg.equals(value)) {
1339 if (oprOnStack == value) {
1340 oprOnStack = null;
1341 } else {
1342 load(value);
1343 }
1344 storeIfUsed(barg);
1345 }
1346 }
1347 } else {
1348 // Single-predecessor block can just map parameter slots
1349 List<Block.Parameter> bargs = ref.targetBlock().parameters();
1350 for (int i = 0; i < bargs.size(); i++) {
1351 Value value = sargs.get(i);
1352 if (oprOnStack == value) {
1353 storeIfUsed(oprOnStack);
1354 oprOnStack = null;
1355 }
1356 // Map slot of the block argument to slot of the value
1357 singlePredecessorsValues.put(bargs.get(i), singlePredecessorsValues.getOrDefault(value, value));
1358 }
1359 }
1360 }
1361 }