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