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