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