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