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 try {
800 var info = lookup.revealDirect(md.resolveToHandle(lookup, op.invokeKind()));
801 protectedAccess = Modifier.isProtected(info.getModifiers())
802 && !info.getDeclaringClass().getPackageName().equals(lookup.lookupClass().getPackageName());
803 } catch (ReflectiveOperationException | IllegalArgumentException _) {
804 // @@@ protected access detection failed
805 }
806 if (protectedAccess) {
807 lookupHandle(specialCaller, md.name(), mDesc,
808 op.invokeKind() == InvokeOp.InvokeKind.STATIC ? "findStatic" : "findVirtual");
809 }
810 }
811 if (op.isVarArgs()) {
812 processOperands(op.argOperands());
813 var varArgOperands = op.varArgOperands();
814 var compType = ((ArrayType) op.invokeReference().signature().parameterTypes().getLast()).componentType();
815 loadArray(compType, varArgOperands);
816 } else {
817 processOperands(op);
818 }
819 Class<?> refClass;
820 try {
821 refClass = (Class<?>)refType.erasure().resolve(lookup);
822 } catch (ReflectiveOperationException e) {
823 throw new IllegalArgumentException(e);
824 }
825 boolean isInterface = refClass.isInterface();
826 switch (op.invokeKind()) {
827 case STATIC -> {
828 if (protectedAccess) {
829 cob.invokevirtual(CD_MethodHandle, "invokeExact", mDesc);
830 } else {
831 cob.invokestatic(refType.toNominalDescriptor(), md.name(), mDesc, isInterface);
832 }
833 }
834 case INSTANCE -> {
835 if (protectedAccess) {
836 cob.invokevirtual(CD_MethodHandle, "invokeExact", mDesc.insertParameterTypes(0, specialCaller));
837 } else {
838 cob.invoke(isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL,
839 refType.toNominalDescriptor(), md.name(), mDesc, isInterface);
840 }
841 }
842 case SUPER ->
843 cob.invokevirtual(CD_MethodHandle,
844 "invokeExact",
845 mDesc.insertParameterTypes(0, specialCaller));
846 }
847 ClassDesc ret = toClassDesc(op.resultType());
848 if (!ret.isPrimitive() && !ret.equals(mDesc.returnType())) {
849 // Explicit cast if method return type differs
850 cob.checkcast(ret);
851 }
852 push(op.result());
853 }
854 case FuncCallOp op -> {
855 Op.Invokable fop = functionMap.get(op.funcName());
856 if (fop == null) {
857 throw new IllegalArgumentException("Could not resolve function: " + op.funcName());
858 }
859 processOperands(op);
860 MethodTypeDesc mDesc = MethodRef.toNominalDescriptor(fop.invokableSignature());
861 cob.invoke(
862 INVOKESTATIC,
863 className,
864 op.funcName(),
865 mDesc,
866 false);
867 ClassDesc ret = toClassDesc(op.resultType());
868 if (ret.isClassOrInterface() && !ret.equals(mDesc.returnType())) {
869 // Explicit cast if method return type differs
870 cob.checkcast(ret);
871 }
872 push(op.result());
873 }
874 case FieldAccessOp.FieldLoadOp op -> fieldAccess(op);
875 case FieldAccessOp.FieldStoreOp op -> fieldAccess(op);
876 case InstanceOfOp op -> {
877 processFirstOperand(op);
878 cob.instanceOf(((JavaType) op.targetType()).toNominalDescriptor());
879 push(op.result());
880 }
881 case CastOp op -> {
882 processFirstOperand(op);
883 cob.checkcast(((JavaType) op.targetType()).toNominalDescriptor());
884 push(op.result());
885 }
886 case DynamicFuncCallOp op -> {
887 Op.Invokable fop = functionMap.get(op.funcName());
888 if (fop == null) {
889 throw new IllegalArgumentException("Could not resolve function: " + op.funcName());
890 }
891 processOperands(op);
892 cob.invokedynamic(DynamicCallSiteDesc.of(
893 op.bootstrapMethod(),
894 op.invocationName(),
895 op.invocationType(),
896 op.interfaceMethodType(),
897 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
898 className,
899 op.funcName(),
900 MethodRef.toNominalDescriptor(fop.invokableSignature())),
901 op.dynamicMethodType()));
902 push(op.result());
903 }
904 case ConcatOp op -> {
905 processOperands(op);
906 cob.invokedynamic(DynamicCallSiteDesc.of(DMHD_STRING_CONCAT, MethodTypeDesc.of(CD_String,
907 toClassDesc(op.operands().get(0).type()),
908 toClassDesc(op.operands().get(1).type()))));
909 push(op.result());
910 }
911 case MonitorOp.MonitorEnterOp op -> {
912 processFirstOperand(op);
913 cob.monitorenter();
914 }
915 case MonitorOp.MonitorExitOp op -> {
916 processFirstOperand(op);
917 cob.monitorexit();
918 }
919 default ->
920 throw new UnsupportedOperationException("Unsupported operation: " + ops.get(i));
921 }
922 }
923 Op top = b.terminatingOp();
924 switch (top) {
925 case ReturnOp op -> {
926 if (returnType != TypeKind.VOID) {
927 processFirstOperand(op);
928 // @@@ box, unbox, cast here ?
929 }
930 cob.return_(returnType);
931 }
932 case ThrowOp op -> {
933 processFirstOperand(op);
934 cob.athrow();
935 }
936 case BranchOp op -> {
937 setCatchStack(op.branch(), recentCatchBlocks);
938
939 assignBlockArguments(op.branch());
940 cob.goto_(getLabel(op.branch()));
941 }
942 case ConditionalBranchOp op -> {
943 setCatchStack(op.trueBranch(), recentCatchBlocks);
944 setCatchStack(op.falseBranch(), recentCatchBlocks);
945
946 if (getConditionForCondBrOp(op) instanceof CompareOp cop) {
947 // Processing of the BinaryTestOp was deferred, so it can be merged with CondBrOp
948 conditionalBranch(prepareConditionalBranch(cop), op.trueBranch(), op.falseBranch());
949 } else {
950 processFirstOperand(op);
951 conditionalBranch(IFEQ, op.trueBranch(), op.falseBranch());
952 }
953 }
954 case ConstantLabelSwitchOp op -> {
955 op.successors().forEach(t -> setCatchStack(t, recentCatchBlocks));
956 var cases = new ArrayList<SwitchCase>();
957 int lo = Integer.MAX_VALUE;
958 int hi = Integer.MIN_VALUE;
959 Label defTarget = null;
960 for (int i = 0; i < op.labels().size(); i++) {
961 Integer val = op.labels().get(i);
962 Label target = getLabel(op.successors().get(i));
963 if (val == null) { // default target has null label value
964 defTarget = target;
965 } else {
966 cases.add(SwitchCase.of(val, target));
967 if (val < lo) lo = val;
968 if (val > hi) hi = val;
969 }
970 }
971 if (defTarget == null) {
972 throw new IllegalArgumentException("Missing default target");
973 }
974 processFirstOperand(op);
975 if (tableSwitchOverLookupSwitch(lo, hi, cases.size())) {
976 cob.tableswitch(defTarget, cases);
977 } else {
978 cob.lookupswitch(defTarget, cases);
979 }
980 }
981 case ExceptionRegionEnter op -> {
982 List<Block.Reference> enteringCatchBlocks = op.catchReferences();
983 List<CodeType> enteringCatchTypes = op.catchTypes();
984 Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length + enteringCatchBlocks.size());
985 int i = recentCatchBlocks.length;
986 int catchIndex = 0;
987 for (Block.Reference catchRef : enteringCatchBlocks) {
988 catchBlockTypes[catchRef.targetBlock().index()] = enteringCatchTypes.get(catchIndex++);
989 activeCatchBlocks[i++] = catchRef.targetBlock();
990 setCatchStack(catchRef, recentCatchBlocks);
991 }
992 setCatchStack(op.startReference(), activeCatchBlocks);
993
994 assignBlockArguments(op.startReference());
995 cob.goto_(getLabel(op.startReference()));
996 }
997 case ExceptionRegionExit op -> {
998 List<Block.Reference> exitingCatchBlocks = op.enterOp().catchReferences().reversed();
999 Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length - exitingCatchBlocks.size());
1000 setCatchStack(op.endReference(), activeCatchBlocks);
1001
1002 // Assert block exits in reverse order
1003 int i = recentCatchBlocks.length;
1004 for (Block.Reference catchRef : exitingCatchBlocks) {
1005 assert catchRef.targetBlock() == recentCatchBlocks[--i];
1006 }
1007
1008 assignBlockArguments(op.endReference());
1009 cob.goto_(getLabel(op.endReference()));
1010 }
1011 default ->
1012 throw new UnsupportedOperationException("Terminating operation not supported: " + top);
1013 }
1014 }
1015 exceptionRegionsChange(new Block[0]);
1016 }
1017
1018 private void lookupHandle(ClassDesc owner, String name, ConstantDesc type, String finder) {
1019 // handle must precede any operand
1020 if (oprOnStack != null) {
1021 storeIfUsed(oprOnStack);
1022 oprOnStack = null;
1023 }
1024 cob.ldc(DynamicConstantDesc.of(BSM_CLASS_DATA))
1025 .checkcast(CD_MethodHandles_Lookup)
1026 .ldc(owner)
1027 .ldc(name)
1028 .ldc(type)
1029 .invokevirtual(CD_MethodHandles_Lookup,
1030 finder,
1031 type instanceof MethodTypeDesc ? MTD_FIND_METHOD : MTD_FIND_FIELD);
1032 }
1033
1034 private void fieldAccess(FieldAccessOp op) {
1035 FieldRef ref = op.fieldReference();
1036 JavaType refType = (JavaType) ref.refType();
1037 ClassDesc fieldType = ((JavaType) ref.type()).toNominalDescriptor();
1038 boolean store = op instanceof FieldAccessOp.FieldStoreOp;
1039 boolean isStatic = op.operands().size() == (store ? 1 : 0);
1040 boolean protectedAccess = false;
1041 try {
1042 Member m = ref.resolveToField(lookup);
1043 protectedAccess = Modifier.isProtected(m.getModifiers())
1044 && !m.getDeclaringClass().getPackageName().equals(lookup.lookupClass().getPackageName());
1045 } catch (ReflectiveOperationException | IllegalArgumentException _) {
1046 // @@@ protected access detection failed
1047 }
1048 ClassDesc caller = lookup.lookupClass().describeConstable().orElseThrow();
1049 if (protectedAccess) {
1050 lookupHandle(caller, ref.name(), fieldType, "find" + (isStatic ? "Static" : "") + (store ? "Setter" : "Getter"));
1051 }
1052 processOperands(op);
1053 if (protectedAccess) {
1054 cob.invokevirtual(CD_MethodHandle,
1055 "invokeExact",
1056 isStatic ? (store ? MethodTypeDesc.of(CD_void, fieldType)
1057 : MethodTypeDesc.of(fieldType))
1058 : (store ? MethodTypeDesc.of(CD_void, caller, fieldType)
1059 : MethodTypeDesc.of(fieldType, caller)));
1060 } else {
1061 cob.fieldAccess(isStatic ? (store ? PUTSTATIC : GETSTATIC)
1062 : (store ? PUTFIELD : GETFIELD),
1063 refType.toNominalDescriptor(),
1064 ref.name(),
1065 fieldType);
1066 }
1067 if (!store) {
1068 push(op.result());
1069 }
1070 }
1071
1072 private void loadArray(JavaType compType, List<Value> array) {
1073 cob.loadConstant(array.size());
1074 var compTypeDesc = compType.toNominalDescriptor();
1075 var typeKind = TypeKind.from(compTypeDesc);
1076 if (compTypeDesc.isPrimitive()) {
1077 cob.newarray(typeKind);
1078 } else {
1079 cob.anewarray(compTypeDesc);
1080 }
1081 for (int j = 0; j < array.size(); j++) {
1082 // we duplicate array value on the stack to be consumed by arrayStore
1083 // after completion of this loop the array value will be on top of the stack
1084 cob.dup();
1085 cob.loadConstant(j);
1086 load(array.get(j));
1087 cob.arrayStore(typeKind);
1088 }
1089 }
1090
1091 private void exceptionRegionsChange(Block[] newCatchBlocks) {
1092 if (!Arrays.equals(recentCatchBlocks, newCatchBlocks)) {
1093 int i = recentCatchBlocks.length - 1;
1094 Label currentLabel = cob.newBoundLabel();
1095 // Exit catch blocks missing in the newCatchBlocks
1096 while (i >=0 && (i >= newCatchBlocks.length || recentCatchBlocks[i] != newCatchBlocks[i])) {
1097 Block catchBlock = recentCatchBlocks[i--];
1098 CodeType catchType = catchBlockTypes[catchBlock.index()];
1099 Label tryStart = tryStartLabels[catchBlock.index()];
1100 Label handler = getLabel(catchBlock.index());
1101 switch (catchType) {
1102 case TupleType tt ->
1103 tt.componentTypes().forEach(type ->
1104 cob.exceptionCatch(tryStart, currentLabel, handler, ((JavaType) type).toNominalDescriptor()));
1105 case ClassType ct ->
1106 cob.exceptionCatch(tryStart, currentLabel, handler, ct.toNominalDescriptor());
1107 case PrimitiveType pt when pt.equals(JavaType.VOID) ->
1108 cob.exceptionCatchAll(tryStart, currentLabel, handler);
1109 default ->
1110 throw new IllegalArgumentException("Bad catch type: " + catchType);
1111 }
1112 tryStartLabels[catchBlock.index()] = null;
1113 }
1114 // Fill tryStartLabels for new entries
1115 while (++i < newCatchBlocks.length) {
1116 tryStartLabels[newCatchBlocks[i].index()] = currentLabel;
1117 }
1118 recentCatchBlocks = newCatchBlocks;
1119 }
1120 }
1121
1122 // Determine whether to issue a tableswitch or a lookupswitch
1123 // instruction.
1124 private static boolean tableSwitchOverLookupSwitch(long lo, long hi, long nlabels) {
1125 long table_space_cost = 4 + (hi - lo + 1); // words
1126 long table_time_cost = 3; // comparisons
1127 long lookup_space_cost = 3 + 2 * nlabels;
1128 long lookup_time_cost = nlabels;
1129 return
1130 nlabels > 0 &&
1131 table_space_cost + 3 * table_time_cost <=
1132 lookup_space_cost + 3 * lookup_time_cost;
1133 }
1134
1135 // Checks if the Op.Result is used more than once in operands and block arguments
1136 private static boolean moreThanOneUse(Value val) {
1137 return val.uses().stream().flatMap(u ->
1138 Stream.concat(
1139 u.op().operands().stream(),
1140 u.op().successors().stream()
1141 .flatMap(r -> r.arguments().stream())))
1142 .filter(val::equals).limit(2).count() > 1;
1143 }
1144
1145 private void push(Value res) {
1146 assert oprOnStack == null;
1147 if (res.type().equals(JavaType.VOID)) return;
1148 if (isNextUse(res)) {
1149 if (moreThanOneUse(res)) {
1150 switch (toTypeKind(res.type()).slotSize()) {
1151 case 1 -> cob.dup();
1152 case 2 -> cob.dup2();
1153 }
1154 storeIfUsed(res);
1155 }
1156 oprOnStack = res;
1157 } else {
1158 storeIfUsed(res);
1159 oprOnStack = null;
1160 }
1161 }
1162
1163 // the rhs of any shift instruction must be int or smaller -> convert longs
1164 private void adjustRightTypeToInt(Op op) {
1165 CodeType right = op.operands().getLast().type();
1166 if (right.equals(JavaType.LONG)) {
1167 cob.conversion(toTypeKind(right), TypeKind.INT);
1168 }
1169 }
1170
1171 private static Op getConditionForCondBrOp(ConditionalBranchOp op) {
1172 Value p = op.predicateOperand();
1173 if (p.uses().size() != 1) {
1174 return null;
1175 }
1176
1177 if (p.declaringBlock() != op.ancestorBlock()) {
1178 return null;
1179 }
1180
1181 // Check if used in successor
1182 for (Block.Reference s : op.successors()) {
1183 if (s.arguments().contains(p)) {
1184 return null;
1185 }
1186 }
1187
1188 if (p instanceof Op.Result or) {
1189 return or.op();
1190 } else {
1191 return null;
1192 }
1193 }
1194
1195 private void conditionalBranch(Opcode reverseOpcode, Block.Reference trueBlock, Block.Reference falseBlock) {
1196 if (!needToAssignBlockArguments(falseBlock)) {
1197 cob.branch(reverseOpcode, getLabel(falseBlock));
1198 } else {
1199 cob.ifThen(reverseOpcode,
1200 bb -> {
1201 assignBlockArguments(falseBlock);
1202 bb.goto_(getLabel(falseBlock));
1203 });
1204 }
1205 assignBlockArguments(trueBlock);
1206 cob.goto_(getLabel(trueBlock));
1207 }
1208
1209 private Opcode prepareConditionalBranch(CompareOp op) {
1210 Value firstOperand = op.operands().get(0);
1211 TypeKind typeKind = toTypeKind(firstOperand.type());
1212 Value secondOperand = op.operands().get(1);
1213 processOperand(firstOperand);
1214 if (isZeroIntOrNullConstant(secondOperand)) {
1215 return switch (typeKind) {
1216 case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1217 switch (op) {
1218 case EqOp _ -> IFNE;
1219 case NeqOp _ -> IFEQ;
1220 case GtOp _ -> IFLE;
1221 case GeOp _ -> IFLT;
1222 case LtOp _ -> IFGE;
1223 case LeOp _ -> IFGT;
1224 default ->
1225 throw new UnsupportedOperationException(op + " on int");
1226 };
1227 case REFERENCE ->
1228 switch (op) {
1229 case EqOp _ -> IFNONNULL;
1230 case NeqOp _ -> IFNULL;
1231 default ->
1232 throw new UnsupportedOperationException(op + " on Object");
1233 };
1234 default ->
1235 throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1236 };
1237 }
1238 processOperand(secondOperand);
1239 return switch (typeKind) {
1240 case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1241 switch (op) {
1242 case EqOp _ -> IF_ICMPNE;
1243 case NeqOp _ -> IF_ICMPEQ;
1244 case GtOp _ -> IF_ICMPLE;
1245 case GeOp _ -> IF_ICMPLT;
1246 case LtOp _ -> IF_ICMPGE;
1247 case LeOp _ -> IF_ICMPGT;
1248 default ->
1249 throw new UnsupportedOperationException(op + " on int");
1250 };
1251 case REFERENCE ->
1252 switch (op) {
1253 case EqOp _ -> IF_ACMPNE;
1254 case NeqOp _ -> IF_ACMPEQ;
1255 default ->
1256 throw new UnsupportedOperationException(op + " on Object");
1257 };
1258 case FLOAT -> {
1259 cob.fcmpg(); // FCMPL?
1260 yield reverseIfOpcode(op);
1261 }
1262 case LONG -> {
1263 cob.lcmp();
1264 yield reverseIfOpcode(op);
1265 }
1266 case DOUBLE -> {
1267 cob.dcmpg(); //CMPL?
1268 yield reverseIfOpcode(op);
1269 }
1270 default ->
1271 throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1272 };
1273 }
1274
1275 private boolean isZeroIntOrNullConstant(Value v) {
1276 return v instanceof Op.Result or
1277 && or.op() instanceof ConstantOp cop
1278 && switch (cop.value()) {
1279 case null -> true;
1280 case Integer i -> i == 0;
1281 case Boolean b -> !b;
1282 case Byte b -> b == 0;
1283 case Short s -> s == 0;
1284 case Character ch -> ch == 0;
1285 default -> false;
1286 };
1287 }
1288
1289 private static Opcode reverseIfOpcode(CompareOp op) {
1290 return switch (op) {
1291 case EqOp _ -> IFNE;
1292 case NeqOp _ -> IFEQ;
1293 case GtOp _ -> IFLE;
1294 case GeOp _ -> IFLT;
1295 case LtOp _ -> IFGE;
1296 case LeOp _ -> IFGT;
1297 default ->
1298 throw new UnsupportedOperationException(op.toString());
1299 };
1300 }
1301
1302 private boolean needToAssignBlockArguments(Block.Reference ref) {
1303 List<Value> sargs = ref.arguments();
1304 List<Block.Parameter> bargs = ref.targetBlock().parameters();
1305 boolean need = false;
1306 for (int i = 0; i < bargs.size(); i++) {
1307 Block.Parameter barg = bargs.get(i);
1308 if (!barg.uses().isEmpty() && !barg.equals(sargs.get(i))) {
1309 need = true;
1310 allocateSlot(barg);
1311 }
1312 }
1313 return need;
1314 }
1315
1316 private void assignBlockArguments(Block.Reference ref) {
1317 Block target = ref.targetBlock();
1318 List<Value> sargs = ref.arguments();
1319 if (catchBlockTypes[target.index()] != null) {
1320 // Jumping to an exception handler, exception parameter is expected on stack
1321 Value value = sargs.getFirst();
1322 if (oprOnStack == value) {
1323 oprOnStack = null;
1324 } else {
1325 load(value);
1326 }
1327 } else if (target.predecessors().size() > 1) {
1328 List<Block.Parameter> bargs = target.parameters();
1329 // First push successor arguments on the stack, then pop and assign
1330 // so as not to overwrite slots that are reused slots at different argument positions
1331 for (int i = 0; i < bargs.size(); i++) {
1332 Block.Parameter barg = bargs.get(i);
1333 Value value = sargs.get(i);
1334 if (!barg.equals(value)) {
1335 if (oprOnStack == value) {
1336 oprOnStack = null;
1337 } else {
1338 load(value);
1339 }
1340 storeIfUsed(barg);
1341 }
1342 }
1343 } else {
1344 // Single-predecessor block can just map parameter slots
1345 List<Block.Parameter> bargs = ref.targetBlock().parameters();
1346 for (int i = 0; i < bargs.size(); i++) {
1347 Value value = sargs.get(i);
1348 if (oprOnStack == value) {
1349 storeIfUsed(oprOnStack);
1350 oprOnStack = null;
1351 }
1352 // Map slot of the block argument to slot of the value
1353 singlePredecessorsValues.put(bargs.get(i), singlePredecessorsValues.getOrDefault(value, value));
1354 }
1355 }
1356 }
1357 }