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 java.lang.reflect.code.bytecode; 27 28 import java.lang.classfile.Attributes; 29 import java.lang.classfile.ClassFile; 30 import java.lang.classfile.CodeElement; 31 import java.lang.classfile.CodeModel; 32 import java.lang.classfile.Instruction; 33 import java.lang.classfile.Label; 34 import java.lang.classfile.MethodModel; 35 import java.lang.classfile.Opcode; 36 import java.lang.classfile.TypeKind; 37 import java.lang.classfile.attribute.StackMapFrameInfo; 38 import java.lang.classfile.instruction.*; 39 import java.lang.constant.ClassDesc; 40 import java.lang.constant.ConstantDescs; 41 import java.lang.reflect.AccessFlag; 42 43 import java.lang.reflect.code.Block; 44 import java.lang.reflect.code.TypeElement; 45 import java.lang.reflect.code.op.CoreOp; 46 import java.lang.reflect.code.Op; 47 import java.lang.reflect.code.Value; 48 import java.lang.reflect.code.type.FieldRef; 49 import java.lang.reflect.code.type.MethodRef; 50 import java.lang.reflect.code.type.FunctionType; 51 import java.lang.reflect.code.type.JavaType; 52 import java.util.ArrayDeque; 53 import java.util.ArrayList; 54 import java.util.Collections; 55 import java.util.Deque; 56 import java.util.HashMap; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.stream.Collectors; 60 import java.util.stream.IntStream; 61 import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.*; 62 import java.lang.classfile.constantpool.ClassEntry; 63 import java.lang.constant.MethodTypeDesc; 64 65 66 public final class BytecodeLift { 67 68 private final Block.Builder entryBlock; 69 private final CodeModel codeModel; 70 private final Map<Label, Block.Builder> blockMap; 71 private final Map<String, Op.Result> varMap; 72 private final Deque<Value> stack; 73 private Block.Builder currentBlock; 74 75 private static String varName(int slot, TypeKind tk) { 76 return tk.typeName() + slot; 77 } 78 79 private static TypeElement toTypeElement(StackMapFrameInfo.VerificationTypeInfo vti) { 80 return switch (vti) { 81 case ITEM_INTEGER -> JavaType.INT; 82 case ITEM_FLOAT -> JavaType.FLOAT; 83 case ITEM_DOUBLE -> JavaType.DOUBLE; 84 case ITEM_LONG -> JavaType.LONG; 85 case ITEM_NULL -> JavaType.J_L_OBJECT; 86 case StackMapFrameInfo.ObjectVerificationTypeInfo ovti -> 87 JavaType.type(ovti.classSymbol()); 88 case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> 89 JavaType.J_L_OBJECT; 90 default -> 91 throw new IllegalArgumentException("Unexpected VTI: " + vti); 92 93 }; 94 } 95 96 private TypeElement toTypeElement(ClassEntry ce) { 97 return JavaType.type(ce.asSymbol()); 98 } 99 100 private BytecodeLift(Block.Builder entryBlock, MethodModel methodModel) { 101 if (!methodModel.flags().has(AccessFlag.STATIC)) { 102 throw new IllegalArgumentException("Unsuported lift of non-static method: " + methodModel); 103 } 104 this.entryBlock = entryBlock; 105 this.currentBlock = entryBlock; 106 this.codeModel = methodModel.code().orElseThrow(); 107 this.varMap = new HashMap<>(); 108 this.stack = new ArrayDeque<>(); 109 List<Block.Parameter> bps = entryBlock.parameters(); 110 List<ClassDesc> mps = methodModel.methodTypeSymbol().parameterList(); 111 for (int i = 0, slot = 0; i < bps.size(); i++) { 112 TypeKind tk = TypeKind.from(mps.get(i)).asLoadable(); 113 varStore(slot, tk, bps.get(i)); 114 slot += tk.slotSize(); 115 } 116 this.blockMap = codeModel.findAttribute(Attributes.STACK_MAP_TABLE).map(sma -> 117 sma.entries().stream().collect(Collectors.toUnmodifiableMap( 118 StackMapFrameInfo::target, 119 smfi -> entryBlock.block(smfi.stack().stream().map(BytecodeLift::toTypeElement).toList())))).orElse(Map.of()); 120 } 121 122 private void varStore(int slot, TypeKind tk, Value value) { 123 varMap.compute(varName(slot, tk), (varName, var) -> { 124 if (var == null) { 125 return op(CoreOp.var(varName, value)); 126 } else { 127 op(CoreOp.varStore(var, value)); 128 return var; 129 } 130 }); 131 } 132 133 private Op.Result var(int slot, TypeKind tk) { 134 Op.Result r = varMap.get(varName(slot, tk)); 135 if (r == null) throw new IllegalArgumentException("Undeclared variable: " + slot + "-" + tk); // @@@ these cases may need lazy var injection 136 return r; 137 } 138 139 private Op.Result op(Op op) { 140 return currentBlock.op(op); 141 } 142 143 // Lift to core dialect 144 public static CoreOp.FuncOp lift(byte[] classdata, String methodName) { 145 return lift(classdata, methodName, null); 146 } 147 148 public static CoreOp.FuncOp lift(byte[] classdata, String methodName, MethodTypeDesc methodType) { 149 return lift(ClassFile.of( 150 ClassFile.DebugElementsOption.DROP_DEBUG, 151 ClassFile.LineNumbersOption.DROP_LINE_NUMBERS).parse(classdata).methods().stream() 152 .filter(mm -> mm.methodName().equalsString(methodName) && (methodType == null || mm.methodTypeSymbol().equals(methodType))) 153 .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown method: " + methodName))); 154 } 155 156 public static CoreOp.FuncOp lift(MethodModel methodModel) { 157 return CoreOp.func( 158 methodModel.methodName().stringValue(), 159 MethodRef.ofNominalDescriptor(methodModel.methodTypeSymbol())).body(entryBlock -> 160 new BytecodeLift(entryBlock, methodModel).lift()); 161 } 162 163 private Block.Builder getBlock(Label l) { 164 Block.Builder bb = blockMap.get(l); 165 if (bb == null) { 166 if (currentBlock == null) { 167 throw new IllegalArgumentException("Block without an stack frame detected."); 168 } else { 169 return newBlock(); 170 } 171 } 172 return bb; 173 } 174 175 private Block.Builder newBlock() { 176 return entryBlock.block(stack.stream().map(Value::type).toList()); 177 } 178 179 private void moveTo(Block.Builder next) { 180 currentBlock = next; 181 // Stack is reconstructed from block parameters 182 stack.clear(); 183 if (currentBlock != null) { 184 currentBlock.parameters().forEach(stack::add); 185 } 186 } 187 188 private void endOfFlow() { 189 currentBlock = null; 190 // Flow discontinued, stack cleared to be ready for the next label target 191 stack.clear(); 192 } 193 194 private void lift() { 195 final Map<ExceptionCatch, Op.Result> exceptionRegionsMap = new HashMap<>(); 196 197 List<CodeElement> elements = codeModel.elementList(); 198 for (int i = 0; i < elements.size(); i++) { 199 switch (elements.get(i)) { 200 case ExceptionCatch _ -> { 201 // Exception blocks are inserted by label target (below) 202 } 203 case LabelTarget lt -> { 204 // Start of a new block 205 Block.Builder next = getBlock(lt.label()); 206 if (currentBlock != null) { 207 // Implicit goto next block, add explicitly 208 // Use stack content as next block arguments 209 op(CoreOp.branch(next.successor(List.copyOf(stack)))); 210 } 211 moveTo(next); 212 // Insert relevant tryStart and construct handler blocks, all in reversed order 213 for (ExceptionCatch ec : codeModel.exceptionHandlers().reversed()) { 214 if (lt.label() == ec.tryStart()) { 215 Block.Builder handler = getBlock(ec.handler()); 216 // Create start block 217 next = newBlock(); 218 Op ere = CoreOp.exceptionRegionEnter(next.successor(List.copyOf(stack)), handler.successor()); 219 op(ere); 220 // Store ERE into map for exit 221 exceptionRegionsMap.put(ec, ere.result()); 222 moveTo(next); 223 } 224 } 225 // Insert relevant tryEnd blocks in normal order 226 for (ExceptionCatch ec : codeModel.exceptionHandlers()) { 227 if (lt.label() == ec.tryEnd()) { 228 // Create exit block with parameters constructed from the stack 229 next = newBlock(); 230 op(CoreOp.exceptionRegionExit(exceptionRegionsMap.get(ec), next.successor())); 231 moveTo(next); 232 } 233 } 234 } 235 case BranchInstruction inst when inst.opcode().isUnconditionalBranch() -> { 236 op(CoreOp.branch(getBlock(inst.target()).successor(List.copyOf(stack)))); 237 endOfFlow(); 238 } 239 case BranchInstruction inst -> { 240 // Conditional branch 241 Value operand = stack.pop(); 242 Op cop = switch (inst.opcode()) { 243 case IFNE -> CoreOp.eq(operand, op(CoreOp.constant(JavaType.INT, 0))); 244 case IFEQ -> CoreOp.neq(operand, op(CoreOp.constant(JavaType.INT, 0))); 245 case IFGE -> CoreOp.lt(operand, op(CoreOp.constant(JavaType.INT, 0))); 246 case IFLE -> CoreOp.gt(operand, op(CoreOp.constant(JavaType.INT, 0))); 247 case IFGT -> CoreOp.le(operand, op(CoreOp.constant(JavaType.INT, 0))); 248 case IFLT -> CoreOp.ge(operand, op(CoreOp.constant(JavaType.INT, 0))); 249 case IFNULL -> CoreOp.neq(operand, op(CoreOp.constant(JavaType.J_L_OBJECT, null))); 250 case IFNONNULL -> CoreOp.eq(operand, op(CoreOp.constant(JavaType.J_L_OBJECT, null))); 251 case IF_ICMPNE -> CoreOp.eq(stack.pop(), operand); 252 case IF_ICMPEQ -> CoreOp.neq(stack.pop(), operand); 253 case IF_ICMPGE -> CoreOp.lt(stack.pop(), operand); 254 case IF_ICMPLE -> CoreOp.gt(stack.pop(), operand); 255 case IF_ICMPGT -> CoreOp.le(stack.pop(), operand); 256 case IF_ICMPLT -> CoreOp.ge(stack.pop(), operand); 257 case IF_ACMPEQ -> CoreOp.neq(stack.pop(), operand); 258 case IF_ACMPNE -> CoreOp.eq(stack.pop(), operand); 259 default -> throw new UnsupportedOperationException("Unsupported branch instruction: " + inst); 260 }; 261 if (!stack.isEmpty()) { 262 throw new UnsupportedOperationException("Operands on stack for branch not supported"); 263 } 264 Block.Builder next = currentBlock.block(); 265 op(CoreOp.conditionalBranch(op(cop), 266 next.successor(), 267 getBlock(inst.target()).successor())); 268 moveTo(next); 269 } 270 // case LookupSwitchInstruction si -> { 271 // // Default label is first successor 272 // b.addSuccessor(blockMap.get(si.defaultTarget())); 273 // addSuccessors(si.cases(), blockMap, b); 274 // } 275 // case TableSwitchInstruction si -> { 276 // // Default label is first successor 277 // b.addSuccessor(blockMap.get(si.defaultTarget())); 278 // addSuccessors(si.cases(), blockMap, b); 279 // } 280 case ReturnInstruction inst when inst.typeKind() == TypeKind.VoidType -> { 281 op(CoreOp._return()); 282 endOfFlow(); 283 } 284 case ReturnInstruction _ -> { 285 op(CoreOp._return(stack.pop())); 286 endOfFlow(); 287 } 288 case ThrowInstruction _ -> { 289 op(CoreOp._throw(stack.pop())); 290 endOfFlow(); 291 } 292 case LoadInstruction inst -> { 293 stack.push(op(CoreOp.varLoad(var(inst.slot(), inst.typeKind())))); 294 } 295 case StoreInstruction inst -> { 296 varStore(inst.slot(), inst.typeKind(), stack.pop()); 297 } 298 case IncrementInstruction inst -> { 299 varStore(inst.slot(), TypeKind.IntType, op(CoreOp.add( 300 op(CoreOp.varLoad(var(inst.slot(), TypeKind.IntType))), 301 op(CoreOp.constant(JavaType.INT, inst.constant()))))); 302 } 303 case ConstantInstruction inst -> { 304 stack.push(op(switch (inst.constantValue()) { 305 case ClassDesc v -> CoreOp.constant(JavaType.J_L_CLASS, JavaType.type(v)); 306 case Double v -> CoreOp.constant(JavaType.DOUBLE, v); 307 case Float v -> CoreOp.constant(JavaType.FLOAT, v); 308 case Integer v -> CoreOp.constant(JavaType.INT, v); 309 case Long v -> CoreOp.constant(JavaType.LONG, v); 310 case String v -> CoreOp.constant(JavaType.J_L_STRING, v); 311 default -> 312 // @@@ MethodType, MethodHandle, ConstantDynamic 313 throw new IllegalArgumentException("Unsupported constant value: " + inst.constantValue()); 314 })); 315 } 316 case ConvertInstruction inst -> { 317 stack.push(op(CoreOp.conv(switch (inst.toType()) { 318 case ByteType -> JavaType.BYTE; 319 case ShortType -> JavaType.SHORT; 320 case IntType -> JavaType.INT; 321 case FloatType -> JavaType.FLOAT; 322 case LongType -> JavaType.LONG; 323 case DoubleType -> JavaType.DOUBLE; 324 case CharType -> JavaType.CHAR; 325 case BooleanType -> JavaType.BOOLEAN; 326 default -> 327 throw new IllegalArgumentException("Unsupported conversion target: " + inst.toType()); 328 }, stack.pop()))); 329 } 330 case OperatorInstruction inst -> { 331 Value operand = stack.pop(); 332 stack.push(op(switch (inst.opcode()) { 333 case IADD, LADD, FADD, DADD -> 334 CoreOp.add(stack.pop(), operand); 335 case ISUB, LSUB, FSUB, DSUB -> 336 CoreOp.sub(stack.pop(), operand); 337 case IMUL, LMUL, FMUL, DMUL -> 338 CoreOp.mul(stack.pop(), operand); 339 case IDIV, LDIV, FDIV, DDIV -> 340 CoreOp.div(stack.pop(), operand); 341 case IREM, LREM, FREM, DREM -> 342 CoreOp.mod(stack.pop(), operand); 343 case INEG, LNEG, FNEG, DNEG -> 344 CoreOp.neg(operand); 345 case ARRAYLENGTH -> 346 CoreOp.arrayLength(operand); 347 case IAND, LAND -> 348 CoreOp.and(stack.pop(), operand); 349 case IOR, LOR -> 350 CoreOp.or(stack.pop(), operand); 351 case IXOR, LXOR -> 352 CoreOp.xor(stack.pop(), operand); 353 default -> 354 throw new IllegalArgumentException("Unsupported operator opcode: " + inst.opcode()); 355 })); 356 } 357 case FieldInstruction inst -> { 358 FieldRef fd = FieldRef.field( 359 JavaType.type(inst.owner().asSymbol()), 360 inst.name().stringValue(), 361 JavaType.type(inst.typeSymbol())); 362 switch (inst.opcode()) { 363 case GETFIELD -> 364 stack.push(op(CoreOp.fieldLoad(fd, stack.pop()))); 365 case GETSTATIC -> 366 stack.push(op(CoreOp.fieldLoad(fd))); 367 case PUTFIELD -> { 368 Value value = stack.pop(); 369 stack.push(op(CoreOp.fieldStore(fd, stack.pop(), value))); 370 } 371 case PUTSTATIC -> 372 stack.push(op(CoreOp.fieldStore(fd, stack.pop()))); 373 default -> 374 throw new IllegalArgumentException("Unsupported field opcode: " + inst.opcode()); 375 } 376 } 377 case ArrayStoreInstruction _ -> { 378 Value value = stack.pop(); 379 Value index = stack.pop(); 380 op(CoreOp.arrayStoreOp(stack.pop(), index, value)); 381 } 382 case ArrayLoadInstruction _ -> { 383 Value index = stack.pop(); 384 stack.push(op(CoreOp.arrayLoadOp(stack.pop(), index))); 385 } 386 case InvokeInstruction inst -> { 387 FunctionType mType = MethodRef.ofNominalDescriptor(inst.typeSymbol()); 388 List<Value> operands = new ArrayList<>(); 389 for (var _ : mType.parameterTypes()) { 390 operands.add(stack.pop()); 391 } 392 MethodRef mDesc = MethodRef.method( 393 JavaType.type(inst.owner().asSymbol()), 394 inst.name().stringValue(), 395 mType); 396 Op.Result result = switch (inst.opcode()) { 397 case INVOKEVIRTUAL, INVOKEINTERFACE -> { 398 operands.add(stack.pop()); 399 yield op(CoreOp.invoke(mDesc, operands.reversed())); 400 } 401 case INVOKESTATIC -> 402 op(CoreOp.invoke(mDesc, operands.reversed())); 403 case INVOKESPECIAL -> { 404 if (inst.name().equalsString(ConstantDescs.INIT_NAME)) { 405 yield op(CoreOp._new( 406 FunctionType.functionType( 407 mDesc.refType(), 408 mType.parameterTypes()), 409 operands.reversed())); 410 } else { 411 operands.add(stack.pop()); 412 yield op(CoreOp.invoke(mDesc, operands.reversed())); 413 } 414 } 415 default -> 416 throw new IllegalArgumentException("Unsupported invocation opcode: " + inst.opcode()); 417 }; 418 if (!result.type().equals(JavaType.VOID)) { 419 stack.push(result); 420 } 421 } 422 case NewObjectInstruction _ -> { 423 // Skip over this and the dup to process the invoke special 424 if (i + 2 < elements.size() - 1 425 && elements.get(i + 1) instanceof StackInstruction dup 426 && dup.opcode() == Opcode.DUP) { 427 i++; 428 } else { 429 throw new UnsupportedOperationException("New must be followed by dup"); 430 } 431 } 432 case NewPrimitiveArrayInstruction inst -> { 433 stack.push(op(CoreOp.newArray( 434 switch (inst.typeKind()) { 435 case BooleanType -> JavaType.BOOLEAN_ARRAY; 436 case ByteType -> JavaType.BYTE_ARRAY; 437 case CharType -> JavaType.CHAR_ARRAY; 438 case DoubleType -> JavaType.DOUBLE_ARRAY; 439 case FloatType -> JavaType.FLOAT_ARRAY; 440 case IntType -> JavaType.INT_ARRAY; 441 case LongType -> JavaType.LONG_ARRAY; 442 case ShortType -> JavaType.SHORT_ARRAY; 443 default -> 444 throw new UnsupportedOperationException("Unsupported new primitive array type: " + inst.typeKind()); 445 }, 446 stack.pop()))); 447 } 448 case NewReferenceArrayInstruction inst -> { 449 stack.push(op(CoreOp.newArray( 450 JavaType.type(inst.componentType().asSymbol().arrayType()), 451 stack.pop()))); 452 } 453 case NewMultiArrayInstruction inst -> { 454 stack.push(op(CoreOp._new( 455 FunctionType.functionType( 456 JavaType.type(inst.arrayType().asSymbol()), 457 Collections.nCopies(inst.dimensions(), JavaType.INT)), 458 IntStream.range(0, inst.dimensions()).mapToObj(_ -> stack.pop()).toList().reversed()))); 459 } 460 case TypeCheckInstruction inst when inst.opcode() == Opcode.CHECKCAST -> { 461 stack.push(op(CoreOp.cast(JavaType.type(inst.type().asSymbol()), stack.pop()))); 462 } 463 case StackInstruction inst -> { 464 switch (inst.opcode()) { 465 case POP, POP2 -> stack.pop(); // @@@ check the type width 466 case DUP, DUP2 -> stack.push(stack.peek()); 467 //@@@ implement all other stack ops 468 default -> 469 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst); 470 } 471 } 472 case Instruction inst -> 473 throw new UnsupportedOperationException("Unsupported instruction: " + inst.opcode().name()); 474 default -> 475 throw new UnsupportedOperationException("Unsupported code element: " + elements.get(i)); 476 } 477 } 478 } 479 }