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