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