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