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