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