|
| 1 | +/* |
| 2 | + * Copyright (c) 2021 ByteSkript org (Moderocky) |
| 3 | + * View the full licence information and permissions: |
| 4 | + * https://github.com/Moderocky/ByteSkript/blob/master/LICENSE |
| 5 | + */ |
| 6 | + |
| 7 | +package org.byteskript.skript.compiler; |
| 8 | + |
| 9 | +import org.byteskript.skript.error.ScriptRuntimeError; |
| 10 | +import org.byteskript.skript.runtime.Skript; |
| 11 | +import org.byteskript.skript.runtime.type.AtomicVariable; |
| 12 | +import org.objectweb.asm.ClassWriter; |
| 13 | +import org.objectweb.asm.MethodVisitor; |
| 14 | +import org.objectweb.asm.Type; |
| 15 | + |
| 16 | +import java.lang.invoke.*; |
| 17 | +import java.lang.reflect.Method; |
| 18 | +import java.util.Random; |
| 19 | + |
| 20 | +import static org.objectweb.asm.Opcodes.*; |
| 21 | + |
| 22 | +/** |
| 23 | + * The function call-site class compiler. |
| 24 | + * This cannot use {@link mx.kenzie.foundation} because it is required for |
| 25 | + * use in environments where BSC is not available. |
| 26 | + */ |
| 27 | +public class BridgeCompiler { |
| 28 | + |
| 29 | + protected final MethodHandles.Lookup lookup; |
| 30 | + protected final String owner; |
| 31 | + protected final MethodType source; |
| 32 | + protected final Method target; |
| 33 | + protected final String location; |
| 34 | + protected Class<?> generated; |
| 35 | + |
| 36 | + public BridgeCompiler(MethodHandles.Lookup lookup, String owner, MethodType source, Method target) { |
| 37 | + this.lookup = lookup; |
| 38 | + this.owner = owner; |
| 39 | + this.source = source; |
| 40 | + this.target = target; |
| 41 | + this.location = owner + "$" + "Bridge" + new Random().nextInt(100000, 999999); |
| 42 | + } |
| 43 | + |
| 44 | + public Class<?> createClass() |
| 45 | + throws IllegalAccessException { |
| 46 | + final Class<?>[] arguments = source.parameterArray(); |
| 47 | + final Class<?>[] parameters = target.getParameterTypes(); |
| 48 | + final Class<?> expected = source.returnType(); |
| 49 | + final Class<?> result = target.getReturnType(); |
| 50 | + if (arguments.length != parameters.length) |
| 51 | + throw new ScriptRuntimeError("Function argument count did not match target parameter count."); |
| 52 | + final ClassWriter writer = new ClassWriter(0); |
| 53 | + writer.visit(Skript.JAVA_VERSION, 0x0001 | 0x1000, location, null, "java/lang/Object", null); |
| 54 | + final MethodVisitor visitor; |
| 55 | + final Type[] types = new Type[arguments.length]; |
| 56 | + for (int i = 0; i < arguments.length; i++) types[i] = Type.getType(arguments[i]); |
| 57 | + visitor = writer.visitMethod(0x0001 | 0x0008 | 0x0040 | 0x1000, "bridge", Type.getMethodDescriptor(Type.getType(expected), types), null, null); |
| 58 | + visitor.visitCode(); |
| 59 | + for (int i = 0; i < arguments.length; i++) { // assume no fat arguments ? |
| 60 | + final Class<?> argument = arguments[i]; |
| 61 | + final Class<?> parameter = parameters[i]; |
| 62 | + visitor.visitVarInsn(20 + this.instructionOffset(argument), i); |
| 63 | + this.boxAtomic(visitor, parameter); |
| 64 | + visitor.visitTypeInsn(CHECKCAST, Type.getInternalName(this.getWrapperType(parameter))); |
| 65 | + this.unbox(visitor, parameter); |
| 66 | + } |
| 67 | + this.invoke(visitor); |
| 68 | + this.box(visitor, result); |
| 69 | + visitor.visitTypeInsn(CHECKCAST, Type.getInternalName(this.getWrapperType(expected))); |
| 70 | + visitor.visitInsn(171 + this.instructionOffset(expected)); |
| 71 | + visitor.visitMaxs(Math.max(parameters.length + 1 + this.wideIndexOffset(parameters, result), 1), arguments.length); |
| 72 | + visitor.visitEnd(); |
| 73 | + writer.visitEnd(); |
| 74 | + this.generated = lookup.defineClass(writer.toByteArray()); |
| 75 | + return generated; |
| 76 | + } |
| 77 | + |
| 78 | + public CallSite getCallSite() |
| 79 | + throws NoSuchMethodException, IllegalAccessException { |
| 80 | + final MethodHandle handle = lookup.findStatic(generated, "bridge", source); |
| 81 | + return new ConstantCallSite(handle); |
| 82 | + } |
| 83 | + |
| 84 | + //region Utilities |
| 85 | + protected void invoke(MethodVisitor visitor) { |
| 86 | + final boolean special = target.getDeclaringClass().isInterface(); |
| 87 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(target.getDeclaringClass()), target.getName(), Type.getMethodDescriptor(target), special); |
| 88 | + } |
| 89 | + |
| 90 | + protected void doTypeConversion(MethodVisitor visitor, Class<?> from, Class<?> to) { |
| 91 | + if (from == to) return; |
| 92 | + if (from == void.class || to == void.class) return; |
| 93 | + if (from.isPrimitive() && to.isPrimitive()) { |
| 94 | + final int opcode; |
| 95 | + if (from == float.class) { |
| 96 | + if (to == double.class) opcode = F2D; |
| 97 | + else if (to == long.class) opcode = F2L; |
| 98 | + else opcode = F2I; |
| 99 | + } else if (from == double.class) { |
| 100 | + if (to == float.class) opcode = D2F; |
| 101 | + else if (to == long.class) opcode = D2L; |
| 102 | + else opcode = D2I; |
| 103 | + } else if (from == long.class) { |
| 104 | + if (to == float.class) opcode = L2F; |
| 105 | + else if (to == double.class) opcode = L2D; |
| 106 | + else opcode = L2I; |
| 107 | + } else { |
| 108 | + if (to == float.class) opcode = I2F; |
| 109 | + else if (to == double.class) opcode = I2D; |
| 110 | + else if (to == byte.class) opcode = I2B; |
| 111 | + else if (to == short.class) opcode = I2S; |
| 112 | + else if (to == char.class) opcode = I2C; |
| 113 | + else opcode = I2L; |
| 114 | + } |
| 115 | + visitor.visitInsn(opcode); |
| 116 | + } else if (from.isPrimitive() ^ to.isPrimitive()) { |
| 117 | + throw new IllegalArgumentException("Type wrapping is currently unsupported due to side-effects: '" + from.getSimpleName() + "' -> '" + to.getSimpleName() + "'"); |
| 118 | + } else visitor.visitTypeInsn(CHECKCAST, Type.getInternalName(to)); |
| 119 | + } |
| 120 | + |
| 121 | + protected Class<?> getWrapperType(Class<?> primitive) { |
| 122 | + if (primitive == byte.class) return Byte.class; |
| 123 | + if (primitive == short.class) return Short.class; |
| 124 | + if (primitive == int.class) return Integer.class; |
| 125 | + if (primitive == long.class) return Long.class; |
| 126 | + if (primitive == float.class) return Float.class; |
| 127 | + if (primitive == double.class) return Double.class; |
| 128 | + if (primitive == boolean.class) return Boolean.class; |
| 129 | + if (primitive == void.class) return Void.class; |
| 130 | + return primitive; |
| 131 | + } |
| 132 | + |
| 133 | + protected void boxAtomic(MethodVisitor visitor, Class<?> parameter) { |
| 134 | + if (parameter == AtomicVariable.class) |
| 135 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(AtomicVariable.class), "wrap", "(Ljava/lang/Object;)" + Type.getDescriptor(AtomicVariable.class), false); |
| 136 | + else |
| 137 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(AtomicVariable.class), "unwrap", "(Ljava/lang/Object;)Ljava/lang/Object;", false); |
| 138 | + } |
| 139 | + |
| 140 | + protected void unbox(MethodVisitor visitor, Class<?> parameter) { |
| 141 | + if (parameter == byte.class) |
| 142 | + visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Byte.class), "byteValue", "()B", false); |
| 143 | + if (parameter == short.class) |
| 144 | + visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Short.class), "shortValue", "()S", false); |
| 145 | + if (parameter == int.class) |
| 146 | + visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", "()I", false); |
| 147 | + if (parameter == long.class) |
| 148 | + visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Long.class), "longValue", "()J", false); |
| 149 | + if (parameter == float.class) |
| 150 | + visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Float.class), "floatValue", "()F", false); |
| 151 | + if (parameter == double.class) |
| 152 | + visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Double.class), "doubleValue", "()D", false); |
| 153 | + if (parameter == boolean.class) |
| 154 | + visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Boolean.class), "booleanValue", "()Z", false); |
| 155 | + } |
| 156 | + |
| 157 | + protected void box(MethodVisitor visitor, Class<?> value) { |
| 158 | + if (value == byte.class) |
| 159 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Byte.class), "valueOf", "(B)Ljava/lang/Byte;", false); |
| 160 | + if (value == short.class) |
| 161 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Short.class), "valueOf", "(S)Ljava/lang/Short;", false); |
| 162 | + if (value == int.class) |
| 163 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", "(I)Ljava/lang/Integer;", false); |
| 164 | + if (value == long.class) |
| 165 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Long.class), "valueOf", "(J)Ljava/lang/Long;", false); |
| 166 | + if (value == float.class) |
| 167 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Float.class), "valueOf", "(F)Ljava/lang/Float;", false); |
| 168 | + if (value == double.class) |
| 169 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Double.class), "valueOf", "(D)Ljava/lang/Double;", false); |
| 170 | + if (value == boolean.class) |
| 171 | + visitor.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Boolean.class), "valueOf", "(Z)Ljava/lang/Boolean;", false); |
| 172 | + if (value == void.class) |
| 173 | + visitor.visitInsn(ACONST_NULL); |
| 174 | + } |
| 175 | + |
| 176 | + protected int wideIndexOffset(Class<?>[] params, Class<?> ret) { |
| 177 | + int i = 0; |
| 178 | + for (Class<?> param : params) { |
| 179 | + i += wideIndexOffset(param); |
| 180 | + } |
| 181 | + return Math.max(i, wideIndexOffset(ret)); |
| 182 | + } |
| 183 | + |
| 184 | + protected int wideIndexOffset(Class<?> thing) { |
| 185 | + if (thing == long.class || thing == double.class) return 1; |
| 186 | + return 0; |
| 187 | + } |
| 188 | + |
| 189 | + protected int instructionOffset(Class<?> type) { |
| 190 | + if (type == int.class) return 1; |
| 191 | + if (type == boolean.class) return 1; |
| 192 | + if (type == byte.class) return 1; |
| 193 | + if (type == short.class) return 1; |
| 194 | + if (type == long.class) return 2; |
| 195 | + if (type == float.class) return 3; |
| 196 | + if (type == double.class) return 4; |
| 197 | + if (type == void.class) return 6; |
| 198 | + return 5; |
| 199 | + } |
| 200 | + //endregion |
| 201 | + |
| 202 | +} |
0 commit comments