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 }