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