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