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