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 ConstructorRef.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 FLAG_QUOTABLE is set, the lambda is quotable 476 if (bsm.methodName().equals("altMetafactory")) { 477 assert inst.bootstrapArgs().size() > 3; 478 assert inst.bootstrapArgs().get(3) instanceof Integer; 479 480 if (inst.bootstrapArgs().get(3) instanceof Integer flags 481 && (flags & LambdaMetafactory.FLAG_QUOTABLE) != 0) { 482 lambda = lambda.quotable(); 483 } 484 } 485 486 if (dmhd.methodName().startsWith("lambda$") && dmhd.owner().equals(classModel.thisClass().asSymbol())) { 487 // inline lambda impl method 488 MethodModel implMethod = classModel.methods().stream().filter(m -> m.methodName().equalsString(dmhd.methodName())).findFirst().orElseThrow(); 489 stack.push(op(lambda.body(eb -> new BytecodeLift(eb, 490 classModel, 491 implMethod.code().orElseThrow(), 492 capturedValues).liftBody()))); 493 } else { 494 // lambda call to a MH 495 stack.push(op(lambda.body(eb -> { 496 Op.Result ret = eb.op(JavaOp.invoke( 497 MethodRef.method(JavaType.type(dmhd.owner()), 498 dmhd.methodName(), 499 lambdaFunc.returnType(), 500 lambdaFunc.parameterTypes()), 501 Stream.concat(Arrays.stream(capturedValues), eb.parameters().stream()).toArray(Value[]::new))); 502 eb.op(ret.type().equals(JavaType.VOID) ? CoreOp.return_() : CoreOp.return_(ret)); 503 }))); 504 } 505 } else if (bsmOwner.equals(CD_StringConcatFactory)) { 506 int argsCount = inst.typeSymbol().parameterCount(); 507 Deque<Value> args = new ArrayDeque<>(argsCount); 508 for (int ai = 0; ai < argsCount; ai++) { 509 args.push(stack.pop()); 510 } 511 Value res = null; 512 if (bsm.methodName().equals("makeConcat")) { 513 for (Value argVal : args) { 514 res = res == null ? argVal : op(JavaOp.concat(res, argVal)); 515 } 516 } else { 517 assert bsm.methodName().equals("makeConcatWithConstants"); 518 var bsmArgs = inst.bootstrapArgs(); 519 String recipe = (String)(bsmArgs.getFirst()); 520 int bsmArg = 1; 521 for (int ri = 0; ri < recipe.length(); ri++) { 522 Value argVal = switch (recipe.charAt(ri)) { 523 case '\u0001' -> args.pop(); 524 case '\u0002' -> liftConstant(bsmArgs.get(bsmArg++)); 525 default -> { 526 char c; 527 int start = ri; 528 while (ri < recipe.length() && (c = recipe.charAt(ri)) != '\u0001' && c != '\u0002') ri++; 529 yield liftConstant(recipe.substring(start, ri--)); 530 } 531 }; 532 res = res == null ? argVal : op(JavaOp.concat(res, argVal)); 533 } 534 } 535 if (res != null) stack.push(res); 536 } else { 537 MethodTypeDesc mtd = inst.typeSymbol(); 538 539 //bootstrap 540 MethodTypeDesc bsmDesc = bsm.invocationType(); 541 MethodRef bsmRef = MethodRef.method(JavaType.type(bsmOwner), 542 bsm.methodName(), 543 JavaType.type(bsmDesc.returnType()), 544 bsmDesc.parameterList().stream().map(JavaType::type).toArray(TypeElement[]::new)); 545 546 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, inst.name().toString(), mtd, inst.bootstrapArgs()); 547 Value methodHandle = op(JavaOp.invoke(MethodRef.method(CallSite.class, "dynamicInvoker", MethodHandle.class), 548 op(JavaOp.invoke(JavaType.type(ConstantDescs.CD_CallSite), bsmRef, bootstrapArgs)))); 549 550 //invocation 551 List<Value> operands = new ArrayList<>(); 552 for (int c = 0; c < mtd.parameterCount(); c++) { 553 operands.add(stack.pop()); 554 } 555 operands.add(methodHandle); 556 MethodRef mDesc = MethodRef.method(JavaType.type(ConstantDescs.CD_MethodHandle), 557 "invokeExact", 558 MethodRef.ofNominalDescriptor(mtd)); 559 Op.Result result = op(JavaOp.invoke(mDesc, operands.reversed())); 560 if (!result.type().equals(JavaType.VOID)) { 561 stack.push(result); 562 } 563 } 564 } 565 case NewObjectInstruction inst -> { 566 // Skip over this and the dup to process the invoke special 567 if (i + 2 < elements.size() - 1 568 && elements.get(i + 1) instanceof StackInstruction dup 569 && dup.opcode() == Opcode.DUP) { 570 i++; 571 newStack.push(inst.className().asSymbol()); 572 } else { 573 throw new UnsupportedOperationException("New must be followed by dup"); 574 } 575 } 576 case NewPrimitiveArrayInstruction inst -> { 577 stack.push(op(JavaOp.newArray( 578 switch (inst.typeKind()) { 579 case BOOLEAN -> JavaType.BOOLEAN_ARRAY; 580 case BYTE -> JavaType.BYTE_ARRAY; 581 case CHAR -> JavaType.CHAR_ARRAY; 582 case DOUBLE -> JavaType.DOUBLE_ARRAY; 583 case FLOAT -> JavaType.FLOAT_ARRAY; 584 case INT -> JavaType.INT_ARRAY; 585 case LONG -> JavaType.LONG_ARRAY; 586 case SHORT -> JavaType.SHORT_ARRAY; 587 default -> 588 throw new UnsupportedOperationException("Unsupported new primitive array type: " + inst.typeKind()); 589 }, 590 stack.pop()))); 591 } 592 case NewReferenceArrayInstruction inst -> { 593 stack.push(op(JavaOp.newArray( 594 JavaType.type(inst.componentType().asSymbol().arrayType()), 595 stack.pop()))); 596 } 597 case NewMultiArrayInstruction inst -> { 598 stack.push(op(JavaOp.new_( 599 ConstructorRef.constructor( 600 JavaType.type(inst.arrayType().asSymbol()), 601 Collections.nCopies(inst.dimensions(), JavaType.INT)), 602 IntStream.range(0, inst.dimensions()).mapToObj(_ -> stack.pop()).toList().reversed()))); 603 } 604 case TypeCheckInstruction inst when inst.opcode() == Opcode.CHECKCAST -> { 605 stack.push(op(JavaOp.cast(JavaType.type(inst.type().asSymbol()), stack.pop()))); 606 } 607 case TypeCheckInstruction inst -> { 608 stack.push(op(JavaOp.instanceOf(JavaType.type(inst.type().asSymbol()), stack.pop()))); 609 } 610 case StackInstruction inst -> { 611 switch (inst.opcode()) { 612 case POP -> { 613 stack.pop(); 614 } 615 case POP2 -> { 616 if (isCategory1(stack.pop())) { 617 stack.pop(); 618 } 619 } 620 case DUP -> { 621 stack.push(stack.peek()); 622 } 623 case DUP_X1 -> { 624 var value1 = stack.pop(); 625 var value2 = stack.pop(); 626 stack.push(value1); 627 stack.push(value2); 628 stack.push(value1); 629 } 630 case DUP_X2 -> { 631 var value1 = stack.pop(); 632 var value2 = stack.pop(); 633 if (isCategory1(value2)) { 634 var value3 = stack.pop(); 635 stack.push(value1); 636 stack.push(value3); 637 } else { 638 stack.push(value1); 639 } 640 stack.push(value2); 641 stack.push(value1); 642 } 643 case DUP2 -> { 644 var value1 = stack.peek(); 645 if (isCategory1(value1)) { 646 stack.pop(); 647 var value2 = stack.peek(); 648 stack.push(value1); 649 stack.push(value2); 650 } 651 stack.push(value1); 652 } 653 case DUP2_X1 -> { 654 var value1 = stack.pop(); 655 var value2 = stack.pop(); 656 if (isCategory1(value1)) { 657 var value3 = stack.pop(); 658 stack.push(value2); 659 stack.push(value1); 660 stack.push(value3); 661 } else { 662 stack.push(value1); 663 } 664 stack.push(value2); 665 stack.push(value1); 666 } 667 case DUP2_X2 -> { 668 var value1 = stack.pop(); 669 var value2 = stack.pop(); 670 if (isCategory1(value1)) { 671 var value3 = stack.pop(); 672 if (isCategory1(value3)) { 673 var value4 = stack.pop(); 674 stack.push(value2); 675 stack.push(value1); 676 stack.push(value4); 677 } else { 678 stack.push(value2); 679 stack.push(value1); 680 } 681 stack.push(value3); 682 } else { 683 if (isCategory1(value2)) { 684 var value3 = stack.pop(); 685 stack.push(value1); 686 stack.push(value3); 687 } else { 688 stack.push(value1); 689 } 690 } 691 stack.push(value2); 692 stack.push(value1); 693 } 694 case SWAP -> { 695 var value1 = stack.pop(); 696 var value2 = stack.pop(); 697 stack.push(value1); 698 stack.push(value2); 699 } 700 default -> 701 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst); 702 } 703 } 704 case MonitorInstruction inst -> { 705 var monitor = stack.pop(); 706 switch (inst.opcode()) { 707 case MONITORENTER -> op(JavaOp.monitorEnter(monitor)); 708 case MONITOREXIT -> op(JavaOp.monitorExit(monitor)); 709 default -> 710 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst); 711 } 712 } 713 case NopInstruction _ -> {} 714 case PseudoInstruction _ -> {} 715 case Instruction inst -> 716 throw new UnsupportedOperationException("Unsupported instruction: " + inst.opcode().name()); 717 default -> 718 throw new UnsupportedOperationException("Unsupported code element: " + elements.get(i)); 719 } 720 } 721 assert newStack.isEmpty(); 722 } 723 724 private Op.Result liftConstantsIntoArray(TypeElement arrayType, Object... constants) { 725 Op.Result array = op(JavaOp.newArray(arrayType, liftConstant(constants.length))); 726 for (int i = 0; i < constants.length; i++) { 727 op(JavaOp.arrayStoreOp(array, liftConstant(i), liftConstant(constants[i]))); 728 } 729 return array; 730 } 731 732 private Op.Result liftDefaultValue(ClassDesc type) { 733 return liftConstant(switch (TypeKind.from(type)) { 734 case BOOLEAN -> false; 735 case BYTE -> (byte)0; 736 case CHAR -> (char)0; 737 case DOUBLE -> 0d; 738 case FLOAT -> 0f; 739 case INT -> 0; 740 case LONG -> 0l; 741 case REFERENCE -> null; 742 case SHORT -> (short)0; 743 default -> throw new IllegalStateException("Invalid type " + type.displayName()); 744 }); 745 } 746 747 private Op.Result liftConstant(Object c) { 748 return switch (c) { 749 case null -> op(CoreOp.constant(UnresolvedType.unresolvedRef(), null)); 750 case ClassDesc cd -> op(CoreOp.constant(JavaType.J_L_CLASS, JavaType.type(cd))); 751 case Double d -> op(CoreOp.constant(JavaType.DOUBLE, d)); 752 case Float f -> op(CoreOp.constant(JavaType.FLOAT, f)); 753 case Integer ii -> op(CoreOp.constant(UnresolvedType.unresolvedInt(), ii)); 754 case Long l -> op(CoreOp.constant(JavaType.LONG, l)); 755 case String s -> op(CoreOp.constant(JavaType.J_L_STRING, s)); 756 case DirectMethodHandleDesc dmh -> { 757 Op.Result lookup = op(JavaOp.invoke(LOOKUP)); 758 Op.Result owner = liftConstant(dmh.owner()); 759 Op.Result name = liftConstant(dmh.methodName()); 760 MethodTypeDesc invDesc = dmh.invocationType(); 761 yield op(switch (dmh.kind()) { 762 case STATIC, INTERFACE_STATIC -> 763 JavaOp.invoke(FIND_STATIC, lookup, owner, name, liftConstant(invDesc)); 764 case VIRTUAL, INTERFACE_VIRTUAL -> 765 JavaOp.invoke(FIND_VIRTUAL, lookup, owner, name, liftConstant(invDesc.dropParameterTypes(0, 1))); 766 case SPECIAL, INTERFACE_SPECIAL -> 767 //CoreOp.invoke(MethodRef.method(e), "findSpecial", owner, name, liftConstant(invDesc.dropParameterTypes(0, 1)), lookup.lookupClass()); 768 throw new UnsupportedOperationException(dmh.toString()); 769 case CONSTRUCTOR -> 770 JavaOp.invoke(FIND_CONSTRUCTOR, lookup, owner, liftConstant(invDesc.changeReturnType(ConstantDescs.CD_Void))); 771 case GETTER -> 772 JavaOp.invoke(FIND_GETTER, lookup, owner, name, liftConstant(invDesc.returnType())); 773 case STATIC_GETTER -> 774 JavaOp.invoke(FIND_STATIC_GETTER, lookup, owner, name, liftConstant(invDesc.returnType())); 775 case SETTER -> 776 JavaOp.invoke(FIND_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(1))); 777 case STATIC_SETTER -> 778 JavaOp.invoke(FIND_STATIC_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(0))); 779 }); 780 } 781 case MethodTypeDesc mt -> op(switch (mt.parameterCount()) { 782 case 0 -> JavaOp.invoke(METHOD_TYPE_0, liftConstant(mt.returnType())); 783 case 1 -> JavaOp.invoke(METHOD_TYPE_1, liftConstant(mt.returnType()), liftConstant(mt.parameterType(0))); 784 default -> JavaOp.invoke(METHOD_TYPE_L, liftConstant(mt.returnType()), liftConstantsIntoArray(CLASS_ARRAY, (Object[])mt.parameterArray())); 785 }); 786 case DynamicConstantDesc<?> v when v.bootstrapMethod().owner().equals(ConstantDescs.CD_ConstantBootstraps) 787 && v.bootstrapMethod().methodName().equals("nullConstant") 788 -> { 789 c = null; 790 yield liftConstant(null); 791 } 792 case DynamicConstantDesc<?> dcd -> { 793 DirectMethodHandleDesc bsm = dcd.bootstrapMethod(); 794 MethodTypeDesc bsmDesc = bsm.invocationType(); 795 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, dcd.constantName(), dcd.constantType(), dcd.bootstrapArgsList()); 796 MethodRef bsmRef = MethodRef.method(JavaType.type(bsm.owner()), 797 bsm.methodName(), 798 JavaType.type(bsmDesc.returnType()), 799 bsmDesc.parameterList().stream().map(JavaType::type).toArray(TypeElement[]::new)); 800 yield op(JavaOp.invoke(bsmRef, bootstrapArgs)); 801 } 802 case Boolean b -> op(CoreOp.constant(JavaType.BOOLEAN, b)); 803 case Byte b -> op(CoreOp.constant(JavaType.BYTE, b)); 804 case Short s -> op(CoreOp.constant(JavaType.SHORT, s)); 805 case Character ch -> op(CoreOp.constant(JavaType.CHAR, ch)); 806 default -> throw new UnsupportedOperationException(c.getClass().toString()); 807 }; 808 } 809 810 private Value[] liftBootstrapArgs(MethodTypeDesc bsmDesc, String name, ConstantDesc desc, List<ConstantDesc> bsmArgs) { 811 Value[] bootstrapArgs = new Value[bsmDesc.parameterCount()]; 812 bootstrapArgs[0] = op(JavaOp.invoke(LOOKUP)); 813 bootstrapArgs[1] = liftConstant(name); 814 bootstrapArgs[2] = liftConstant(desc); 815 ClassDesc lastArgType = bsmDesc.parameterType(bsmDesc.parameterCount() - 1); 816 if (lastArgType.isArray()) { 817 for (int ai = 0; ai < bootstrapArgs.length - 4; ai++) { 818 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai)); 819 } 820 // Vararg tail of the bootstrap method parameters 821 bootstrapArgs[bootstrapArgs.length - 1] = 822 liftConstantsIntoArray(JavaType.type(lastArgType), 823 bsmArgs.subList(bootstrapArgs.length - 4, bsmArgs.size()).toArray()); 824 } else { 825 for (int ai = 0; ai < bootstrapArgs.length - 3; ai++) { 826 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai)); 827 } 828 } 829 return bootstrapArgs; 830 } 831 832 private void liftSwitch(Label defaultTarget, List<SwitchCase> cases) { 833 Value v = stack.pop(); 834 if (!valueType(v).equals(PrimitiveType.INT)) { 835 v = op(JavaOp.conv(PrimitiveType.INT, v)); 836 } 837 SwitchCase last = cases.getLast(); 838 Block.Builder def = targetBlockForBranch(defaultTarget); 839 for (SwitchCase sc : cases) { 840 if (sc == last) { 841 op(CoreOp.conditionalBranch( 842 op(JavaOp.eq(v, liftConstant(sc.caseValue()))), 843 successorWithStack(targetBlockForBranch(sc.target())), 844 successorWithStack(def))); 845 } else { 846 Block.Builder next = entryBlock.block(); 847 op(CoreOp.conditionalBranch( 848 op(JavaOp.eq(v, liftConstant(sc.caseValue()))), 849 successorWithStack(targetBlockForBranch(sc.target())), 850 next.successor())); 851 currentBlock = next; 852 } 853 } 854 endOfFlow(); 855 } 856 857 private Block.Builder newBlock(List<Block.Parameter> otherBlockParams) { 858 return entryBlock.block(otherBlockParams.stream().map(Block.Parameter::type).toList()); 859 } 860 861 private void endOfFlow() { 862 currentBlock = null; 863 // Flow discontinued, stack cleared to be ready for the next label target 864 stack.clear(); 865 } 866 867 private Block.Builder targetBlockForExceptionHandler(BitSet initialEreStack, int exceptionHandlerIndex) { 868 Block.Builder target = exceptionHandlerBlocks.get(exceptionHandlerIndex); 869 if (target == null) { // Avoid ConcurrentModificationException 870 Label ehLabel = exceptionHandlers.get(exceptionHandlerIndex); 871 target = transitionBlockForTarget(initialEreStack, exceptionHandlersMap.get(ehLabel), blockMap.get(ehLabel), exceptionHandlerIndex); 872 exceptionHandlerBlocks.put(exceptionHandlerIndex, target); 873 } 874 return target; 875 } 876 877 private Block.Builder targetBlockForBranch(Label targetLabel) { 878 return transitionBlockForTarget(actualEreStack, exceptionHandlersMap.get(targetLabel), blockMap.get(targetLabel), -1); 879 } 880 881 private Block.Builder transitionBlockForTarget(BitSet initialEreStack, BitSet targetEreStack, Block.Builder targetBlock, int targetExceptionHandlerIndex) { 882 if (targetBlock == null) return null; 883 Block.Builder transitionBlock = newBlock(targetBlock.parameters()); 884 ereTransit(initialEreStack, targetEreStack, transitionBlock, targetBlock, transitionBlock.parameters(), targetExceptionHandlerIndex); 885 return transitionBlock; 886 } 887 888 record EreT(boolean enter, int ehi) {} 889 890 private void ereTransit(BitSet initialEreStack, BitSet targetEreStack, Block.Builder initialBlock, Block.Builder targetBlock, List<? extends Value> values, int targetExceptionHandlerIndex) { 891 List<EreT> transits = new ArrayList<>(); 892 BitSet ereStack = (BitSet)initialEreStack.clone(); 893 ereStack.andNot(targetEreStack); 894 // Split region exits by handler stack 895 for (int ehi = ereStack.previousSetBit(Integer.MAX_VALUE); ehi >= 0; ehi = ereStack.previousSetBit(ehi - 1)) { 896 transits.add(new EreT(false, ehi)); 897 } 898 ereStack = (BitSet)targetEreStack.clone(); 899 ereStack.andNot(initialEreStack); 900 // Split region enters by handler stack 901 for (int ehi = ereStack.nextSetBit(0); ehi >= 0; ehi = ereStack.nextSetBit(ehi + 1)) { 902 transits.add(new EreT(true, ehi)); 903 } 904 905 if (transits.isEmpty()) { 906 // Join with branch 907 initialBlock.op(CoreOp.branch(targetBlock.successor(values))); 908 } else { 909 // Insert ERE transitions 910 Block.Builder currentBlock = initialBlock; 911 ereStack = (BitSet)initialEreStack.clone(); 912 for (int i = 0; i < transits.size() - 1; i++) { 913 EreT t = transits.get(i); 914 Block.Builder next = entryBlock.block(); 915 ereTransit(initialBlock, currentBlock, t.enter(), next, List.of(), t.ehi(), targetExceptionHandlerIndex, ereStack); 916 currentBlock = next; 917 ereStack.set(t.ehi(), t.enter()); 918 } 919 EreT t = transits.getLast(); 920 ereTransit(initialBlock, currentBlock, t.enter(), targetBlock, values, t.ehi(), targetExceptionHandlerIndex, ereStack); 921 } 922 } 923 924 private void ereTransit(Block.Builder initialBlock, Block.Builder currentBlock, boolean enter, Block.Builder targetBlock, List<? extends Value> values, int ehi, int targetExceptionHandlerIndex, BitSet handlerEreStack) { 925 Block.Reference ref = targetBlock.successor(values); 926 Block.Reference catcher = (ehi == targetExceptionHandlerIndex 927 ? initialBlock 928 : targetBlockForExceptionHandler(handlerEreStack, ehi)).successor(); 929 currentBlock.op(enter ? JavaOp.exceptionRegionEnter(ref, catcher) : JavaOp.exceptionRegionExit(ref, catcher)); 930 } 931 932 Block.Reference successorWithStack(Block.Builder next) { 933 return next.successor(stackValues(next)); 934 } 935 936 private List<Value> stackValues(Block.Builder limit) { 937 return stack.stream().limit(limit.parameters().size()).toList(); 938 } 939 940 private static TypeElement valueType(Value v) { 941 var t = v.type(); 942 while (t instanceof VarType vt) t = vt.valueType(); 943 return t; 944 } 945 946 private static boolean isCategory1(Value v) { 947 TypeElement t = v.type(); 948 return !t.equals(JavaType.LONG) && !t.equals(JavaType.DOUBLE); 949 } 950 951 private static boolean isUnconditionalBranch(Opcode opcode) { 952 return switch (opcode) { 953 case GOTO, ATHROW, GOTO_W, LOOKUPSWITCH, TABLESWITCH -> true; 954 default -> opcode.kind() == Opcode.Kind.RETURN; 955 }; 956 } 957 }