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.internal.classfile.impl.BytecodeHelpers; 29 30 import java.lang.classfile.Attributes; 31 import java.lang.classfile.ClassFile; 32 import java.lang.classfile.ClassModel; 33 import java.lang.classfile.CodeElement; 34 import java.lang.classfile.CodeModel; 35 import java.lang.classfile.Instruction; 36 import java.lang.classfile.Label; 37 import java.lang.classfile.MethodModel; 38 import java.lang.classfile.Opcode; 39 import java.lang.classfile.PseudoInstruction; 40 import java.lang.classfile.TypeKind; 41 import java.lang.classfile.attribute.StackMapFrameInfo; 42 import java.lang.classfile.instruction.*; 43 import java.lang.constant.ClassDesc; 44 import java.lang.constant.ConstantDesc; 45 import java.lang.constant.ConstantDescs; 46 import java.lang.constant.DirectMethodHandleDesc; 47 import java.lang.constant.DynamicConstantDesc; 48 import java.lang.constant.MethodTypeDesc; 49 import java.lang.invoke.CallSite; 50 import java.lang.invoke.MethodHandle; 51 import java.lang.reflect.AccessFlag; 52 import jdk.incubator.code.Block; 53 import jdk.incubator.code.TypeElement; 54 import jdk.incubator.code.op.CoreOp; 55 import jdk.incubator.code.Op; 56 import jdk.incubator.code.Value; 57 import jdk.incubator.code.analysis.NormalizeBlocksTransformer; 58 import jdk.incubator.code.type.FieldRef; 59 import jdk.incubator.code.type.FunctionType; 60 import jdk.incubator.code.type.JavaType; 61 import jdk.incubator.code.type.MethodRef; 62 import jdk.incubator.code.type.PrimitiveType; 63 import jdk.incubator.code.type.VarType; 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 -> CoreOp.eq(operand, liftConstant(0)); 263 case IFEQ -> CoreOp.neq(operand, liftConstant(0)); 264 case IFGE -> CoreOp.lt(operand, liftConstant(0)); 265 case IFLE -> CoreOp.gt(operand, liftConstant(0)); 266 case IFGT -> CoreOp.le(operand, liftConstant(0)); 267 case IFLT -> CoreOp.ge(operand, liftConstant(0)); 268 case IFNULL -> CoreOp.neq(operand, liftConstant(null)); 269 case IFNONNULL -> CoreOp.eq(operand, liftConstant(null)); 270 case IF_ICMPNE -> CoreOp.eq(stack.pop(), operand); 271 case IF_ICMPEQ -> CoreOp.neq(stack.pop(), operand); 272 case IF_ICMPGE -> CoreOp.lt(stack.pop(), operand); 273 case IF_ICMPLE -> CoreOp.gt(stack.pop(), operand); 274 case IF_ICMPGT -> CoreOp.le(stack.pop(), operand); 275 case IF_ICMPLT -> CoreOp.ge(stack.pop(), operand); 276 case IF_ACMPEQ -> CoreOp.neq(stack.pop(), operand); 277 case IF_ACMPNE -> CoreOp.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(CoreOp._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(CoreOp.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(CoreOp.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 CoreOp.add(stack.pop(), operand); 337 case ISUB, LSUB, FSUB, DSUB -> 338 CoreOp.sub(stack.pop(), operand); 339 case IMUL, LMUL, FMUL, DMUL -> 340 CoreOp.mul(stack.pop(), operand); 341 case IDIV, LDIV, FDIV, DDIV -> 342 CoreOp.div(stack.pop(), operand); 343 case IREM, LREM, FREM, DREM -> 344 CoreOp.mod(stack.pop(), operand); 345 case INEG, LNEG, FNEG, DNEG -> 346 CoreOp.neg(operand); 347 case ARRAYLENGTH -> 348 CoreOp.arrayLength(operand); 349 case IAND, LAND -> 350 CoreOp.and(stack.pop(), operand); 351 case IOR, LOR -> 352 CoreOp.or(stack.pop(), operand); 353 case IXOR, LXOR -> 354 CoreOp.xor(stack.pop(), operand); 355 case ISHL, LSHL -> 356 CoreOp.lshl(stack.pop(), operand); 357 case ISHR, LSHR -> 358 CoreOp.ashr(stack.pop(), operand); 359 case IUSHR, LUSHR -> 360 CoreOp.lshr(stack.pop(), operand); 361 case LCMP -> 362 CoreOp.invoke(LCMP, stack.pop(), operand); 363 case FCMPL, FCMPG -> 364 CoreOp.invoke(FCMP, stack.pop(), operand); 365 case DCMPL, DCMPG -> 366 CoreOp.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(CoreOp.fieldLoad(fd, stack.pop()))); 379 case GETSTATIC -> 380 stack.push(op(CoreOp.fieldLoad(fd))); 381 case PUTFIELD -> { 382 Value value = stack.pop(); 383 op(CoreOp.fieldStore(fd, stack.pop(), value)); 384 } 385 case PUTSTATIC -> 386 op(CoreOp.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(CoreOp.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(CoreOp.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(CoreOp.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(CoreOp.invoke(CoreOp.InvokeOp.InvokeKind.INSTANCE, false, 429 mDesc.type().returnType(), mDesc, operands.reversed())); 430 } 431 case INVOKESTATIC -> 432 op(CoreOp.invoke(CoreOp.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(CoreOp._new( 438 FunctionType.functionType( 439 mDesc.refType(), 440 mType.parameterTypes()), 441 operands.reversed())); 442 } else { 443 operands.add(stack.pop()); 444 yield op(CoreOp.invoke(CoreOp.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 = FunctionType.functionType(JavaType.type(mt.returnType()), 474 mt.parameterList().stream().map(JavaType::type).toList()); 475 CoreOp.LambdaOp.Builder lambda = CoreOp.lambda(currentBlock.parentBody(), 476 lambdaFunc, 477 JavaType.type(inst.typeSymbol().returnType())); 478 if (dmhd.methodName().startsWith("lambda$") && dmhd.owner().equals(classModel.thisClass().asSymbol())) { 479 // inline lambda impl method 480 MethodModel implMethod = classModel.methods().stream().filter(m -> m.methodName().equalsString(dmhd.methodName())).findFirst().orElseThrow(); 481 stack.push(op(lambda.body(eb -> new BytecodeLift(eb, 482 classModel, 483 implMethod.code().orElseThrow(), 484 capturedValues).liftBody()))); 485 } else { 486 // lambda call to a MH 487 stack.push(op(lambda.body(eb -> { 488 Op.Result ret = eb.op(CoreOp.invoke( 489 MethodRef.method(JavaType.type(dmhd.owner()), 490 dmhd.methodName(), 491 lambdaFunc.returnType(), 492 lambdaFunc.parameterTypes()), 493 Stream.concat(Arrays.stream(capturedValues), eb.parameters().stream()).toArray(Value[]::new))); 494 eb.op(ret.type().equals(JavaType.VOID) ? CoreOp._return() : CoreOp._return(ret)); 495 }))); 496 } 497 } else if (bsmOwner.equals(CD_StringConcatFactory)) { 498 int argsCount = inst.typeSymbol().parameterCount(); 499 Deque<Value> args = new ArrayDeque<>(argsCount); 500 for (int ai = 0; ai < argsCount; ai++) { 501 args.push(stack.pop()); 502 } 503 Value res = null; 504 if (bsm.methodName().equals("makeConcat")) { 505 for (Value argVal : args) { 506 res = res == null ? argVal : op(CoreOp.concat(res, argVal)); 507 } 508 } else { 509 assert bsm.methodName().equals("makeConcatWithConstants"); 510 var bsmArgs = inst.bootstrapArgs(); 511 String recipe = (String)(bsmArgs.getFirst()); 512 int bsmArg = 1; 513 for (int ri = 0; ri < recipe.length(); ri++) { 514 Value argVal = switch (recipe.charAt(ri)) { 515 case '\u0001' -> args.pop(); 516 case '\u0002' -> liftConstant(bsmArgs.get(bsmArg++)); 517 default -> { 518 char c; 519 int start = ri; 520 while (ri < recipe.length() && (c = recipe.charAt(ri)) != '\u0001' && c != '\u0002') ri++; 521 yield liftConstant(recipe.substring(start, ri--)); 522 } 523 }; 524 res = res == null ? argVal : op(CoreOp.concat(res, argVal)); 525 } 526 } 527 if (res != null) stack.push(res); 528 } else { 529 MethodTypeDesc mtd = inst.typeSymbol(); 530 531 //bootstrap 532 MethodTypeDesc bsmDesc = bsm.invocationType(); 533 MethodRef bsmRef = MethodRef.method(JavaType.type(bsmOwner), 534 bsm.methodName(), 535 JavaType.type(bsmDesc.returnType()), 536 bsmDesc.parameterList().stream().map(JavaType::type).toArray(TypeElement[]::new)); 537 538 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, inst.name().toString(), mtd, inst.bootstrapArgs()); 539 Value methodHandle = op(CoreOp.invoke(MethodRef.method(CallSite.class, "dynamicInvoker", MethodHandle.class), 540 op(CoreOp.invoke(JavaType.type(ConstantDescs.CD_CallSite), bsmRef, bootstrapArgs)))); 541 542 //invocation 543 List<Value> operands = new ArrayList<>(); 544 for (int c = 0; c < mtd.parameterCount(); c++) { 545 operands.add(stack.pop()); 546 } 547 operands.add(methodHandle); 548 MethodRef mDesc = MethodRef.method(JavaType.type(ConstantDescs.CD_MethodHandle), 549 "invokeExact", 550 MethodRef.ofNominalDescriptor(mtd)); 551 Op.Result result = op(CoreOp.invoke(mDesc, operands.reversed())); 552 if (!result.type().equals(JavaType.VOID)) { 553 stack.push(result); 554 } 555 } 556 } 557 case NewObjectInstruction inst -> { 558 // Skip over this and the dup to process the invoke special 559 if (i + 2 < elements.size() - 1 560 && elements.get(i + 1) instanceof StackInstruction dup 561 && dup.opcode() == Opcode.DUP) { 562 i++; 563 newStack.push(inst.className().asSymbol()); 564 } else { 565 throw new UnsupportedOperationException("New must be followed by dup"); 566 } 567 } 568 case NewPrimitiveArrayInstruction inst -> { 569 stack.push(op(CoreOp.newArray( 570 switch (inst.typeKind()) { 571 case BOOLEAN -> JavaType.BOOLEAN_ARRAY; 572 case BYTE -> JavaType.BYTE_ARRAY; 573 case CHAR -> JavaType.CHAR_ARRAY; 574 case DOUBLE -> JavaType.DOUBLE_ARRAY; 575 case FLOAT -> JavaType.FLOAT_ARRAY; 576 case INT -> JavaType.INT_ARRAY; 577 case LONG -> JavaType.LONG_ARRAY; 578 case SHORT -> JavaType.SHORT_ARRAY; 579 default -> 580 throw new UnsupportedOperationException("Unsupported new primitive array type: " + inst.typeKind()); 581 }, 582 stack.pop()))); 583 } 584 case NewReferenceArrayInstruction inst -> { 585 stack.push(op(CoreOp.newArray( 586 JavaType.type(inst.componentType().asSymbol().arrayType()), 587 stack.pop()))); 588 } 589 case NewMultiArrayInstruction inst -> { 590 stack.push(op(CoreOp._new( 591 FunctionType.functionType( 592 JavaType.type(inst.arrayType().asSymbol()), 593 Collections.nCopies(inst.dimensions(), JavaType.INT)), 594 IntStream.range(0, inst.dimensions()).mapToObj(_ -> stack.pop()).toList().reversed()))); 595 } 596 case TypeCheckInstruction inst when inst.opcode() == Opcode.CHECKCAST -> { 597 stack.push(op(CoreOp.cast(JavaType.type(inst.type().asSymbol()), stack.pop()))); 598 } 599 case TypeCheckInstruction inst -> { 600 stack.push(op(CoreOp.instanceOf(JavaType.type(inst.type().asSymbol()), stack.pop()))); 601 } 602 case StackInstruction inst -> { 603 switch (inst.opcode()) { 604 case POP -> { 605 stack.pop(); 606 } 607 case POP2 -> { 608 if (isCategory1(stack.pop())) { 609 stack.pop(); 610 } 611 } 612 case DUP -> { 613 stack.push(stack.peek()); 614 } 615 case DUP_X1 -> { 616 var value1 = stack.pop(); 617 var value2 = stack.pop(); 618 stack.push(value1); 619 stack.push(value2); 620 stack.push(value1); 621 } 622 case DUP_X2 -> { 623 var value1 = stack.pop(); 624 var value2 = stack.pop(); 625 if (isCategory1(value2)) { 626 var value3 = stack.pop(); 627 stack.push(value1); 628 stack.push(value3); 629 } else { 630 stack.push(value1); 631 } 632 stack.push(value2); 633 stack.push(value1); 634 } 635 case DUP2 -> { 636 var value1 = stack.peek(); 637 if (isCategory1(value1)) { 638 stack.pop(); 639 var value2 = stack.peek(); 640 stack.push(value1); 641 stack.push(value2); 642 } 643 stack.push(value1); 644 } 645 case DUP2_X1 -> { 646 var value1 = stack.pop(); 647 var value2 = stack.pop(); 648 if (isCategory1(value1)) { 649 var value3 = stack.pop(); 650 stack.push(value2); 651 stack.push(value1); 652 stack.push(value3); 653 } else { 654 stack.push(value1); 655 } 656 stack.push(value2); 657 stack.push(value1); 658 } 659 case DUP2_X2 -> { 660 var value1 = stack.pop(); 661 var value2 = stack.pop(); 662 if (isCategory1(value1)) { 663 var value3 = stack.pop(); 664 if (isCategory1(value3)) { 665 var value4 = stack.pop(); 666 stack.push(value2); 667 stack.push(value1); 668 stack.push(value4); 669 } else { 670 stack.push(value2); 671 stack.push(value1); 672 } 673 stack.push(value3); 674 } else { 675 if (isCategory1(value2)) { 676 var value3 = stack.pop(); 677 stack.push(value1); 678 stack.push(value3); 679 } else { 680 stack.push(value1); 681 } 682 } 683 stack.push(value2); 684 stack.push(value1); 685 } 686 case SWAP -> { 687 var value1 = stack.pop(); 688 var value2 = stack.pop(); 689 stack.push(value1); 690 stack.push(value2); 691 } 692 default -> 693 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst); 694 } 695 } 696 case MonitorInstruction inst -> { 697 var monitor = stack.pop(); 698 switch (inst.opcode()) { 699 case MONITORENTER -> op(CoreOp.monitorEnter(monitor)); 700 case MONITOREXIT -> op(CoreOp.monitorExit(monitor)); 701 default -> 702 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst); 703 } 704 } 705 case NopInstruction _ -> {} 706 case PseudoInstruction _ -> {} 707 case Instruction inst -> 708 throw new UnsupportedOperationException("Unsupported instruction: " + inst.opcode().name()); 709 default -> 710 throw new UnsupportedOperationException("Unsupported code element: " + elements.get(i)); 711 } 712 } 713 assert newStack.isEmpty(); 714 } 715 716 private Op.Result liftConstantsIntoArray(TypeElement arrayType, Object... constants) { 717 Op.Result array = op(CoreOp.newArray(arrayType, liftConstant(constants.length))); 718 for (int i = 0; i < constants.length; i++) { 719 op(CoreOp.arrayStoreOp(array, liftConstant(i), liftConstant(constants[i]))); 720 } 721 return array; 722 } 723 724 private Op.Result liftDefaultValue(ClassDesc type) { 725 return liftConstant(switch (TypeKind.from(type)) { 726 case BOOLEAN -> false; 727 case BYTE -> (byte)0; 728 case CHAR -> (char)0; 729 case DOUBLE -> 0d; 730 case FLOAT -> 0f; 731 case INT -> 0; 732 case LONG -> 0l; 733 case REFERENCE -> null; 734 case SHORT -> (short)0; 735 default -> throw new IllegalStateException("Invalid type " + type.displayName()); 736 }); 737 } 738 739 private Op.Result liftConstant(Object c) { 740 return switch (c) { 741 case null -> op(CoreOp.constant(UnresolvedType.unresolvedRef(), null)); 742 case ClassDesc cd -> op(CoreOp.constant(JavaType.J_L_CLASS, JavaType.type(cd))); 743 case Double d -> op(CoreOp.constant(JavaType.DOUBLE, d)); 744 case Float f -> op(CoreOp.constant(JavaType.FLOAT, f)); 745 case Integer ii -> op(CoreOp.constant(UnresolvedType.unresolvedInt(), ii)); 746 case Long l -> op(CoreOp.constant(JavaType.LONG, l)); 747 case String s -> op(CoreOp.constant(JavaType.J_L_STRING, s)); 748 case DirectMethodHandleDesc dmh -> { 749 Op.Result lookup = op(CoreOp.invoke(LOOKUP)); 750 Op.Result owner = liftConstant(dmh.owner()); 751 Op.Result name = liftConstant(dmh.methodName()); 752 MethodTypeDesc invDesc = dmh.invocationType(); 753 yield op(switch (dmh.kind()) { 754 case STATIC, INTERFACE_STATIC -> 755 CoreOp.invoke(FIND_STATIC, lookup, owner, name, liftConstant(invDesc)); 756 case VIRTUAL, INTERFACE_VIRTUAL -> 757 CoreOp.invoke(FIND_VIRTUAL, lookup, owner, name, liftConstant(invDesc.dropParameterTypes(0, 1))); 758 case SPECIAL, INTERFACE_SPECIAL -> 759 //CoreOp.invoke(MethodRef.method(e), "findSpecial", owner, name, liftConstant(invDesc.dropParameterTypes(0, 1)), lookup.lookupClass()); 760 throw new UnsupportedOperationException(dmh.toString()); 761 case CONSTRUCTOR -> 762 CoreOp.invoke(FIND_CONSTRUCTOR, lookup, owner, liftConstant(invDesc.changeReturnType(ConstantDescs.CD_Void))); 763 case GETTER -> 764 CoreOp.invoke(FIND_GETTER, lookup, owner, name, liftConstant(invDesc.returnType())); 765 case STATIC_GETTER -> 766 CoreOp.invoke(FIND_STATIC_GETTER, lookup, owner, name, liftConstant(invDesc.returnType())); 767 case SETTER -> 768 CoreOp.invoke(FIND_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(1))); 769 case STATIC_SETTER -> 770 CoreOp.invoke(FIND_STATIC_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(0))); 771 }); 772 } 773 case MethodTypeDesc mt -> op(switch (mt.parameterCount()) { 774 case 0 -> CoreOp.invoke(METHOD_TYPE_0, liftConstant(mt.returnType())); 775 case 1 -> CoreOp.invoke(METHOD_TYPE_1, liftConstant(mt.returnType()), liftConstant(mt.parameterType(0))); 776 default -> CoreOp.invoke(METHOD_TYPE_L, liftConstant(mt.returnType()), liftConstantsIntoArray(CLASS_ARRAY, (Object[])mt.parameterArray())); 777 }); 778 case DynamicConstantDesc<?> v when v.bootstrapMethod().owner().equals(ConstantDescs.CD_ConstantBootstraps) 779 && v.bootstrapMethod().methodName().equals("nullConstant") 780 -> { 781 c = null; 782 yield liftConstant(null); 783 } 784 case DynamicConstantDesc<?> dcd -> { 785 DirectMethodHandleDesc bsm = dcd.bootstrapMethod(); 786 MethodTypeDesc bsmDesc = bsm.invocationType(); 787 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, dcd.constantName(), dcd.constantType(), dcd.bootstrapArgsList()); 788 MethodRef bsmRef = MethodRef.method(JavaType.type(bsm.owner()), 789 bsm.methodName(), 790 JavaType.type(bsmDesc.returnType()), 791 bsmDesc.parameterList().stream().map(JavaType::type).toArray(TypeElement[]::new)); 792 yield op(CoreOp.invoke(bsmRef, bootstrapArgs)); 793 } 794 case Boolean b -> op(CoreOp.constant(JavaType.BOOLEAN, b)); 795 case Byte b -> op(CoreOp.constant(JavaType.BYTE, b)); 796 case Short s -> op(CoreOp.constant(JavaType.SHORT, s)); 797 case Character ch -> op(CoreOp.constant(JavaType.CHAR, ch)); 798 default -> throw new UnsupportedOperationException(c.getClass().toString()); 799 }; 800 } 801 802 private Value[] liftBootstrapArgs(MethodTypeDesc bsmDesc, String name, ConstantDesc desc, List<ConstantDesc> bsmArgs) { 803 Value[] bootstrapArgs = new Value[bsmDesc.parameterCount()]; 804 bootstrapArgs[0] = op(CoreOp.invoke(LOOKUP)); 805 bootstrapArgs[1] = liftConstant(name); 806 bootstrapArgs[2] = liftConstant(desc); 807 ClassDesc lastArgType = bsmDesc.parameterType(bsmDesc.parameterCount() - 1); 808 if (lastArgType.isArray()) { 809 for (int ai = 0; ai < bootstrapArgs.length - 4; ai++) { 810 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai)); 811 } 812 // Vararg tail of the bootstrap method parameters 813 bootstrapArgs[bootstrapArgs.length - 1] = 814 liftConstantsIntoArray(JavaType.type(lastArgType), 815 bsmArgs.subList(bootstrapArgs.length - 4, bsmArgs.size()).toArray()); 816 } else { 817 for (int ai = 0; ai < bootstrapArgs.length - 3; ai++) { 818 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai)); 819 } 820 } 821 return bootstrapArgs; 822 } 823 824 private void liftSwitch(Label defaultTarget, List<SwitchCase> cases) { 825 Value v = stack.pop(); 826 if (!valueType(v).equals(PrimitiveType.INT)) { 827 v = op(CoreOp.conv(PrimitiveType.INT, v)); 828 } 829 SwitchCase last = cases.getLast(); 830 Block.Builder def = targetBlockForBranch(defaultTarget); 831 for (SwitchCase sc : cases) { 832 if (sc == last) { 833 op(CoreOp.conditionalBranch( 834 op(CoreOp.eq(v, liftConstant(sc.caseValue()))), 835 successorWithStack(targetBlockForBranch(sc.target())), 836 successorWithStack(def))); 837 } else { 838 Block.Builder next = entryBlock.block(); 839 op(CoreOp.conditionalBranch( 840 op(CoreOp.eq(v, liftConstant(sc.caseValue()))), 841 successorWithStack(targetBlockForBranch(sc.target())), 842 next.successor())); 843 currentBlock = next; 844 } 845 } 846 endOfFlow(); 847 } 848 849 private Block.Builder newBlock(List<Block.Parameter> otherBlockParams) { 850 return entryBlock.block(otherBlockParams.stream().map(Block.Parameter::type).toList()); 851 } 852 853 private void endOfFlow() { 854 currentBlock = null; 855 // Flow discontinued, stack cleared to be ready for the next label target 856 stack.clear(); 857 } 858 859 private Block.Builder targetBlockForExceptionHandler(BitSet initialEreStack, int exceptionHandlerIndex) { 860 Block.Builder target = exceptionHandlerBlocks.get(exceptionHandlerIndex); 861 if (target == null) { // Avoid ConcurrentModificationException 862 Label ehLabel = exceptionHandlers.get(exceptionHandlerIndex); 863 target = transitionBlockForTarget(initialEreStack, exceptionHandlersMap.get(ehLabel), blockMap.get(ehLabel), exceptionHandlerIndex); 864 exceptionHandlerBlocks.put(exceptionHandlerIndex, target); 865 } 866 return target; 867 } 868 869 private Block.Builder targetBlockForBranch(Label targetLabel) { 870 return transitionBlockForTarget(actualEreStack, exceptionHandlersMap.get(targetLabel), blockMap.get(targetLabel), -1); 871 } 872 873 private Block.Builder transitionBlockForTarget(BitSet initialEreStack, BitSet targetEreStack, Block.Builder targetBlock, int targetExceptionHandlerIndex) { 874 if (targetBlock == null) return null; 875 Block.Builder transitionBlock = newBlock(targetBlock.parameters()); 876 ereTransit(initialEreStack, targetEreStack, transitionBlock, targetBlock, transitionBlock.parameters(), targetExceptionHandlerIndex); 877 return transitionBlock; 878 } 879 880 record EreT(boolean enter, int ehi) {} 881 882 private void ereTransit(BitSet initialEreStack, BitSet targetEreStack, Block.Builder initialBlock, Block.Builder targetBlock, List<? extends Value> values, int targetExceptionHandlerIndex) { 883 List<EreT> transits = new ArrayList<>(); 884 BitSet ereStack = (BitSet)initialEreStack.clone(); 885 ereStack.andNot(targetEreStack); 886 // Split region exits by handler stack 887 for (int ehi = ereStack.previousSetBit(Integer.MAX_VALUE); ehi >= 0; ehi = ereStack.previousSetBit(ehi - 1)) { 888 transits.add(new EreT(false, ehi)); 889 } 890 ereStack = (BitSet)targetEreStack.clone(); 891 ereStack.andNot(initialEreStack); 892 // Split region enters by handler stack 893 for (int ehi = ereStack.nextSetBit(0); ehi >= 0; ehi = ereStack.nextSetBit(ehi + 1)) { 894 transits.add(new EreT(true, ehi)); 895 } 896 897 if (transits.isEmpty()) { 898 // Join with branch 899 initialBlock.op(CoreOp.branch(targetBlock.successor(values))); 900 } else { 901 // Insert ERE transitions 902 Block.Builder currentBlock = initialBlock; 903 ereStack = (BitSet)initialEreStack.clone(); 904 for (int i = 0; i < transits.size() - 1; i++) { 905 EreT t = transits.get(i); 906 Block.Builder next = entryBlock.block(); 907 ereTransit(initialBlock, currentBlock, t.enter(), next, List.of(), t.ehi(), targetExceptionHandlerIndex, ereStack); 908 currentBlock = next; 909 ereStack.set(t.ehi(), t.enter()); 910 } 911 EreT t = transits.getLast(); 912 ereTransit(initialBlock, currentBlock, t.enter(), targetBlock, values, t.ehi(), targetExceptionHandlerIndex, ereStack); 913 } 914 } 915 916 private void ereTransit(Block.Builder initialBlock, Block.Builder currentBlock, boolean enter, Block.Builder targetBlock, List<? extends Value> values, int ehi, int targetExceptionHandlerIndex, BitSet handlerEreStack) { 917 Block.Reference ref = targetBlock.successor(values); 918 Block.Reference catcher = (ehi == targetExceptionHandlerIndex 919 ? initialBlock 920 : targetBlockForExceptionHandler(handlerEreStack, ehi)).successor(); 921 currentBlock.op(enter ? CoreOp.exceptionRegionEnter(ref, catcher) : CoreOp.exceptionRegionExit(ref, catcher)); 922 } 923 924 Block.Reference successorWithStack(Block.Builder next) { 925 return next.successor(stackValues(next)); 926 } 927 928 private List<Value> stackValues(Block.Builder limit) { 929 return stack.stream().limit(limit.parameters().size()).toList(); 930 } 931 932 private static TypeElement valueType(Value v) { 933 var t = v.type(); 934 while (t instanceof VarType vt) t = vt.valueType(); 935 return t; 936 } 937 938 private static boolean isCategory1(Value v) { 939 TypeElement t = v.type(); 940 return !t.equals(JavaType.LONG) && !t.equals(JavaType.DOUBLE); 941 } 942 }