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 LCMP = MethodRef.method(JavaType.J_L_LONG, "compare", JavaType.INT, JavaType.LONG, JavaType.LONG);
79 private static final MethodRef FCMP = MethodRef.method(JavaType.J_L_FLOAT, "compare", JavaType.INT, JavaType.FLOAT, JavaType.FLOAT);
80 private static final MethodRef DCMP = MethodRef.method(JavaType.J_L_DOUBLE, "compare", JavaType.INT, JavaType.DOUBLE, JavaType.DOUBLE);
81 private static final MethodRef LOOKUP = MethodRef.method(JavaType.type(ConstantDescs.CD_MethodHandles), "lookup", MHS_LOOKUP);
82 private static final MethodRef FIND_STATIC = MethodRef.method(MHS_LOOKUP, "findStatic", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, MT);
83 private static final MethodRef FIND_VIRTUAL = MethodRef.method(MHS_LOOKUP, "findVirtual", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, MT);
84 private static final MethodRef FIND_CONSTRUCTOR = MethodRef.method(MHS_LOOKUP, "findConstructor", MH, JavaType.J_L_CLASS, MT);
85 private static final MethodRef FIND_GETTER = MethodRef.method(MHS_LOOKUP, "findGetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
86 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);
87 private static final MethodRef FIND_SETTER = MethodRef.method(MHS_LOOKUP, "findSetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
88 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);
89 private static final MethodRef METHOD_TYPE_0 = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS);
90 private static final MethodRef METHOD_TYPE_1 = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS, JavaType.J_L_CLASS);
91 private static final MethodRef METHOD_TYPE_L = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS, CLASS_ARRAY);
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.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.handlerBcis = new ArrayList<>();
139 this.catchTypes = new ArrayList<>();
140 record RegionKey(int start, int end) {}
141 Map<RegionKey, List<Integer>> grouped = new LinkedHashMap<>();
142 for (ExceptionCatch ec : codeModel.exceptionHandlers()) {
143 int handler = handlerBcis.size();
144 handlerBcis.add(label2Bci.applyAsInt(ec.handler()));
145 catchTypes.add(ec.catchType().map(ct -> JavaType.type(ct.asSymbol())).orElse(JavaType.VOID));
146 grouped.computeIfAbsent(new RegionKey(label2Bci.applyAsInt(ec.tryStart()), label2Bci.applyAsInt(ec.tryEnd())), _ -> new ArrayList<>())
147 .add(handler);
148 }
149 List<ExceptionRegion> regions = new ArrayList<>();
150 for (var c : grouped.entrySet()) {
151 regions.add(new ExceptionRegion(regions.size(), c.getKey().start(), c.getKey().end(), c.getValue()));
152 }
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 = 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 }
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 handler, List<Op.Result> enteredRegions) {}
185
186 // Handlers block references with related catch types
187 record CatchEntries(List<CodeType> types, List<Block.Reference> references) {}
188
189 private List<CodeType> toBlockParams(List<StackMapFrameInfo.VerificationTypeInfo> vtis) {
190 ArrayList<CodeType> params = new ArrayList<>(vtis.size());
191 for (int i = vtis.size() - 1; i >= 0; i--) {
192 var vti = vtis.get(i);
193 switch (vti) {
194 case INTEGER -> params.add(UnresolvedType.unresolvedInt());
195 case FLOAT -> params.add(JavaType.FLOAT);
196 case DOUBLE -> params.add(JavaType.DOUBLE);
197 case LONG -> params.add(JavaType.LONG);
198 case NULL -> params.add(UnresolvedType.unresolvedRef());
199 case UNINITIALIZED_THIS ->
200 params.add(JavaType.type(classModel.thisClass().asSymbol()));
201 case StackMapFrameInfo.ObjectVerificationTypeInfo ovti ->
202 params.add(JavaType.type(ovti.classSymbol()));
203
204 // Unitialized entry (a new object before its constructor is called)
205 // must be skipped from block parameters because they do not exist in code reflection model
206 case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> {}
207 default ->
208 throw new IllegalArgumentException("Unexpected VTI: " + vti);
209 }
210 }
211 return params;
212 }
213
214 private Op.Result op(Op op) {
215 return currentBlock.add(op);
216 }
217
218 // Lift to core dialect
219 public static CoreOp.FuncOp lift(byte[] classdata, String methodName) {
220 return lift(classdata, methodName, null);
221 }
222
223 public static CoreOp.FuncOp lift(byte[] classdata, String methodName, MethodTypeDesc methodType) {
224 return lift(ClassFile.of(
225 ClassFile.DebugElementsOption.DROP_DEBUG,
226 ClassFile.LineNumbersOption.DROP_LINE_NUMBERS).parse(classdata).methods().stream()
227 .filter(mm -> mm.methodName().equalsString(methodName) && (methodType == null || mm.methodTypeSymbol().equals(methodType)))
228 .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown method: " + methodName)));
229 }
230
231 public static CoreOp.FuncOp lift(MethodModel methodModel) {
232 ClassModel classModel = methodModel.parent().orElseThrow();
233 MethodTypeDesc mDesc = methodModel.methodTypeSymbol();
234 if (!methodModel.flags().has(AccessFlag.STATIC)) {
235 mDesc = mDesc.insertParameterTypes(0, classModel.thisClass().asSymbol());
236 }
237 return NormalizeExceptionRegionsTransformer.transform(
238 NormalizeBlocksTransformer.transform(
239 UnresolvedTypesTransformer.transform(
240 SlotToVarTransformer.transform(
241 CoreOp.func(methodModel.methodName().stringValue(),
242 MethodRef.ofNominalDescriptor(mDesc)).body(entryBlock ->
243 new BytecodeLift(entryBlock,
244 classModel,
245 methodModel.code().orElseThrow()).liftBody())))));
246 }
247
248 private void liftBody() {
249 // store entry block
250 int slot = 0;
251 for (var ep : initialValues) {
252 op(SlotOp.store(slot, ep));
253 slot += ep.type().equals(JavaType.LONG) || ep.type().equals(JavaType.DOUBLE) ? 2 : 1;
254 }
255
256 for (int i = 0; i < elements.size(); i++) {
257 switch (elements.get(i)) {
258 case ExceptionCatch _ -> {
259 // Exception blocks are inserted by label target (below)
260 }
261 case LabelTarget lt -> {
262 int bci = label2Bci.applyAsInt(lt.label());
263 List<ExceptionRegion> newEreStack = exceptionRegionsMap.get(bci);
264 if (newEreStack != null) {
265 Block.Builder target = blockMap.get(bci);
266 if (target != null) {
267 if (currentBlock != null) {
268 // Transition to a branch target or a handler
269 exitRegions(actualEreStack, currentBlock, targetEntryBlock(bci), stackValues(target));
270 }
271 actualEreStack = enteredRegionStacks.getOrDefault(target, List.of());
272 currentBlock = target;
273 stack.clear();
274 stack.addAll(target.parameters());
275 } else if (currentBlock != null && !actualEreStack.stream().map(enteredRegionMap::get).toList().equals(newEreStack)) {
276 // Transition to a block with a different ERE stack
277 Block.Builder next = entryBlock.block();
278 actualEreStack = ereTransit(newEreStack, next);
279 currentBlock = next;
280 }
281 }
282 }
283 case BranchInstruction inst when isUnconditionalBranch(inst.opcode()) -> {
284 int targetBci = label2Bci.applyAsInt(inst.target());
285 Block.Builder target = blockMap.get(targetBci);
286 exitRegions(actualEreStack, currentBlock, targetEntryBlock(targetBci), stackValues(target));
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 = transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(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 = transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(defaultTarget));
852 for (SwitchCase sc : cases) {
853 if (sc == last) {
854 op(CoreOp.conditionalBranch(
855 op(JavaOp.eq(v, liftConstant(sc.caseValue()))),
856 successorWithStack(transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(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(transitionBlockForTarget(actualEreStack, label2Bci.applyAsInt(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 // Create a block that exits regions before it reaches the target
882 private Block.Builder transitionBlockForTarget(List<Op.Result> initialEreStack, int targetBci) {
883 Block.Builder targetBlock = blockMap.get(targetBci);
884 if (targetBlock == null) return null;
885 Block.Builder transitionBlock = newBlock(targetBlock.parameters());
886 exitRegions(initialEreStack, transitionBlock, targetEntryBlock(targetBci), transitionBlock.parameters());
887 return transitionBlock;
888 }
889
890 // Return a block that enters regions needed by the target
891 private Block.Builder targetEntryBlock(int bci) {
892 Block.Builder targetBlock = blockMap.get(bci);
893 List<ExceptionRegion> targetEreStack = exceptionRegionsMap.getOrDefault(bci, List.of());
894 if (targetEreStack.isEmpty()) {
895 enteredRegionStacks.putIfAbsent(targetBlock, List.of());
896 return targetBlock;
897 }
898 Block.Builder enterBlock = labelEntryBlocks.get(bci);
899 if (enterBlock == null) { // Avoid ConcurrentModificationException
900 enterBlock = newBlock(targetBlock.parameters());
901 labelEntryBlocks.put(bci, enterBlock);
902 List<Op.Result> ereStack = new ArrayList<>();
903 Block.Builder currentBlock = enterBlock;
904 for (int i = 0; i < targetEreStack.size(); i++) {
905 boolean last = i == targetEreStack.size() - 1;
906 Block.Builder nextBlock = last ? targetBlock : newBlock(targetBlock.parameters());
907 Block.Reference nextReference = nextBlock.reference(currentBlock.parameters());
908 ExceptionRegion entered = targetEreStack.get(i);
909 CatchEntries catches = catchReferences(ereStack, entered, currentBlock);
910 Op.Result enter = currentBlock.add(JavaOp.exceptionRegionEnter(catches.types(), nextReference,
911 catches.references()));
912 enteredRegionMap.put(enter, entered);
913 ereStack.add(enter);
914 currentBlock = nextBlock;
915 }
916 enteredRegionStacks.putIfAbsent(targetBlock, List.copyOf(ereStack));
917 }
918 return enterBlock;
919 }
920
921 // Emit exits from innermost region to outermost region
922 private void exitRegions(List<Op.Result> initialEreStack, Block.Builder initialBlock, Block.Builder targetBlock,
923 List<? extends Value> values) {
924 if (initialEreStack.isEmpty()) {
925 initialBlock.add(CoreOp.branch(targetBlock.reference(values)));
926 } else {
927 Block.Builder currentBlock = initialBlock;
928 for (int i = initialEreStack.size() - 1; i >= 0; i--) {
929 boolean last = i == 0;
930 Block.Builder nextBlock = last ? targetBlock : entryBlock.block();
931 Block.Reference nextReference = last ? nextBlock.reference(values) : nextBlock.reference();
932 currentBlock.add(JavaOp.exceptionRegionExit(initialEreStack.get(i), nextReference));
933 currentBlock = nextBlock;
934 }
935 }
936 }
937
938 // Move from one region stack to another
939 private List<Op.Result> ereTransit(List<ExceptionRegion> targetEreStack, Block.Builder targetBlock) {
940 int common = 0;
941 int limit = Math.min(actualEreStack.size(), targetEreStack.size());
942 while (common < limit && enteredRegionMap.get(actualEreStack.get(common)) == targetEreStack.get(common)) {
943 common++;
944 }
945 int exits = actualEreStack.size() - common;
946 int enters = targetEreStack.size() - common;
947 if (exits == 0 && enters == 0) {
948 // Join with branch
949 currentBlock.add(CoreOp.branch(targetBlock.reference()));
950 enteredRegionStacks.putIfAbsent(targetBlock, actualEreStack);
951 return actualEreStack;
952 }
953 List<Op.Result> ereStack = new ArrayList<>(actualEreStack);
954 Block.Builder block = currentBlock;
955 int transitionCount = exits + enters;
956 for (int t = 0; t < transitionCount; t++) {
957 boolean last = t == transitionCount - 1;
958 Block.Builder nextBlock = last ? targetBlock : entryBlock.block();
959 Block.Reference nextReference = nextBlock.reference();
960 if (t < exits) {
961 block.add(JavaOp.exceptionRegionExit(ereStack.removeLast(), nextReference));
962 } else {
963 ExceptionRegion entered = targetEreStack.get(common + t - exits);
964 CatchEntries catches = catchReferences(ereStack, entered, block);
965 Op.Result enter = block.add(JavaOp.exceptionRegionEnter(catches.types(), nextReference, catches.references()));
966 enteredRegionMap.put(enter, entered);
967 ereStack.add(enter);
968 }
969 block = nextBlock;
970 }
971 enteredRegionStacks.putIfAbsent(targetBlock, ereStack);
972 return ereStack;
973 }
974
975 // Build catch targets for one enter op
976 private CatchEntries catchReferences(List<Op.Result> initialEreStack, ExceptionRegion enteredRegion,
977 Block.Builder b) {
978 record Group(int firstHandler, int handlerBci, List<CodeType> catchTypes) {}
979
980 List<Group> groups = new ArrayList<>();
981 for (int handler : enteredRegion.handlers()) {
982 int handlerBci = handlerBcis.get(handler);
983 CodeType catchType = catchTypes.get(handler);
984 Group last = groups.isEmpty() ? null : groups.getLast();
985 if (last != null && last.handlerBci() == handlerBci) {
986 last.catchTypes().add(catchType);
987 } else {
988 groups.add(new Group(handler, handlerBci, new ArrayList<>(List.of(catchType))));
989 }
990 }
991
992 List<Block.Reference> references = new ArrayList<>();
993 List<CodeType> types = new ArrayList<>();
994 List<Op.Result> enteredRegions = List.copyOf(initialEreStack);
995 for (Group group : groups.reversed()) {
996 CatchTargetKey key = new CatchTargetKey(group.firstHandler(), enteredRegions);
997 Block.Builder target = exceptionHandlerBlocks.get(key);
998 if (target == null) { // Avoid ConcurrentModificationException
999 target = transitionBlockForTarget(enteredRegions, group.handlerBci());
1000 exceptionHandlerBlocks.put(key, target);
1001 }
1002 Op.Result arg = b.add(constant(target.parameters().getFirst().type(), null));
1003 references.add(target.reference(arg));
1004 types.add(group.catchTypes().size() == 1
1005 ? group.catchTypes().getFirst()
1006 : CoreType.tupleType(group.catchTypes()));
1007 }
1008 return new CatchEntries(types, references);
1009 }
1010
1011 Block.Reference successorWithStack(Block.Builder next) {
1012 return next.reference(stackValues(next));
1013 }
1014
1015 private List<Value> stackValues(Block.Builder limit) {
1016 return stack.stream().limit(limit.parameters().size()).toList();
1017 }
1018
1019 private static CodeType valueType(Value v) {
1020 var t = v.type();
1021 while (t instanceof VarType vt) t = vt.valueType();
1022 return t;
1023 }
1024
1025 private static boolean isCategory1(Value v) {
1026 CodeType t = v.type();
1027 return !t.equals(JavaType.LONG) && !t.equals(JavaType.DOUBLE);
1028 }
1029
1030 private static boolean isUnconditionalBranch(Opcode opcode) {
1031 return switch (opcode) {
1032 case GOTO, ATHROW, GOTO_W, LOOKUPSWITCH, TABLESWITCH -> true;
1033 default -> opcode.kind() == Opcode.Kind.RETURN;
1034 };
1035 }
1036 }