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