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