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