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