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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 import jdk.incubator.code.*;
25 import jdk.incubator.code.dialect.core.*;
26 import jdk.incubator.code.dialect.java.*;
27
28 import java.lang.classfile.Attributes;
29 import java.lang.classfile.ClassFile;
30 import java.lang.classfile.ClassModel;
31 import java.lang.classfile.CodeElement;
32 import java.lang.classfile.CodeModel;
33 import java.lang.classfile.Instruction;
34 import java.lang.classfile.Label;
35 import java.lang.classfile.MethodModel;
36 import java.lang.classfile.Opcode;
37 import java.lang.classfile.PseudoInstruction;
38 import java.lang.classfile.TypeKind;
39 import java.lang.classfile.attribute.CodeAttribute;
40 import java.lang.classfile.attribute.StackMapFrameInfo;
41 import java.lang.classfile.instruction.*;
42 import java.lang.constant.ClassDesc;
43 import java.lang.constant.ConstantDesc;
44 import java.lang.constant.ConstantDescs;
45 import java.lang.constant.DirectMethodHandleDesc;
46 import java.lang.constant.DynamicConstantDesc;
47 import java.lang.constant.MethodTypeDesc;
48 import java.lang.invoke.CallSite;
49 import java.lang.invoke.MethodHandle;
50 import java.lang.reflect.AccessFlag;
51 import java.util.ArrayDeque;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collections;
55 import java.util.Deque;
56 import java.util.function.ToIntFunction;
57 import java.util.HashMap;
58 import java.util.IdentityHashMap;
59 import java.util.LinkedHashMap;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.stream.Collectors;
63 import java.util.stream.IntStream;
64 import java.util.stream.Stream;
65
66 import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.*;
67 import static jdk.incubator.code.dialect.core.CoreOp.constant;
68 import static jdk.incubator.code.dialect.java.JavaType.type;
69
70 public final class BytecodeLift {
71
72 private static final ClassDesc CD_LambdaMetafactory = ClassDesc.ofDescriptor("Ljava/lang/invoke/LambdaMetafactory;");
73 private static final ClassDesc CD_StringConcatFactory = ClassDesc.ofDescriptor("Ljava/lang/invoke/StringConcatFactory;");
74 private static final JavaType MHS_LOOKUP = JavaType.type(ConstantDescs.CD_MethodHandles_Lookup);
75 private static final JavaType MH = JavaType.type(ConstantDescs.CD_MethodHandle);
76 private static final JavaType MT = JavaType.type(ConstantDescs.CD_MethodType);
77 private static final JavaType CLASS_ARRAY = JavaType.array(JavaType.J_L_CLASS);
78 private static final MethodRef LOOKUP = MethodRef.method(JavaType.type(ConstantDescs.CD_MethodHandles), "lookup", MHS_LOOKUP);
79 private static final MethodRef FIND_STATIC = MethodRef.method(MHS_LOOKUP, "findStatic", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, MT);
80 private static final MethodRef FIND_VIRTUAL = MethodRef.method(MHS_LOOKUP, "findVirtual", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, MT);
81 private static final MethodRef FIND_CONSTRUCTOR = MethodRef.method(MHS_LOOKUP, "findConstructor", MH, JavaType.J_L_CLASS, MT);
82 private static final MethodRef FIND_GETTER = MethodRef.method(MHS_LOOKUP, "findGetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
83 private static final MethodRef FIND_STATIC_GETTER = MethodRef.method(MHS_LOOKUP, "findStaticGetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
84 private static final MethodRef FIND_SETTER = MethodRef.method(MHS_LOOKUP, "findSetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
85 private static final MethodRef FIND_STATIC_SETTER = MethodRef.method(MHS_LOOKUP, "findStaticSetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
86 private static final MethodRef METHOD_TYPE_0 = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS);
87 private static final MethodRef METHOD_TYPE_1 = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS, JavaType.J_L_CLASS);
88 private static final MethodRef METHOD_TYPE_L = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS, CLASS_ARRAY);
89 private static final MethodRef COMPARE_LONG = MethodRef.method(Long.class, "compare", int.class, long.class, long.class);
90 private static final MethodRef COMPARE_FLOAT = MethodRef.method(Float.class, "compare", int.class, float.class, float.class);
91 private static final MethodRef COMPARE_DOUBLE = MethodRef.method(Double.class, "compare", int.class, double.class, double.class);
92
93 private final Block.Builder entryBlock;
94 private final List<Value> initialValues;
95 private final ClassModel classModel;
96 // Cached blocks that enter a handler from a region stack
97 private final Map<CatchTargetKey, Block.Builder> exceptionHandlerBlocks;
98 // Cached blocks that enter regions at a bytecode index
99 private final Map<Integer, Block.Builder> labelEntryBlocks;
100 // Active region stacks at bytecode positions where they change
101 private final Map<Integer, List<ExceptionRegion>> exceptionRegionsMap;
102 // Entered region results recorded for each block
103 private final Map<Block.Builder, List<Op.Result>> enteredRegionStacks;
104 // Region owned by each enter op result
105 private final Map<Op.Result, ExceptionRegion> enteredRegionMap;
106 // Stack map blocks keyed by bytecode index
107 private final Map<Integer, Block.Builder> blockMap;
108 private final List<CodeElement> elements;
109 private final Deque<Value> stack;
110 private final Deque<ClassDesc> newStack;
111 // Bytecode index for each exception table handler
112 private final List<Integer> handlerBcis;
113 private final List<CodeType> catchTypes;
114 // Converts classfile labels to bytecode indexes
115 private final ToIntFunction<Label> label2Bci;
116 // Current entered region result stack
117 private List<Op.Result> actualEreStack;
118 private Block.Builder currentBlock;
119
120 private BytecodeLift(Block.Builder entryBlock, ClassModel classModel, CodeModel codeModel, Value... capturedValues) {
121 this.entryBlock = entryBlock;
122 this.initialValues = Stream.concat(Stream.of(capturedValues), entryBlock.parameters().stream()).toList();
123 this.currentBlock = entryBlock;
124 this.classModel = classModel;
125 this.exceptionHandlerBlocks = new HashMap<>();
126 this.labelEntryBlocks = new HashMap<>();
127 this.enteredRegionStacks = new IdentityHashMap<>();
128 this.enteredRegionMap = new IdentityHashMap<>();
129 this.actualEreStack = List.of();
130 this.newStack = new ArrayDeque<>();
131 this.label2Bci = ((CodeAttribute)codeModel)::labelToBci;
132 this.stack = new ArrayDeque<>();
133 this.blockMap = codeModel.findAttribute(Attributes.stackMapTable()).map(sma ->
134 sma.entries().stream().collect(Collectors.toUnmodifiableMap(
135 smfi -> label2Bci.applyAsInt(smfi.target()),
136 smfi -> entryBlock.block(toBlockParams(smfi.stack()))))).orElseGet(Map::of);
137 this.handlerBcis = new ArrayList<>();
138 this.catchTypes = new ArrayList<>();
139 record RegionKey(int start, int end) {}
140 Map<RegionKey, List<Integer>> grouped = new LinkedHashMap<>();
141 for (ExceptionCatch ec : codeModel.exceptionHandlers()) {
142 int handler = handlerBcis.size();
143 handlerBcis.add(label2Bci.applyAsInt(ec.handler()));
144 catchTypes.add(ec.catchType().map(ct -> JavaType.type(ct.asSymbol())).orElse(JavaType.VOID));
145 grouped.computeIfAbsent(new RegionKey(label2Bci.applyAsInt(ec.tryStart()), label2Bci.applyAsInt(ec.tryEnd())), _ -> new ArrayList<>())
146 .add(handler);
147 }
148 List<ExceptionRegion> regions = new ArrayList<>();
149 for (var c : grouped.entrySet()) {
150 regions.add(new ExceptionRegion(regions.size(), c.getKey().start(), c.getKey().end(), c.getValue()));
151 }
152 this.exceptionRegionsMap = new HashMap<>();
153 List<ExceptionRegion> previous = List.of();
154 List<CodeElement> elements = codeModel.elementList();
155 for (CodeElement e : elements) {
156 if (e instanceof LabelTarget lt) {
157 int bci = label2Bci.applyAsInt(lt.label());
158 List<ExceptionRegion> next = regions.stream()
159 .filter(er -> er.start() <= bci && bci < er.end())
160 .sorted()
161 .toList();
162 if (!next.equals(previous) || blockMap.containsKey(bci)) {
163 exceptionRegionsMap.put(bci, next);
164 }
165 previous = next;
166 }
167 }
168 // filter out pseudo instructions that may interfere with expected instructions sequence
169 this.elements = elements.stream().filter(e ->
170 e instanceof Instruction || e instanceof LabelTarget lt
171 && exceptionRegionsMap.containsKey(label2Bci.applyAsInt(lt.label()))).toList();
172 }
173
174 // One bytecode try range and its exception table handler indexes
175 record ExceptionRegion(int index, int start, int end, List<Integer> handlers) implements Comparable<ExceptionRegion> {
176
177 // Sort outer regions before inner regions
178 @Override
179 public int compareTo(ExceptionRegion o) {
180 int c = Integer.compare(start, o.start);
181 if (c != 0) return c;
182 c = Integer.compare(o.end, end);
183 return c != 0 ? c : Integer.compare(index, o.index);
184 }
185 }
186
187 // Cache key for a handler reached from a concrete region stack
188 record CatchTargetKey(int handler, List<Op.Result> enteredRegions, List<Map.Entry<Integer, CodeType>> handlers) {}
189
190 // Handlers block references with related catch types
191 record CatchEntries(List<CodeType> types, List<Block.Reference> references) {}
192
193 private List<CodeType> toBlockParams(List<StackMapFrameInfo.VerificationTypeInfo> vtis) {
194 ArrayList<CodeType> params = new ArrayList<>(vtis.size());
195 for (int i = vtis.size() - 1; i >= 0; i--) {
196 var vti = vtis.get(i);
197 switch (vti) {
198 case INTEGER -> params.add(UnresolvedType.unresolvedInt());
199 case FLOAT -> params.add(JavaType.FLOAT);
200 case DOUBLE -> params.add(JavaType.DOUBLE);
201 case LONG -> params.add(JavaType.LONG);
202 case NULL -> params.add(UnresolvedType.unresolvedRef());
203 case UNINITIALIZED_THIS ->
204 params.add(JavaType.type(classModel.thisClass().asSymbol()));
205 case StackMapFrameInfo.ObjectVerificationTypeInfo ovti ->
206 params.add(JavaType.type(ovti.classSymbol()));
207
208 // Unitialized entry (a new object before its constructor is called)
209 // must be skipped from block parameters because they do not exist in code reflection model
210 case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> {}
211 default ->
212 throw new IllegalArgumentException("Unexpected VTI: " + vti);
213 }
214 }
215 return params;
216 }
217
218 private Op.Result op(Op op) {
219 return currentBlock.add(op);
220 }
221
222 // Lift to core dialect
223 public static CoreOp.FuncOp lift(byte[] classdata, String methodName) {
224 return lift(classdata, methodName, null);
225 }
226
227 public static CoreOp.FuncOp lift(byte[] classdata, String methodName, MethodTypeDesc methodType) {
228 return lift(ClassFile.of(
229 ClassFile.DebugElementsOption.DROP_DEBUG,
230 ClassFile.LineNumbersOption.DROP_LINE_NUMBERS).parse(classdata).methods().stream()
231 .filter(mm -> mm.methodName().equalsString(methodName) && (methodType == null || mm.methodTypeSymbol().equals(methodType)))
232 .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown method: " + methodName)));
233 }
234
235 public static CoreOp.FuncOp lift(MethodModel methodModel) {
236 ClassModel classModel = methodModel.parent().orElseThrow();
237 MethodTypeDesc mDesc = methodModel.methodTypeSymbol();
238 if (!methodModel.flags().has(AccessFlag.STATIC)) {
239 mDesc = mDesc.insertParameterTypes(0, classModel.thisClass().asSymbol());
240 }
241 return NormalizeExceptionRegionsTransformer.transform(
242 NormalizeBlocksTransformer.transform(
243 UnresolvedTypesTransformer.transform(
244 SlotToVarTransformer.transform(
245 CoreOp.func(methodModel.methodName().stringValue(),
246 MethodRef.ofNominalDescriptor(mDesc)).body(entryBlock ->
247 new BytecodeLift(entryBlock,
248 classModel,
249 methodModel.code().orElseThrow()).liftBody())))));
250 }
251
252 private void liftBody() {
253 // store entry block
254 int slot = 0;
255 for (var ep : initialValues) {
256 op(SlotOp.store(slot, ep));
257 slot += ep.type().equals(JavaType.LONG) || ep.type().equals(JavaType.DOUBLE) ? 2 : 1;
258 }
259
260 for (int i = 0; i < elements.size(); i++) {
261 CodeElement element = elements.get(i);
262 Opcode numericCompare = null;
263 if (element instanceof OperatorInstruction inst
264 && switch (inst.opcode()) {
265 case LCMP, FCMPL, FCMPG, DCMPL, DCMPG -> true;
266 default -> false;}
267 && i + 1 < elements.size()
268 && (elements.get(i + 1) instanceof BranchInstruction branch)
269 && switch (branch.opcode()) {
270 case IFEQ, IFNE, IFLT, IFLE, IFGT, IFGE -> true;
271 default -> false;
272 }) {
273 // pre-scan if a numeric compare preceeds conditional branch
274 numericCompare = inst.opcode();
275 element = elements.get(++i);
276 }
277 switch (element) {
278 case ExceptionCatch _ -> {
279 // Exception blocks are inserted by label target (below)
280 }
281 case LabelTarget lt -> {
282 int bci = label2Bci.applyAsInt(lt.label());
283 List<ExceptionRegion> newEreStack = exceptionRegionsMap.get(bci);
284 if (newEreStack != null) {
285 Block.Builder target = blockMap.get(bci);
286 if (target != null) {
287 if (currentBlock != null) {
288 // Transition to a branch target or a handler
289 exitRegions(actualEreStack, currentBlock, targetEntryBlock(bci), stackValues(target));
290 }
291 actualEreStack = enteredRegionStacks.getOrDefault(target, List.of());
292 currentBlock = target;
293 stack.clear();
294 stack.addAll(target.parameters());
295 } else if (currentBlock != null && !actualEreStack.stream().map(enteredRegionMap::get).toList().equals(newEreStack)) {
296 // Transition to a block with a different ERE stack
297 Block.Builder next = entryBlock.block();
298 actualEreStack = ereTransit(newEreStack, next);
299 currentBlock = next;
300 }
301 }
302 }
303 case BranchInstruction inst when isUnconditionalBranch(inst.opcode()) -> {
304 int targetBci = label2Bci.applyAsInt(inst.target());
305 Block.Builder target = blockMap.get(targetBci);
306 exitRegions(actualEreStack, currentBlock, targetEntryBlock(targetBci), stackValues(target));
307 endOfFlow();
308 }
309 case BranchInstruction inst -> {
310 // Conditional branch
311 Value right = stack.pop();
312 Value left;
313 switch (inst.opcode()) {
314 case IFNULL, IFNONNULL -> {
315 left = right;
316 right = liftConstant(null);
317 }
318 case IFEQ, IFNE, IFLT, IFLE, IFGT, IFGE -> {
319 if (numericCompare == null) {
320 left = right;
321 right = liftConstant(0);
322 } else {
323 left = stack.pop();
324 }
325 }
326 default -> left = stack.pop();
327 }
328 boolean branchOnTrue = false;
329 Op cop = switch (inst.opcode()) {
330 case IFNE, IF_ICMPNE, IF_ACMPNE, IFNONNULL -> JavaOp.eq(left, right);
331 case IFEQ, IF_ICMPEQ, IF_ACMPEQ, IFNULL -> JavaOp.neq(left, right);
332 case IFGE, IF_ICMPGE -> {
333 if (numericCompare == Opcode.FCMPL || numericCompare == Opcode.DCMPL) {
334 branchOnTrue = true;
335 yield JavaOp.ge(left, right);
336 }
337 yield JavaOp.lt(left, right);
338 }
339 case IFLE, IF_ICMPLE -> {
340 if (numericCompare == Opcode.FCMPG || numericCompare == Opcode.DCMPG) {
341 branchOnTrue = true;
342 yield JavaOp.le(left, right);
343 }
344 yield JavaOp.gt(left, right);
345 }
346 case IFGT, IF_ICMPGT -> {
347 if (numericCompare == Opcode.FCMPL || numericCompare == Opcode.DCMPL) {
348 branchOnTrue = true;
349 yield JavaOp.gt(left, right);
350 }
351 yield JavaOp.le(left, right);
352 }
353 case IFLT, IF_ICMPLT -> {
354 if (numericCompare == Opcode.FCMPG || numericCompare == Opcode.DCMPG) {
355 branchOnTrue = true;
356 yield JavaOp.lt(left, right);
357 }
358 yield JavaOp.ge(left, right);
359 }
360 default -> throw new UnsupportedOperationException("Unsupported branch instruction: " + inst);
361 };
362 Block.Builder branch = transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(inst.target()));
363 Block.Builder next = entryBlock.block();
364 Block.Reference taken = successorWithStack(branch);
365 Block.Reference fallthrough = next.reference();
366 op(CoreOp.conditionalBranch(op(cop),
367 branchOnTrue ? taken : fallthrough,
368 branchOnTrue ? fallthrough : taken));
369 currentBlock = next;
370 }
371 case LookupSwitchInstruction si -> {
372 liftSwitch(si.defaultTarget(), si.cases());
373 }
374 case TableSwitchInstruction si -> {
375 liftSwitch(si.defaultTarget(), si.cases());
376 }
377 case ReturnInstruction inst when inst.typeKind() == TypeKind.VOID -> {
378 op(CoreOp.return_());
379 endOfFlow();
380 }
381 case ReturnInstruction _ -> {
382 op(CoreOp.return_(stack.pop()));
383 endOfFlow();
384 }
385 case ThrowInstruction _ -> {
386 op(JavaOp.throw_(stack.pop()));
387 endOfFlow();
388 }
389 case LoadInstruction inst -> {
390 stack.push(op(SlotOp.load(inst.slot(), inst.typeKind())));
391 }
392 case StoreInstruction inst -> {
393 op(SlotOp.store(inst.slot(), stack.pop()));
394 }
395 case IncrementInstruction inst -> {
396 op(SlotOp.store(inst.slot(), op(JavaOp.add(op(SlotOp.load(inst.slot(), TypeKind.INT)), liftConstant(inst.constant())))));
397 }
398 case ConstantInstruction inst -> {
399 stack.push(liftConstant(inst.constantValue()));
400 }
401 case ConvertInstruction inst -> {
402 stack.push(op(JavaOp.conv(switch (inst.toType()) {
403 case BYTE -> JavaType.BYTE;
404 case SHORT -> JavaType.SHORT;
405 case INT -> JavaType.INT;
406 case FLOAT -> JavaType.FLOAT;
407 case LONG -> JavaType.LONG;
408 case DOUBLE -> JavaType.DOUBLE;
409 case CHAR -> JavaType.CHAR;
410 case BOOLEAN -> JavaType.BOOLEAN;
411 default ->
412 throw new IllegalArgumentException("Unsupported conversion target: " + inst.toType());
413 }, stack.pop())));
414 }
415 case OperatorInstruction inst -> {
416 Value operand = stack.pop();
417 stack.push(op(switch (inst.opcode()) {
418 case IADD, LADD, FADD, DADD ->
419 JavaOp.add(stack.pop(), operand);
420 case ISUB, LSUB, FSUB, DSUB ->
421 JavaOp.sub(stack.pop(), operand);
422 case IMUL, LMUL, FMUL, DMUL ->
423 JavaOp.mul(stack.pop(), operand);
424 case IDIV, LDIV, FDIV, DDIV ->
425 JavaOp.div(stack.pop(), operand);
426 case IREM, LREM, FREM, DREM ->
427 JavaOp.mod(stack.pop(), operand);
428 case INEG, LNEG, FNEG, DNEG ->
429 JavaOp.neg(operand);
430 case ARRAYLENGTH ->
431 JavaOp.arrayLength(operand);
432 case IAND, LAND ->
433 JavaOp.and(stack.pop(), operand);
434 case IOR, LOR ->
435 JavaOp.or(stack.pop(), operand);
436 case IXOR, LXOR ->
437 JavaOp.xor(stack.pop(), operand);
438 case ISHL, LSHL ->
439 JavaOp.lshl(stack.pop(), operand);
440 case ISHR, LSHR ->
441 JavaOp.ashr(stack.pop(), operand);
442 case IUSHR, LUSHR ->
443 JavaOp.lshr(stack.pop(), operand);
444 // standalone numeric compares are not reguraly produced by javac
445 case LCMP ->
446 // LCMP is equivalent to Long::compare
447 JavaOp.invoke(COMPARE_LONG, stack.pop(), operand);
448 case FCMPL, FCMPG ->
449 // @@@ no suitable operator or API equivalent, NaN operands produce different results
450 // comparison of negative zero is compensated
451 JavaOp.invoke(COMPARE_FLOAT, op(JavaOp.add(stack.pop(), liftConstant(0f))),
452 op(JavaOp.add(operand, liftConstant(0f))));
453 case DCMPL, DCMPG ->
454 // @@@ no suitable operator or API equivalent, NaN operands produce different results
455 // comparison of negative zero is compensated
456 JavaOp.invoke(COMPARE_DOUBLE, op(JavaOp.add(stack.pop(), liftConstant(0d))),
457 op(JavaOp.add(operand, liftConstant(0d))));
458 default ->
459 throw new IllegalArgumentException("Unsupported operator opcode: " + inst.opcode());
460 }));
461 }
462 case FieldInstruction inst -> {
463 FieldRef fd = FieldRef.field(
464 JavaType.type(inst.owner().asSymbol()),
465 inst.name().stringValue(),
466 JavaType.type(inst.typeSymbol()));
467 switch (inst.opcode()) {
468 case GETFIELD ->
469 stack.push(op(JavaOp.fieldLoad(fd, stack.pop())));
470 case GETSTATIC ->
471 stack.push(op(JavaOp.fieldLoad(fd)));
472 case PUTFIELD -> {
473 Value value = stack.pop();
474 op(JavaOp.fieldStore(fd, stack.pop(), value));
475 }
476 case PUTSTATIC ->
477 op(JavaOp.fieldStore(fd, stack.pop()));
478 default ->
479 throw new IllegalArgumentException("Unsupported field opcode: " + inst.opcode());
480 }
481 }
482 case ArrayStoreInstruction _ -> {
483 Value value = stack.pop();
484 Value index = stack.pop();
485 op(JavaOp.arrayStoreOp(stack.pop(), index, value));
486 }
487 case ArrayLoadInstruction ali -> {
488 Value index = stack.pop();
489 Value array = stack.pop();
490 if (array.type() instanceof UnresolvedType) {
491 stack.push(op(JavaOp.arrayLoadOp(array, index, switch (ali.typeKind()) {
492 case BYTE -> UnresolvedType.unresolvedInt(); // @@@ Create UnresolvedType.unresolvedByteOrBoolean();
493 case CHAR -> JavaType.CHAR;
494 case DOUBLE -> JavaType.DOUBLE;
495 case FLOAT -> JavaType.FLOAT;
496 case INT -> JavaType.INT;
497 case LONG -> JavaType.LONG;
498 case SHORT -> JavaType.SHORT;
499 case REFERENCE -> UnresolvedType.unresolvedRef();
500 case BOOLEAN, VOID -> throw new IllegalArgumentException("Unexpected array load instruction type");
501 })));
502 } else {
503 stack.push(op(JavaOp.arrayLoadOp(array, index)));
504 }
505 }
506 case InvokeInstruction inst -> {
507 FunctionType mType = MethodRef.ofNominalDescriptor(inst.typeSymbol());
508 List<Value> operands = new ArrayList<>();
509 for (var _ : mType.parameterTypes()) {
510 operands.add(stack.pop());
511 }
512 MethodRef mDesc = MethodRef.method(
513 JavaType.type(inst.owner().asSymbol()),
514 inst.name().stringValue(),
515 mType);
516 Op.Result result = switch (inst.opcode()) {
517 case INVOKEVIRTUAL, INVOKEINTERFACE -> {
518 operands.add(stack.pop());
519 yield op(JavaOp.invoke(JavaOp.InvokeOp.InvokeKind.INSTANCE, false,
520 mDesc.signature().returnType(), mDesc, operands.reversed()));
521 }
522 case INVOKESTATIC ->
523 op(JavaOp.invoke(JavaOp.InvokeOp.InvokeKind.STATIC, false,
524 mDesc.signature().returnType(), mDesc, operands.reversed()));
525 case INVOKESPECIAL -> {
526 if (inst.owner().asSymbol().equals(newStack.peek()) && inst.name().equalsString(ConstantDescs.INIT_NAME)) {
527 newStack.pop();
528 yield op(JavaOp.new_(
529 MethodRef.constructor(
530 mDesc.refType(),
531 mType.parameterTypes()),
532 operands.reversed()));
533 } else {
534 operands.add(stack.pop());
535 yield op(JavaOp.invoke(JavaOp.InvokeOp.InvokeKind.SUPER, false,
536 mDesc.signature().returnType(), mDesc, operands.reversed()));
537 }
538 }
539 default ->
540 throw new IllegalArgumentException("Unsupported invocation opcode: " + inst.opcode());
541 };
542 if (!result.type().equals(JavaType.VOID)) {
543 stack.push(result);
544 }
545 }
546 case InvokeDynamicInstruction inst when inst.bootstrapMethod().kind() == DirectMethodHandleDesc.Kind.STATIC -> {
547 DirectMethodHandleDesc bsm = inst.bootstrapMethod();
548 ClassDesc bsmOwner = bsm.owner();
549 if (bsmOwner.equals(CD_LambdaMetafactory)
550 && inst.bootstrapArgs().get(0) instanceof MethodTypeDesc mtd
551 && inst.bootstrapArgs().get(1) instanceof DirectMethodHandleDesc dmhd) {
552
553 var capturedValues = new Value[dmhd.invocationType().parameterCount() - mtd.parameterCount()];
554 for (int ci = capturedValues.length - 1; ci >= 0; ci--) {
555 capturedValues[ci] = stack.pop();
556 }
557 for (int ci = capturedValues.length; ci < inst.typeSymbol().parameterCount(); ci++) {
558 stack.pop();
559 }
560 MethodTypeDesc mt = dmhd.invocationType();
561 if (capturedValues.length > 0) {
562 mt = mt.dropParameterTypes(0, capturedValues.length);
563 }
564 FunctionType lambdaFunc = CoreType.functionType(JavaType.type(mt.returnType()),
565 mt.parameterList().stream().map(JavaType::type).toList());
566 JavaOp.LambdaOp.Builder lambda = JavaOp.lambda(currentBlock.parentBody(),
567 lambdaFunc,
568 JavaType.type(inst.typeSymbol().returnType()));
569 // if ReflectableLambdaMetafactory is used, the lambda is reflectable
570 if (bsm.owner().displayName().equals("jdk.incubator.code.runtime.ReflectableLambdaMetafactory")) {
571 lambda = lambda.reflectable();
572 }
573
574 if (dmhd.methodName().startsWith("lambda$") && dmhd.owner().equals(classModel.thisClass().asSymbol())) {
575 // inline lambda impl method
576 MethodModel implMethod = classModel.methods().stream().filter(m -> m.methodName().equalsString(dmhd.methodName())).findFirst().orElseThrow();
577 stack.push(op(lambda.body(eb -> new BytecodeLift(eb,
578 classModel,
579 implMethod.code().orElseThrow(),
580 capturedValues).liftBody())));
581 } else {
582 // lambda call to a MH
583 stack.push(op(lambda.body(eb -> {
584 Op.Result ret = eb.add(JavaOp.invoke(
585 MethodRef.method(JavaType.type(dmhd.owner()),
586 dmhd.methodName(),
587 lambdaFunc.returnType(),
588 lambdaFunc.parameterTypes()),
589 Stream.concat(Arrays.stream(capturedValues), eb.parameters().stream()).toArray(Value[]::new)));
590 eb.add(ret.type().equals(JavaType.VOID) ? CoreOp.return_() : CoreOp.return_(ret));
591 })));
592 }
593 } else if (bsmOwner.equals(CD_StringConcatFactory)) {
594 int argsCount = inst.typeSymbol().parameterCount();
595 Deque<Value> args = new ArrayDeque<>(argsCount);
596 for (int ai = 0; ai < argsCount; ai++) {
597 args.push(stack.pop());
598 }
599 Value res = null;
600 if (bsm.methodName().equals("makeConcat")) {
601 for (Value argVal : args) {
602 res = res == null ? argVal : op(JavaOp.concat(res, argVal));
603 }
604 } else {
605 assert bsm.methodName().equals("makeConcatWithConstants");
606 var bsmArgs = inst.bootstrapArgs();
607 String recipe = (String)(bsmArgs.getFirst());
608 int bsmArg = 1;
609 for (int ri = 0; ri < recipe.length(); ri++) {
610 Value argVal = switch (recipe.charAt(ri)) {
611 case '\u0001' -> args.pop();
612 case '\u0002' -> liftConstant(bsmArgs.get(bsmArg++));
613 default -> {
614 char c;
615 int start = ri;
616 while (ri < recipe.length() && (c = recipe.charAt(ri)) != '\u0001' && c != '\u0002') ri++;
617 yield liftConstant(recipe.substring(start, ri--));
618 }
619 };
620 res = res == null ? argVal : op(JavaOp.concat(res, argVal));
621 }
622 }
623 if (res != null) stack.push(res);
624 } else {
625 MethodTypeDesc mtd = inst.typeSymbol();
626
627 //bootstrap
628 MethodTypeDesc bsmDesc = bsm.invocationType();
629 MethodRef bsmRef = MethodRef.method(JavaType.type(bsmOwner),
630 bsm.methodName(),
631 JavaType.type(bsmDesc.returnType()),
632 bsmDesc.parameterList().stream().map(JavaType::type).toArray(CodeType[]::new));
633
634 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, inst.name().toString(), mtd, inst.bootstrapArgs());
635 Value methodHandle = op(JavaOp.invoke(MethodRef.method(CallSite.class, "dynamicInvoker", MethodHandle.class),
636 op(JavaOp.invoke(JavaType.type(ConstantDescs.CD_CallSite), bsmRef, bootstrapArgs))));
637
638 //invocation
639 List<Value> operands = new ArrayList<>();
640 for (int c = 0; c < mtd.parameterCount(); c++) {
641 operands.add(stack.pop());
642 }
643 operands.add(methodHandle);
644 MethodRef mDesc = MethodRef.method(JavaType.type(ConstantDescs.CD_MethodHandle),
645 "invokeExact",
646 MethodRef.ofNominalDescriptor(mtd));
647 Op.Result result = op(JavaOp.invoke(mDesc, operands.reversed()));
648 if (!result.type().equals(JavaType.VOID)) {
649 stack.push(result);
650 }
651 }
652 }
653 case NewObjectInstruction inst -> {
654 // Skip over this and the dup to process the invoke special
655 if (i + 2 < elements.size() - 1
656 && elements.get(i + 1) instanceof StackInstruction dup
657 && dup.opcode() == Opcode.DUP) {
658 i++;
659 newStack.push(inst.className().asSymbol());
660 } else {
661 throw new UnsupportedOperationException("New must be followed by dup");
662 }
663 }
664 case NewPrimitiveArrayInstruction inst -> {
665 stack.push(op(JavaOp.newArray(
666 switch (inst.typeKind()) {
667 case BOOLEAN -> JavaType.BOOLEAN_ARRAY;
668 case BYTE -> JavaType.BYTE_ARRAY;
669 case CHAR -> JavaType.CHAR_ARRAY;
670 case DOUBLE -> JavaType.DOUBLE_ARRAY;
671 case FLOAT -> JavaType.FLOAT_ARRAY;
672 case INT -> JavaType.INT_ARRAY;
673 case LONG -> JavaType.LONG_ARRAY;
674 case SHORT -> JavaType.SHORT_ARRAY;
675 default ->
676 throw new UnsupportedOperationException("Unsupported new primitive array type: " + inst.typeKind());
677 },
678 stack.pop())));
679 }
680 case NewReferenceArrayInstruction inst -> {
681 stack.push(op(JavaOp.newArray(
682 JavaType.type(inst.componentType().asSymbol().arrayType()),
683 stack.pop())));
684 }
685 case NewMultiArrayInstruction inst -> {
686 stack.push(op(JavaOp.new_(
687 MethodRef.constructor(
688 JavaType.type(inst.arrayType().asSymbol()),
689 Collections.nCopies(inst.dimensions(), JavaType.INT)),
690 IntStream.range(0, inst.dimensions()).mapToObj(_ -> stack.pop()).toList().reversed())));
691 }
692 case TypeCheckInstruction inst when inst.opcode() == Opcode.CHECKCAST -> {
693 stack.push(op(JavaOp.cast(JavaType.type(inst.type().asSymbol()), stack.pop())));
694 }
695 case TypeCheckInstruction inst -> {
696 stack.push(op(JavaOp.instanceOf(JavaType.type(inst.type().asSymbol()), stack.pop())));
697 }
698 case StackInstruction inst -> {
699 switch (inst.opcode()) {
700 case POP -> {
701 stack.pop();
702 }
703 case POP2 -> {
704 if (isCategory1(stack.pop())) {
705 stack.pop();
706 }
707 }
708 case DUP -> {
709 stack.push(stack.peek());
710 }
711 case DUP_X1 -> {
712 var value1 = stack.pop();
713 var value2 = stack.pop();
714 stack.push(value1);
715 stack.push(value2);
716 stack.push(value1);
717 }
718 case DUP_X2 -> {
719 var value1 = stack.pop();
720 var value2 = stack.pop();
721 if (isCategory1(value2)) {
722 var value3 = stack.pop();
723 stack.push(value1);
724 stack.push(value3);
725 } else {
726 stack.push(value1);
727 }
728 stack.push(value2);
729 stack.push(value1);
730 }
731 case DUP2 -> {
732 var value1 = stack.peek();
733 if (isCategory1(value1)) {
734 stack.pop();
735 var value2 = stack.peek();
736 stack.push(value1);
737 stack.push(value2);
738 }
739 stack.push(value1);
740 }
741 case DUP2_X1 -> {
742 var value1 = stack.pop();
743 var value2 = stack.pop();
744 if (isCategory1(value1)) {
745 var value3 = stack.pop();
746 stack.push(value2);
747 stack.push(value1);
748 stack.push(value3);
749 } else {
750 stack.push(value1);
751 }
752 stack.push(value2);
753 stack.push(value1);
754 }
755 case DUP2_X2 -> {
756 var value1 = stack.pop();
757 var value2 = stack.pop();
758 if (isCategory1(value1)) {
759 var value3 = stack.pop();
760 if (isCategory1(value3)) {
761 var value4 = stack.pop();
762 stack.push(value2);
763 stack.push(value1);
764 stack.push(value4);
765 } else {
766 stack.push(value2);
767 stack.push(value1);
768 }
769 stack.push(value3);
770 } else {
771 if (isCategory1(value2)) {
772 var value3 = stack.pop();
773 stack.push(value1);
774 stack.push(value3);
775 } else {
776 stack.push(value1);
777 }
778 }
779 stack.push(value2);
780 stack.push(value1);
781 }
782 case SWAP -> {
783 var value1 = stack.pop();
784 var value2 = stack.pop();
785 stack.push(value1);
786 stack.push(value2);
787 }
788 default ->
789 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst);
790 }
791 }
792 case MonitorInstruction inst -> {
793 var monitor = stack.pop();
794 switch (inst.opcode()) {
795 case MONITORENTER -> op(JavaOp.monitorEnter(monitor));
796 case MONITOREXIT -> op(JavaOp.monitorExit(monitor));
797 default ->
798 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst);
799 }
800 }
801 case NopInstruction _ -> {}
802 case PseudoInstruction _ -> {}
803 case Instruction inst ->
804 throw new UnsupportedOperationException("Unsupported instruction: " + inst.opcode().name());
805 default ->
806 throw new UnsupportedOperationException("Unsupported code element: " + elements.get(i));
807 }
808 }
809 assert newStack.isEmpty();
810 }
811
812 private Op.Result liftConstantsIntoArray(CodeType arrayType, Object... constants) {
813 Op.Result array = op(JavaOp.newArray(arrayType, liftConstant(constants.length)));
814 for (int i = 0; i < constants.length; i++) {
815 op(JavaOp.arrayStoreOp(array, liftConstant(i), liftConstant(constants[i])));
816 }
817 return array;
818 }
819
820 private Op.Result liftConstant(Object c) {
821 return switch (c) {
822 case null -> op(CoreOp.constant(UnresolvedType.unresolvedRef(), null));
823 case ClassDesc cd -> op(CoreOp.constant(JavaType.J_L_CLASS, JavaType.type(cd)));
824 case Double d -> op(CoreOp.constant(JavaType.DOUBLE, d));
825 case Float f -> op(CoreOp.constant(JavaType.FLOAT, f));
826 case Integer ii -> op(CoreOp.constant(UnresolvedType.unresolvedInt(), ii));
827 case Long l -> op(CoreOp.constant(JavaType.LONG, l));
828 case String s -> op(CoreOp.constant(JavaType.J_L_STRING, s));
829 case DirectMethodHandleDesc dmh -> {
830 Op.Result lookup = op(JavaOp.invoke(LOOKUP));
831 Op.Result owner = liftConstant(dmh.owner());
832 Op.Result name = liftConstant(dmh.methodName());
833 MethodTypeDesc invDesc = dmh.invocationType();
834 yield op(switch (dmh.kind()) {
835 case STATIC, INTERFACE_STATIC ->
836 JavaOp.invoke(FIND_STATIC, lookup, owner, name, liftConstant(invDesc));
837 case VIRTUAL, INTERFACE_VIRTUAL ->
838 JavaOp.invoke(FIND_VIRTUAL, lookup, owner, name, liftConstant(invDesc.dropParameterTypes(0, 1)));
839 case SPECIAL, INTERFACE_SPECIAL ->
840 //CoreOp.invoke(MethodRef.method(e), "findSpecial", owner, name, liftConstant(invDesc.dropParameterTypes(0, 1)), lookup.lookupClass());
841 throw new UnsupportedOperationException(dmh.toString());
842 case CONSTRUCTOR ->
843 JavaOp.invoke(FIND_CONSTRUCTOR, lookup, owner, liftConstant(invDesc.changeReturnType(ConstantDescs.CD_Void)));
844 case GETTER ->
845 JavaOp.invoke(FIND_GETTER, lookup, owner, name, liftConstant(invDesc.returnType()));
846 case STATIC_GETTER ->
847 JavaOp.invoke(FIND_STATIC_GETTER, lookup, owner, name, liftConstant(invDesc.returnType()));
848 case SETTER ->
849 JavaOp.invoke(FIND_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(1)));
850 case STATIC_SETTER ->
851 JavaOp.invoke(FIND_STATIC_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(0)));
852 });
853 }
854 case MethodTypeDesc mt -> op(switch (mt.parameterCount()) {
855 case 0 -> JavaOp.invoke(METHOD_TYPE_0, liftConstant(mt.returnType()));
856 case 1 -> JavaOp.invoke(METHOD_TYPE_1, liftConstant(mt.returnType()), liftConstant(mt.parameterType(0)));
857 default -> JavaOp.invoke(METHOD_TYPE_L, liftConstant(mt.returnType()), liftConstantsIntoArray(CLASS_ARRAY, (Object[])mt.parameterArray()));
858 });
859 case DynamicConstantDesc<?> v when v.bootstrapMethod().owner().equals(ConstantDescs.CD_ConstantBootstraps)
860 && v.bootstrapMethod().methodName().equals("nullConstant")
861 -> {
862 c = null;
863 yield liftConstant(null);
864 }
865 case DynamicConstantDesc<?> dcd when dcd.bootstrapMethod().equals(ConstantDescs.BSM_INVOKE)
866 && dcd.constantType().equals(ConstantDescs.CD_MethodHandle)
867 && !dcd.bootstrapArgsList().isEmpty()
868 && dcd.bootstrapArgsList().getFirst() instanceof DirectMethodHandleDesc finder
869 && finder.owner().equals(ConstantDescs.CD_MethodHandles_Lookup)
870 && Arrays.asList("findSpecial", "findVirtual", "findStatic",
871 "findGetter", "findSetter", "findStaticGetter", "findStaticSetter")
872 .contains(finder.methodName())
873 -> op(CoreOp.constant(MH, dcd));
874 case DynamicConstantDesc<?> dcd -> {
875 DirectMethodHandleDesc bsm = dcd.bootstrapMethod();
876 MethodTypeDesc bsmDesc = bsm.invocationType();
877 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, dcd.constantName(), dcd.constantType(), dcd.bootstrapArgsList());
878 MethodRef bsmRef = MethodRef.method(JavaType.type(bsm.owner()),
879 bsm.methodName(),
880 JavaType.type(bsmDesc.returnType()),
881 bsmDesc.parameterList().stream().map(JavaType::type).toArray(CodeType[]::new));
882 Op.Result call = op(JavaOp.invoke(bsmRef, bootstrapArgs));
883 JavaType target = JavaType.type(dcd.constantType());
884 yield target.equals(call.type()) || dcd.constantType().isPrimitive()
885 ? call : op(JavaOp.cast(target, call));
886 }
887 case Boolean b -> op(CoreOp.constant(JavaType.BOOLEAN, b));
888 case Byte b -> op(CoreOp.constant(JavaType.BYTE, b));
889 case Short s -> op(CoreOp.constant(JavaType.SHORT, s));
890 case Character ch -> op(CoreOp.constant(JavaType.CHAR, ch));
891 default -> throw new UnsupportedOperationException(c.getClass().toString());
892 };
893 }
894
895 private Value[] liftBootstrapArgs(MethodTypeDesc bsmDesc, String name, ConstantDesc desc, List<ConstantDesc> bsmArgs) {
896 Value[] bootstrapArgs = new Value[bsmDesc.parameterCount()];
897 bootstrapArgs[0] = op(JavaOp.invoke(LOOKUP));
898 bootstrapArgs[1] = liftConstant(name);
899 bootstrapArgs[2] = liftConstant(desc);
900 ClassDesc lastArgType = bsmDesc.parameterType(bsmDesc.parameterCount() - 1);
901 if (lastArgType.isArray()) {
902 for (int ai = 0; ai < bootstrapArgs.length - 4; ai++) {
903 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai));
904 }
905 // Vararg tail of the bootstrap method parameters
906 bootstrapArgs[bootstrapArgs.length - 1] =
907 liftConstantsIntoArray(JavaType.type(lastArgType),
908 bsmArgs.subList(bootstrapArgs.length - 4, bsmArgs.size()).toArray());
909 } else {
910 for (int ai = 0; ai < bootstrapArgs.length - 3; ai++) {
911 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai));
912 }
913 }
914 return bootstrapArgs;
915 }
916
917 private void liftSwitch(Label defaultTarget, List<SwitchCase> cases) {
918 Value v = stack.pop();
919 if (!valueType(v).equals(PrimitiveType.INT)) {
920 v = op(JavaOp.conv(PrimitiveType.INT, v));
921 }
922 SwitchCase last = cases.getLast();
923 Block.Builder def = transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(defaultTarget));
924 for (SwitchCase sc : cases) {
925 if (sc == last) {
926 op(CoreOp.conditionalBranch(
927 op(JavaOp.eq(v, liftConstant(sc.caseValue()))),
928 successorWithStack(transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(sc.target()))),
929 successorWithStack(def)));
930 } else {
931 Block.Builder next = entryBlock.block();
932 op(CoreOp.conditionalBranch(
933 op(JavaOp.eq(v, liftConstant(sc.caseValue()))),
934 successorWithStack(transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(sc.target()))),
935 next.reference()));
936 currentBlock = next;
937 }
938 }
939 endOfFlow();
940 }
941
942 private Block.Builder newBlock(List<Block.Parameter> otherBlockParams) {
943 return entryBlock.block(otherBlockParams.stream().map(Block.Parameter::type).toList());
944 }
945
946 private void endOfFlow() {
947 currentBlock = null;
948 // Flow discontinued, stack cleared to be ready for the next label target
949 stack.clear();
950 actualEreStack = List.of();
951 }
952
953 // Create a block that exits regions before it reaches the target
954 private Block.Builder transitionBlockForTarget(List<Op.Result> initialEreStack, int targetBci) {
955 Block.Builder targetBlock = blockMap.get(targetBci);
956 if (targetBlock == null) return null;
957 Block.Builder transitionBlock = newBlock(targetBlock.parameters());
958 exitRegions(initialEreStack, transitionBlock, targetEntryBlock(targetBci), transitionBlock.parameters());
959 return transitionBlock;
960 }
961
962 // Return a block that enters regions needed by the target
963 private Block.Builder targetEntryBlock(int bci) {
964 Block.Builder targetBlock = blockMap.get(bci);
965 List<ExceptionRegion> targetEreStack = exceptionRegionsMap.getOrDefault(bci, List.of());
966 if (targetEreStack.isEmpty()) {
967 enteredRegionStacks.putIfAbsent(targetBlock, List.of());
968 return targetBlock;
969 }
970 Block.Builder enterBlock = labelEntryBlocks.get(bci);
971 if (enterBlock == null) { // Avoid ConcurrentModificationException
972 enterBlock = newBlock(targetBlock.parameters());
973 labelEntryBlocks.put(bci, enterBlock);
974 List<Op.Result> ereStack = new ArrayList<>();
975 Block.Builder currentBlock = enterBlock;
976 for (int i = 0; i < targetEreStack.size(); i++) {
977 boolean last = i == targetEreStack.size() - 1;
978 Block.Builder nextBlock = last ? targetBlock : newBlock(targetBlock.parameters());
979 Block.Reference nextReference = nextBlock.reference(currentBlock.parameters());
980 ExceptionRegion entered = targetEreStack.get(i);
981 CatchEntries catches = catchReferences(ereStack, entered, currentBlock);
982 Op.Result enter = currentBlock.add(JavaOp.exceptionRegionEnter(catches.types(), nextReference,
983 catches.references()));
984 enteredRegionMap.put(enter, entered);
985 ereStack.add(enter);
986 currentBlock = nextBlock;
987 }
988 enteredRegionStacks.putIfAbsent(targetBlock, List.copyOf(ereStack));
989 }
990 return enterBlock;
991 }
992
993 // Emit exits from innermost region to outermost region
994 private void exitRegions(List<Op.Result> initialEreStack, Block.Builder initialBlock, Block.Builder targetBlock,
995 List<? extends Value> values) {
996 if (initialEreStack.isEmpty()) {
997 initialBlock.add(CoreOp.branch(targetBlock.reference(values)));
998 } else {
999 Block.Builder currentBlock = initialBlock;
1000 for (int i = initialEreStack.size() - 1; i >= 0; i--) {
1001 boolean last = i == 0;
1002 Block.Builder nextBlock = last ? targetBlock : entryBlock.block();
1003 Block.Reference nextReference = last ? nextBlock.reference(values) : nextBlock.reference();
1004 currentBlock.add(JavaOp.exceptionRegionExit(initialEreStack.get(i), nextReference));
1005 currentBlock = nextBlock;
1006 }
1007 }
1008 }
1009
1010 // Move from one region stack to another
1011 private List<Op.Result> ereTransit(List<ExceptionRegion> targetEreStack, Block.Builder targetBlock) {
1012 int common = 0;
1013 int limit = Math.min(actualEreStack.size(), targetEreStack.size());
1014 while (common < limit && enteredRegionMap.get(actualEreStack.get(common)) == targetEreStack.get(common)) {
1015 common++;
1016 }
1017 int exits = actualEreStack.size() - common;
1018 int enters = targetEreStack.size() - common;
1019 if (exits == 0 && enters == 0) {
1020 // Join with branch
1021 currentBlock.add(CoreOp.branch(targetBlock.reference()));
1022 enteredRegionStacks.putIfAbsent(targetBlock, actualEreStack);
1023 return actualEreStack;
1024 }
1025 List<Op.Result> ereStack = new ArrayList<>(actualEreStack);
1026 Block.Builder block = currentBlock;
1027 int transitionCount = exits + enters;
1028 for (int t = 0; t < transitionCount; t++) {
1029 boolean last = t == transitionCount - 1;
1030 Block.Builder nextBlock = last ? targetBlock : entryBlock.block();
1031 Block.Reference nextReference = nextBlock.reference();
1032 if (t < exits) {
1033 block.add(JavaOp.exceptionRegionExit(ereStack.removeLast(), nextReference));
1034 } else {
1035 ExceptionRegion entered = targetEreStack.get(common + t - exits);
1036 CatchEntries catches = catchReferences(ereStack, entered, block);
1037 Op.Result enter = block.add(JavaOp.exceptionRegionEnter(catches.types(), nextReference, catches.references()));
1038 enteredRegionMap.put(enter, entered);
1039 ereStack.add(enter);
1040 }
1041 block = nextBlock;
1042 }
1043 enteredRegionStacks.putIfAbsent(targetBlock, ereStack);
1044 return ereStack;
1045 }
1046
1047 // Build catch targets for one enter op
1048 private CatchEntries catchReferences(List<Op.Result> initialEreStack, ExceptionRegion enteredRegion,
1049 Block.Builder b) {
1050 record Group(int firstHandler, int handlerBci, List<CodeType> catchTypes) {}
1051
1052 List<Group> groups = new ArrayList<>();
1053 for (int handler : enteredRegion.handlers()) {
1054 int handlerBci = handlerBcis.get(handler);
1055 CodeType catchType = catchTypes.get(handler);
1056 Group last = groups.isEmpty() ? null : groups.getLast();
1057 if (last != null && last.handlerBci() == handlerBci) {
1058 last.catchTypes().add(catchType);
1059 } else {
1060 groups.add(new Group(handler, handlerBci, new ArrayList<>(List.of(catchType))));
1061 }
1062 }
1063
1064 List<Block.Reference> references = new ArrayList<>();
1065 List<CodeType> types = new ArrayList<>();
1066 List<Op.Result> enteredRegions = List.copyOf(initialEreStack);
1067 for (Group group : groups.reversed()) {
1068 CatchTargetKey key = new CatchTargetKey(group.handlerBci(), enteredRegions,
1069 enteredRegion.handlers().stream().map(h -> Map.entry(handlerBcis.get(h), catchTypes.get(h))).toList());
1070 Block.Builder target = exceptionHandlerBlocks.get(key);
1071 if (target == null) { // Avoid ConcurrentModificationException
1072 target = transitionBlockForTarget(enteredRegions, group.handlerBci());
1073 exceptionHandlerBlocks.put(key, target);
1074 }
1075 Op.Result arg = b.add(constant(target.parameters().getFirst().type(), null));
1076 references.add(target.reference(arg));
1077 types.add(group.catchTypes().size() == 1
1078 ? group.catchTypes().getFirst()
1079 : CoreType.tupleType(group.catchTypes()));
1080 }
1081 return new CatchEntries(types, references);
1082 }
1083
1084 Block.Reference successorWithStack(Block.Builder next) {
1085 return next.reference(stackValues(next));
1086 }
1087
1088 private List<Value> stackValues(Block.Builder limit) {
1089 return stack.stream().limit(limit.parameters().size()).toList();
1090 }
1091
1092 private static CodeType valueType(Value v) {
1093 var t = v.type();
1094 while (t instanceof VarType vt) t = vt.valueType();
1095 return t;
1096 }
1097
1098 private static boolean isCategory1(Value v) {
1099 CodeType t = v.type();
1100 return !t.equals(JavaType.LONG) && !t.equals(JavaType.DOUBLE);
1101 }
1102
1103 private static boolean isUnconditionalBranch(Opcode opcode) {
1104 return switch (opcode) {
1105 case GOTO, ATHROW, GOTO_W, LOOKUPSWITCH, TABLESWITCH -> true;
1106 default -> opcode.kind() == Opcode.Kind.RETURN;
1107 };
1108 }
1109 }