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