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