package me.august.lumen.common; import me.august.lumen.compile.codegen.BuildContext; import me.august.lumen.compile.parser.ast.expr.Expression; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.lang.reflect.Field; import java.util.*; public final class BytecodeUtil implements Opcodes { public static final String[] LUMEN_PRIMITIVES = { "int", "byte", "boolean", "bool", "long", "char", "double", "float", "void" }; public static final Set<String> LUMEN_PRIMITIVES_SET = new HashSet<>(Arrays.asList(LUMEN_PRIMITIVES)); private static final Map<String, Integer> OPCODES = new HashMap<>(); private static final Map<String, Integer> ACC_MODS = new HashMap<>(); static { initOpcodeTable(); initAccessTable(); } private static final int ASM_OPCODE_FIELDS_START_INDEX = 60; private static final int ASM_OPCODE_FIELDS_END_INDEX = 216; private static void initOpcodeTable() { int start = ASM_OPCODE_FIELDS_START_INDEX; int end = ASM_OPCODE_FIELDS_END_INDEX; for (int i = start; i <= end; i++) { Field field = Opcodes.class.getFields()[i]; try { OPCODES.put(field.getName(), field.getInt(null)); } catch (IllegalAccessException ignored) {} } } private static final int ASM_ACC_FIELDS_START_INDEX = 10; private static final int ASM_ACC_FIELDS_END_INDEX = 29; private static void initAccessTable() { int start = ASM_ACC_FIELDS_START_INDEX; int end = ASM_ACC_FIELDS_END_INDEX; for (int i = start; i <= end; i++) { Field field = Opcodes.class.getFields()[i]; try { ACC_MODS.put(field.getName(), field.getInt(null)); } catch (IllegalAccessException ignored) {} } } private BytecodeUtil() {} /** * Gets the xSTORE instruction for the given type. * @param type The type to be stored * @return The xSTORE instruction for the given type */ public static int storeInstruction(Type type) { return type.getOpcode(ISTORE); } /** * Gets the xLOAD instruction for the given type. * @param type The type to be loaded * @return The xSTORE instruction for the given type */ public static int loadInstruction(Type type) { return type.getOpcode(ILOAD); } /** * Gets the xADD instruction for the given type. * @param type The type to be added * @return The xADD instruction for the given type */ public static int addInstruction(Type type) { return type.getOpcode(IADD); } /** * Gets the xSUB instruction for the given type. * @param type The type to be subtracted * @return The xSUB instruction for the given type */ public static int subtractInstruction(Type type) { return type.getOpcode(ISUB); } /** * Gets the xMUL instruction for the given type. * @param type The type to be multiplied * @return The xMUL instruction for the given type */ public static int multiplyInstruction(Type type) { return type.getOpcode(IMUL); } /** * Gets the xDIV instruction for the given type. * @param type The type to be divided * @return The xDIV instruction for the given type */ public static int divisionInstruction(Type type) { return type.getOpcode(IDIV); } /** * Gets the xREM instruction for the given type. * @param type The type of the remainder operands * @return The xREM instruction for the given type */ public static int remainderInstruction(Type type) { return type.getOpcode(IREM); } /** * Gets the xNEG instruction for the given type. * @param type The type to be negated * @return The xNEG instruction for the given type */ public static int negateInstruction(Type type) { return type.getOpcode(INEG); } public static int fromOpcodeName(String name) { return OPCODES.getOrDefault(name, -1); } public static String toOpcodeName(int opcode) { for (Map.Entry<String, Integer> entry : OPCODES.entrySet()) { if (entry.getValue() == opcode) return entry.getKey(); } return null; } public static int fromAccName(String name) { return ACC_MODS.getOrDefault(name, -1); } public static Type fromSimpleName(String string) { return Type.getType(getDescriptor(string)); } public static Type fromSimpleName(String string, int dims) { return Type.getType(getDescriptor(string, dims)); } public static String getDescriptor(String str) { switch (str) { case "int": return "I"; case "bool": case "boolean": return "Z"; case "byte": return "B"; case "float": return "F"; case "double": return "D"; case "long": return "L"; case "char": return "C"; case "short": return "S"; case "void": return "V"; default: return "L" + str.replace('.', '/') + ";"; } } public static String getDescriptor(String str, int dims) { String base = getDescriptor(str); return StringUtil.repeat('[', dims) + base; } public static void pushInt(MethodVisitor method, int num) { // we can use a iconst_<i> if (num >= -1 && num <= 5) { int opcode; switch (num) { case -1: opcode = ICONST_M1; break; case 0: opcode = ICONST_0; break; case 1: opcode = ICONST_1; break; case 2: opcode = ICONST_2; break; case 3: opcode = ICONST_3; break; case 4: opcode = ICONST_4; break; case 5: opcode = ICONST_5; break; default: opcode = -1; } method.visitInsn(opcode); // in byte range, use bipush } else if (num >= Byte.MIN_VALUE && num <= Byte.MAX_VALUE) { method.visitIntInsn(BIPUSH, num); // in short range, use sipush } else if (num >= Short.MIN_VALUE && num <= Short.MAX_VALUE) { method.visitIntInsn(SIPUSH, num); // load constant } else { method.visitLdcInsn(num); } } public static void pushLong(MethodVisitor method, long num) { if (num == 0) { method.visitInsn(LCONST_0); } else if (num == 1) { method.visitInsn(LCONST_1); } else { method.visitLdcInsn(num); } } public static void pushFloat(MethodVisitor method, float num) { if (num == 0) { method.visitInsn(FCONST_0); } else if (num == 1) { method.visitInsn(FCONST_1); } else if (num == 2) { method.visitInsn(FCONST_2); } else { method.visitLdcInsn(num); } } public static void pushDouble(MethodVisitor method, double num) { if (num == 0) { method.visitInsn(DCONST_0); } else if (num == 1) { method.visitInsn(DCONST_1); } else { method.visitLdcInsn(num); } } public static void pushNull(MethodVisitor method) { method.visitInsn(ACONST_NULL); } private static Set<Type> BOXED_TYPES; static { Class[] classes = new Class[]{ Byte.class, Character.class, Short.class, Integer.class, Long.class, Float.class, Double.class }; BOXED_TYPES = new HashSet<>(classes.length); for (Class cls : classes) BOXED_TYPES.add(Type.getType(cls)); } public static boolean isBoxedType(Type type) { return BOXED_TYPES.contains(type); } public static boolean isPrimitive(Type type) { return type.getSort() >= Type.BOOLEAN && type.getSort() <= Type.DOUBLE; } public static boolean isIntegral(Type type) { return (type.getSort() >= Type.BYTE && type.getSort() <= Type.INT) || type.getSort() == Type.LONG; } public static boolean isNumeric(Type type) { return type.getSort() >= Type.CHAR && type.getSort() <= Type.DOUBLE; } public static Type numberType(Class<? extends Number> cls) { switch (cls.getSimpleName()) { case "Float": return Type.FLOAT_TYPE; case "Double": return Type.DOUBLE_TYPE; case "Integer": return Type.INT_TYPE; case "Long": return Type.LONG_TYPE; case "Short": return Type.SHORT_TYPE; case "Byte": return Type.BYTE_TYPE; default: return null; } } public static int castNumberOpcode(Type orig, Type target) { switch (orig.getSort()) { case Type.INT: switch (target.getSort()) { case Type.BYTE: return I2B; case Type.CHAR: return I2C; case Type.DOUBLE: return I2D; case Type.FLOAT: return I2F; case Type.LONG: return I2L; case Type.SHORT: return I2S; default: return -1; } case Type.LONG: switch (target.getSort()) { case Type.DOUBLE: return L2D; case Type.FLOAT: return L2F; case Type.INT: return L2I; default: return -1; } case Type.FLOAT: switch (target.getSort()) { case Type.DOUBLE: return F2D; case Type.INT: return F2I; case Type.LONG: return F2L; default: return -1; } case Type.DOUBLE: switch (target.getSort()) { case Type.FLOAT: return D2F; case Type.INT: return D2I; case Type.LONG: return D2L; default: return -1; } } return -1; } // byte, char, short, int, long, float, double private static final String PRIMITIVE_ORDER = "BCSIJFD"; public static int compareWidth(Type t1, Type t2) { if (!isNumeric(t1) || !isNumeric(t2)) return Integer.MAX_VALUE; int idx1 = PRIMITIVE_ORDER.indexOf(t1.getDescriptor()); int idx2 = PRIMITIVE_ORDER.indexOf(t2.getDescriptor()); if (idx1 > idx2) { return 1; } else if (idx1 < idx2) { return -1; } else { return 0; } } public static boolean canWidenTo(Type orig, Type target) { // both types must be numeric if (!isNumeric(orig) || !isNumeric(target)) return false; return PRIMITIVE_ORDER.indexOf(orig.getDescriptor()) <= PRIMITIVE_ORDER.indexOf(target.getDescriptor()); } public static Type widest(Type t1, Type t2) { // both types must be numeric if (!isNumeric(t1) || !isNumeric(t2)) return null; if (PRIMITIVE_ORDER.indexOf(t1.getDescriptor()) <= PRIMITIVE_ORDER.indexOf(t2.getDescriptor())) { return t2; } else { return t1; } } public static final Type STRING_TYPE = Type.getType(String.class); public static boolean isString(Type type) { return type.equals(STRING_TYPE); } public static boolean isObject(Type type) { return type.getSort() == Type.OBJECT; } private static final Type SB_TYPE = Type.getType(StringBuilder.class); public static void concatStringsBytecode(MethodVisitor method, BuildContext ctx, Expression... exprs) { // initialize StringBuilder instance method.visitTypeInsn(NEW, SB_TYPE.getInternalName()); method.visitInsn(DUP); method.visitMethodInsn( INVOKESPECIAL, SB_TYPE.getInternalName(), "<init>", Type.getMethodType(Type.VOID_TYPE).getDescriptor(), false // not an interface ); // append expressions for (Expression expr : exprs) { Type type = Type.getMethodType(SB_TYPE, expr.expressionType()); expr.generate(method, ctx); method.visitMethodInsn( INVOKEVIRTUAL, SB_TYPE.getInternalName(), "append", type.getDescriptor(), false // not an interface ); } // finally, call toString() method.visitMethodInsn( INVOKEVIRTUAL, SB_TYPE.getInternalName(), "toString", Type.getMethodType(STRING_TYPE).getDescriptor(), false ); } public static void createArray(MethodVisitor method, BuildContext ctx, Type type, Expression length) { length.generate(method, ctx); int typeOpcode = toArrayTypeOpcode(type); if (typeOpcode >= 0) { method.visitIntInsn(NEWARRAY, typeOpcode); } else { method.visitTypeInsn(ANEWARRAY, type.getInternalName()); } } public static int toArrayTypeOpcode(Type type) { switch (type.getSort()) { case Type.BOOLEAN: return T_BOOLEAN; case Type.BYTE: return T_BYTE; case Type.CHAR: return T_CHAR; case Type.SHORT: return T_SHORT; case Type.INT: return T_INT; case Type.LONG: return T_LONG; case Type.FLOAT: return T_FLOAT; case Type.DOUBLE: return T_DOUBLE; default: return -1; } } public static int negateCondition(int opcode) { switch (opcode) { case IFNULL: return IFNONNULL; case IFNONNULL: return IFNULL; case IFEQ: return IFNE; case IFNE: return IFEQ; case IFLT: return IFGE; case IFLE: return IFGT; case IFGT: return IFLE; case IFGE: return IFLT; case IF_ICMPEQ: return IF_ICMPNE; case IF_ICMPNE: return IF_ICMPEQ; case IF_ICMPLT: return IF_ICMPGE; case IF_ICMPLE: return IF_ICMPGT; case IF_ICMPGT: return IF_ICMPLE; case IF_ICMPGE: return IF_ICMPLT; case IF_ACMPEQ: return IF_ACMPNE; case IF_ACMPNE: return IF_ACMPEQ; default: return -1; } } public static int compareOpcode(Type type) { switch (type.getSort()) { case Type.DOUBLE: return DCMPL; case Type.FLOAT: return FCMPL; case Type.LONG: return LCMP; default: return -1; } } // Type#getOpcode doesn't work on xRETURN // opcodes for some reason... public static int returnOpcode(Type type) { switch (type.getSort()) { case Type.INT: return IRETURN; case Type.LONG: return LRETURN; case Type.FLOAT: return FRETURN; case Type.DOUBLE: return DRETURN; case Type.OBJECT: return ARETURN; case Type.VOID: return RETURN; default: return IRETURN; } } public static int arrayLoadOpcode(Type type) { switch (type.getSort()) { case Type.BYTE: return BALOAD; case Type.CHAR: return CALOAD; case Type.SHORT: return SALOAD; case Type.INT: return IALOAD; case Type.LONG: return LALOAD; case Type.FLOAT: return FALOAD; case Type.DOUBLE: return DALOAD; case Type.ARRAY: case Type.OBJECT: return AALOAD; default: return -1; } } public static int arrayStoreOpcode(Type type) { switch (type.getSort()) { case Type.BYTE: return BASTORE; case Type.CHAR: return CASTORE; case Type.SHORT: return SASTORE; case Type.INT: return IASTORE; case Type.LONG: return LALOAD; case Type.FLOAT: return FASTORE; case Type.DOUBLE: return DASTORE; case Type.ARRAY: case Type.OBJECT: return AASTORE; default: return -1; } } public static Type componentType(Type type) { // not an array if (type.getDimensions() < 1) return null; String descriptor = type.getDescriptor(); return Type.getType(descriptor.substring(1, descriptor.length())); } public static Type toBoxedType(Type type) { switch (type.getSort()) { case Type.BYTE: return Type.getType(Byte.class); case Type.CHAR: return Type.getType(Character.class); case Type.SHORT: return Type.getType(Short.class); case Type.INT: return Type.getType(Integer.class); case Type.LONG: return Type.getType(Long.class); case Type.FLOAT: return Type.getType(Float.class); case Type.DOUBLE: return Type.getType(Double.class); default: return null; } } public static Type toUnboxedType(Type type) { String name = type.getClassName(); switch (name) { case "java.lang.Byte": return Type.BYTE_TYPE; case "java.lang.Character": return Type.CHAR_TYPE; case "java.lang.Short": return Type.SHORT_TYPE; case "java.lang.Integer": return Type.INT_TYPE; case "java.lang.Long": return Type.LONG_TYPE; case "java.lang.Float": return Type.FLOAT_TYPE; case "java.lang.Double": return Type.DOUBLE_TYPE; default: return null; } } }