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 Method intfMethod = funcIntfMethod(intfClass, mtd);
884 processOperands(op.capturedValues());
885 ClassDesc[] captureTypes = op.capturedValues().stream()
886 .map(Value::type).map(BytecodeGenerator::toClassDesc).toArray(ClassDesc[]::new);
887 int lambdaIndex = lambdaSink.size();
888 if (op.isQuotable()) {
889 cob.invokedynamic(DynamicCallSiteDesc.of(
890 DMHD_LAMBDA_ALT_METAFACTORY,
891 intfMethod.getName(),
892 MethodTypeDesc.of(intfType.toNominalDescriptor(),
893 captureTypes),
894 toMTD(intfMethod),
895 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
896 className,
897 "lambda$" + lambdaIndex,
898 mtd.insertParameterTypes(0, captureTypes)),
899 mtd,
900 LambdaMetafactory.FLAG_QUOTABLE,
901 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
902 className,
903 "op$lambda$" + lambdaIndex,
904 OP_METHOD_DESC)));
905 quotable.set(lambdaSink.size());
906 } else {
907 cob.invokedynamic(DynamicCallSiteDesc.of(
908 DMHD_LAMBDA_METAFACTORY,
909 intfMethod.getName(),
910 MethodTypeDesc.of(intfType.toNominalDescriptor(), captureTypes),
911 toMTD(intfMethod),
912 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
913 className,
914 "lambda$" + lambdaIndex,
915 mtd.insertParameterTypes(0, captureTypes)),
916 mtd));
917 }
918 lambdaSink.add(op);
919 } catch (ReflectiveOperationException e) {
920 throw new IllegalArgumentException(e);
921 }
922 push(op.result());
923 }
924 case ConcatOp op -> {
925 processOperands(op);
926 cob.invokedynamic(DynamicCallSiteDesc.of(DMHD_STRING_CONCAT, MethodTypeDesc.of(CD_String,
927 toClassDesc(op.operands().get(0).type()),
928 toClassDesc(op.operands().get(1).type()))));
929 push(op.result());
930 }
931 case MonitorOp.MonitorEnterOp op -> {
932 processFirstOperand(op);
933 cob.monitorenter();
934 }
935 case MonitorOp.MonitorExitOp op -> {
936 processFirstOperand(op);
937 cob.monitorexit();
938 }
939 default ->
940 throw new UnsupportedOperationException("Unsupported operation: " + ops.get(i));
941 }
942 }
943 Op top = b.terminatingOp();
944 switch (top) {
945 case ReturnOp op -> {
946 if (returnType != TypeKind.VOID) {
947 processFirstOperand(op);
948 // @@@ box, unbox, cast here ?
949 }
950 cob.return_(returnType);
951 }
952 case ThrowOp op -> {
953 processFirstOperand(op);
954 cob.athrow();
955 }
956 case BranchOp op -> {
957 setCatchStack(op.branch(), recentCatchBlocks);
958
959 assignBlockArguments(op.branch());
960 cob.goto_(getLabel(op.branch()));
961 }
962 case ConditionalBranchOp op -> {
963 setCatchStack(op.trueBranch(), recentCatchBlocks);
964 setCatchStack(op.falseBranch(), recentCatchBlocks);
965
966 if (getConditionForCondBrOp(op) instanceof BinaryTestOp btop) {
967 // Processing of the BinaryTestOp was deferred, so it can be merged with CondBrOp
968 conditionalBranch(prepareConditionalBranch(btop), op.trueBranch(), op.falseBranch());
969 } else {
970 processFirstOperand(op);
971 conditionalBranch(Opcode.IFEQ, op.trueBranch(), op.falseBranch());
972 }
973 }
974 case ExceptionRegionEnter op -> {
975 List<Block.Reference> enteringCatchBlocks = op.catchBlocks();
976 Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length + enteringCatchBlocks.size());
977 int i = recentCatchBlocks.length;
978 for (Block.Reference catchRef : enteringCatchBlocks) {
979 allCatchBlocks.set(catchRef.targetBlock().index());
980 activeCatchBlocks[i++] = catchRef.targetBlock();
981 setCatchStack(catchRef, recentCatchBlocks);
982 }
983 setCatchStack(op.start(), activeCatchBlocks);
984
985 assignBlockArguments(op.start());
986 cob.goto_(getLabel(op.start()));
987 }
988 case ExceptionRegionExit op -> {
989 List<Block.Reference> exitingCatchBlocks = op.catchBlocks();
990 Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length - exitingCatchBlocks.size());
991 setCatchStack(op.end(), activeCatchBlocks);
992
993 // Assert block exits in reverse order
994 int i = recentCatchBlocks.length;
995 for (Block.Reference catchRef : exitingCatchBlocks) {
996 assert catchRef.targetBlock() == recentCatchBlocks[--i];
997 }
998
999 assignBlockArguments(op.end());
1000 cob.goto_(getLabel(op.end()));
1001 }
1002 default ->
1003 throw new UnsupportedOperationException("Terminating operation not supported: " + top);
1004 }
1005 }
1006 exceptionRegionsChange(new Block[0]);
1007 }
1008
1009 private void exceptionRegionsChange(Block[] newCatchBlocks) {
1010 if (!Arrays.equals(recentCatchBlocks, newCatchBlocks)) {
1011 int i = recentCatchBlocks.length - 1;
1012 Label currentLabel = cob.newBoundLabel();
1013 // Exit catch blocks missing in the newCatchBlocks
1014 while (i >=0 && (i >= newCatchBlocks.length || recentCatchBlocks[i] != newCatchBlocks[i])) {
1015 Block catchBlock = recentCatchBlocks[i--];
1016 List<Block.Parameter> params = catchBlock.parameters();
1017 if (!params.isEmpty()) {
1018 JavaType jt = (JavaType) params.get(0).type();
1019 cob.exceptionCatch(tryStartLabels[catchBlock.index()], currentLabel, getLabel(catchBlock.index()), jt.toNominalDescriptor());
1020 } else {
1021 cob.exceptionCatchAll(tryStartLabels[catchBlock.index()], currentLabel, getLabel(catchBlock.index()));
1022 }
1023 tryStartLabels[catchBlock.index()] = null;
1024 }
1025 // Fill tryStartLabels for new entries
1026 while (++i < newCatchBlocks.length) {
1027 tryStartLabels[newCatchBlocks[i].index()] = currentLabel;
1028 }
1029 recentCatchBlocks = newCatchBlocks;
1030 }
1031 }
1032
1033 // Checks if the Op.Result is used more than once in operands and block arguments
1034 private static boolean moreThanOneUse(Value val) {
1035 return val.uses().stream().flatMap(u ->
1036 Stream.concat(
1037 u.op().operands().stream(),
1038 u.op().successors().stream()
1039 .flatMap(r -> r.arguments().stream())))
1040 .filter(val::equals).limit(2).count() > 1;
1041 }
1042
1043 private void push(Value res) {
1044 assert oprOnStack == null;
1045 if (res.type().equals(JavaType.VOID)) return;
1046 if (isNextUse(res)) {
1047 if (moreThanOneUse(res)) {
1048 switch (toTypeKind(res.type()).slotSize()) {
1049 case 1 -> cob.dup();
1050 case 2 -> cob.dup2();
1051 }
1052 storeIfUsed(res);
1053 }
1054 oprOnStack = res;
1055 } else {
1056 storeIfUsed(res);
1057 oprOnStack = null;
1058 }
1059 }
1060
1061 // the rhs of any shift instruction must be int or smaller -> convert longs
1062 private void adjustRightTypeToInt(Op op) {
1063 TypeElement right = op.operands().getLast().type();
1064 if (right.equals(JavaType.LONG)) {
1065 cob.conversion(toTypeKind(right), TypeKind.INT);
1066 }
1067 }
1068
1069 private static Op getConditionForCondBrOp(ConditionalBranchOp op) {
1070 Value p = op.predicate();
1071 if (p.uses().size() != 1) {
1072 return null;
1073 }
1074
1075 if (p.declaringBlock() != op.ancestorBlock()) {
1076 return null;
1077 }
1078
1079 // Check if used in successor
1080 for (Block.Reference s : op.successors()) {
1081 if (s.arguments().contains(p)) {
1082 return null;
1083 }
1084 }
1085
1086 if (p instanceof Op.Result or) {
1087 return or.op();
1088 } else {
1089 return null;
1090 }
1091 }
1092
1093 private Method funcIntfMethod(Class<?> intfc, MethodTypeDesc mtd) {
1094 Method intfM = null;
1095 for (Method m : intfc.getMethods()) {
1096 // ensure it's SAM interface
1097 String methodName = m.getName();
1098 if (Modifier.isAbstract(m.getModifiers())
1099 && (m.getReturnType() != String.class
1100 || m.getParameterCount() != 0
1101 || !methodName.equals("toString"))
1102 && (m.getReturnType() != int.class
1103 || m.getParameterCount() != 0
1104 || !methodName.equals("hashCode"))
1105 && (m.getReturnType() != boolean.class
1106 || m.getParameterCount() != 1
1107 || m.getParameterTypes()[0] != Object.class
1108 || !methodName.equals("equals"))) {
1109 if (intfM == null && isAdaptable(m, mtd)) {
1110 intfM = m;
1111 } else if (!intfM.getName().equals(methodName)) {
1112 // too many abstract methods
1113 throw new IllegalArgumentException("Not a single-method interface: " + intfc.getName());
1114 }
1115 }
1116 }
1117 if (intfM == null) {
1118 throw new IllegalArgumentException("No method in: " + intfc.getName() + " matching: " + mtd);
1119 }
1120 return intfM;
1121 }
1122
1123 private static boolean isAdaptable(Method m, MethodTypeDesc mdesc) {
1124 // @@@ filter overrides
1125 return true;
1126 }
1127
1128 private static MethodTypeDesc toMTD(Method m) {
1129 return MethodTypeDesc.of(
1130 m.getReturnType().describeConstable().get(),
1131 Stream.of(m.getParameterTypes()).map(t -> t.describeConstable().get()).toList());
1132 }
1133
1134 private void conditionalBranch(Opcode reverseOpcode, Block.Reference trueBlock, Block.Reference falseBlock) {
1135 if (!needToAssignBlockArguments(falseBlock)) {
1136 cob.branch(reverseOpcode, getLabel(falseBlock));
1137 } else {
1138 cob.ifThen(reverseOpcode,
1139 bb -> {
1140 assignBlockArguments(falseBlock);
1141 bb.goto_(getLabel(falseBlock));
1142 });
1143 }
1144 assignBlockArguments(trueBlock);
1145 cob.goto_(getLabel(trueBlock));
1146 }
1147
1148 private Opcode prepareConditionalBranch(BinaryTestOp op) {
1149 Value firstOperand = op.operands().get(0);
1150 TypeKind typeKind = toTypeKind(firstOperand.type());
1151 Value secondOperand = op.operands().get(1);
1152 processOperand(firstOperand);
1153 if (isZeroIntOrNullConstant(secondOperand)) {
1154 return switch (typeKind) {
1155 case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1156 switch (op) {
1157 case EqOp _ -> Opcode.IFNE;
1158 case NeqOp _ -> Opcode.IFEQ;
1159 case GtOp _ -> Opcode.IFLE;
1160 case GeOp _ -> Opcode.IFLT;
1161 case LtOp _ -> Opcode.IFGE;
1162 case LeOp _ -> Opcode.IFGT;
1163 default ->
1164 throw new UnsupportedOperationException(op + " on int");
1165 };
1166 case REFERENCE ->
1167 switch (op) {
1168 case EqOp _ -> Opcode.IFNONNULL;
1169 case NeqOp _ -> Opcode.IFNULL;
1170 default ->
1171 throw new UnsupportedOperationException(op + " on Object");
1172 };
1173 default ->
1174 throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1175 };
1176 }
1177 processOperand(secondOperand);
1178 return switch (typeKind) {
1179 case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1180 switch (op) {
1181 case EqOp _ -> Opcode.IF_ICMPNE;
1182 case NeqOp _ -> Opcode.IF_ICMPEQ;
1183 case GtOp _ -> Opcode.IF_ICMPLE;
1184 case GeOp _ -> Opcode.IF_ICMPLT;
1185 case LtOp _ -> Opcode.IF_ICMPGE;
1186 case LeOp _ -> Opcode.IF_ICMPGT;
1187 default ->
1188 throw new UnsupportedOperationException(op + " on int");
1189 };
1190 case REFERENCE ->
1191 switch (op) {
1192 case EqOp _ -> Opcode.IF_ACMPNE;
1193 case NeqOp _ -> Opcode.IF_ACMPEQ;
1194 default ->
1195 throw new UnsupportedOperationException(op + " on Object");
1196 };
1197 case FLOAT -> {
1198 cob.fcmpg(); // FCMPL?
1199 yield reverseIfOpcode(op);
1200 }
1201 case LONG -> {
1202 cob.lcmp();
1203 yield reverseIfOpcode(op);
1204 }
1205 case DOUBLE -> {
1206 cob.dcmpg(); //CMPL?
1207 yield reverseIfOpcode(op);
1208 }
1209 default ->
1210 throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1211 };
1212 }
1213
1214 private boolean isZeroIntOrNullConstant(Value v) {
1215 return v instanceof Op.Result or
1216 && or.op() instanceof ConstantOp cop
1217 && switch (cop.value()) {
1218 case null -> true;
1219 case Integer i -> i == 0;
1220 case Boolean b -> !b;
1221 case Byte b -> b == 0;
1222 case Short s -> s == 0;
1223 case Character ch -> ch == 0;
1224 default -> false;
1225 };
1226 }
1227
1228 private static Opcode reverseIfOpcode(BinaryTestOp op) {
1229 return switch (op) {
1230 case EqOp _ -> Opcode.IFNE;
1231 case NeqOp _ -> Opcode.IFEQ;
1232 case GtOp _ -> Opcode.IFLE;
1233 case GeOp _ -> Opcode.IFLT;
1234 case LtOp _ -> Opcode.IFGE;
1235 case LeOp _ -> Opcode.IFGT;
1236 default ->
1237 throw new UnsupportedOperationException(op.toString());
1238 };
1239 }
1240
1241 private boolean needToAssignBlockArguments(Block.Reference ref) {
1242 List<Value> sargs = ref.arguments();
1243 List<Block.Parameter> bargs = ref.targetBlock().parameters();
1244 boolean need = false;
1245 for (int i = 0; i < bargs.size(); i++) {
1246 Block.Parameter barg = bargs.get(i);
1247 if (!barg.uses().isEmpty() && !barg.equals(sargs.get(i))) {
1248 need = true;
1249 allocateSlot(barg);
1250 }
1251 }
1252 return need;
1253 }
1254
1255 private void assignBlockArguments(Block.Reference ref) {
1256 Block target = ref.targetBlock();
1257 List<Value> sargs = ref.arguments();
1258 if (allCatchBlocks.get(target.index())) {
1259 // Jumping to an exception handler, exception parameter is expected on stack
1260 Value value = sargs.getFirst();
1261 if (oprOnStack == value) {
1262 oprOnStack = null;
1263 } else {
1264 load(value);
1265 }
1266 } else if (target.predecessors().size() > 1) {
1267 List<Block.Parameter> bargs = target.parameters();
1268 // First push successor arguments on the stack, then pop and assign
1269 // so as not to overwrite slots that are reused slots at different argument positions
1270 for (int i = 0; i < bargs.size(); i++) {
1271 Block.Parameter barg = bargs.get(i);
1272 Value value = sargs.get(i);
1273 if (!barg.uses().isEmpty() && !barg.equals(value)) {
1274 if (oprOnStack == value) {
1275 oprOnStack = null;
1276 } else {
1277 load(value);
1278 }
1279 storeIfUsed(barg);
1280 }
1281 }
1282 } else {
1283 // Single-predecessor block can just map parameter slots
1284 List<Block.Parameter> bargs = ref.targetBlock().parameters();
1285 for (int i = 0; i < bargs.size(); i++) {
1286 Value value = sargs.get(i);
1287 if (oprOnStack == value) {
1288 storeIfUsed(oprOnStack);
1289 oprOnStack = null;
1290 }
1291 // Map slot of the block argument to slot of the value
1292 singlePredecessorsValues.put(bargs.get(i), singlePredecessorsValues.getOrDefault(value, value));
1293 }
1294 }
1295 }
1296 }