Skip to content

Commit 3325d08

Browse files
committed
Merge branch 'dynamic-functions'
2 parents ec48d89 + a0b2ba4 commit 3325d08

19 files changed

+400
-144
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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+
}

src/main/java/org/byteskript/skript/compiler/CommonTypes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import mx.kenzie.foundation.Type;
1010
import org.byteskript.skript.api.Event;
1111
import org.byteskript.skript.api.Referent;
12+
import org.byteskript.skript.runtime.type.AtomicVariable;
1213
import org.byteskript.skript.runtime.type.Executable;
1314

1415
import java.io.InputStream;
@@ -46,6 +47,7 @@ public class CommonTypes {
4647
public static final Type SUPPLIER = new Type(Supplier.class);
4748
public static final Type EXECUTABLE = new Type(Executable.class);
4849

50+
public static final Type ATOMIC = new Type(AtomicVariable.class);
4951
public static final Type METHOD = new Type(Method.class);
5052
public static final Type FIELD = new Type(Field.class);
5153
public static final Type INPUT_STREAM = new Type(InputStream.class);

src/main/java/org/byteskript/skript/compiler/Context.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
import org.byteskript.skript.api.syntax.Section;
1616
import org.byteskript.skript.compiler.structure.*;
1717

18-
import java.util.ArrayList;
19-
import java.util.Collection;
20-
import java.util.List;
21-
import java.util.Map;
18+
import java.util.*;
2219
import java.util.function.Consumer;
2320

2421
public abstract class Context {
@@ -162,14 +159,16 @@ public void destroySection() {
162159
}
163160
}
164161

165-
public abstract boolean hasFunction(String name);
162+
public abstract boolean hasFunction(String name, int arguments);
166163

167-
public abstract Function getFunction(String name);
164+
public abstract Function getFunction(String name, int arguments);
168165

169-
public Function getDefaultFunction(String name) {
170-
final Function function = getFunction(name);
171-
if (function == null) return new Function(name, getType());
172-
return function;
166+
public Function getDefaultFunction(String name, int arguments) {
167+
final Function function = getFunction(name, arguments);
168+
if (function != null) return function;
169+
final Type[] types = new Type[arguments];
170+
Arrays.fill(types, CommonTypes.OBJECT); // assert all types are actually object :)
171+
return new Function(name, getType(), CommonTypes.OBJECT, types);
173172
}
174173

175174
public abstract void registerFunction(Function function);

src/main/java/org/byteskript/skript/compiler/FileContext.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,9 @@ private void addSkriptFunctions() {
6060
final Class<?> skript = Class.forName("skript");
6161
final Type owner = new Type(skript);
6262
for (final Method method : Class.forName("skript").getMethods()) {
63-
if (method.getReturnType() != Object.class) continue;
64-
if (!Modifier.isPublic(method.getModifiers())) continue;
6563
if (!Modifier.isStatic(method.getModifiers())) continue;
66-
this.functions.add(new Function(method.getName(), owner));
64+
if (!Modifier.isPublic(method.getModifiers())) continue;
65+
this.functions.add(new Function(method.getName(), owner, CommonTypes.OBJECT, Type.array(CommonTypes.OBJECT, method.getParameterCount()), new Type(method.getReturnType()), Type.of(method.getParameterTypes())));
6766
}
6867
} catch (Throwable ex) {
6968
throw new RuntimeException("Unable to load Skript functions.", ex);
@@ -337,17 +336,17 @@ public HandlerType getHandlerMode() {
337336
}
338337

339338
@Override
340-
public boolean hasFunction(String name) {
339+
public boolean hasFunction(String name, int arguments) {
341340
for (Function function : functions) {
342-
if (function.name().equals(name)) return true;
341+
if (function.name().equals(name) && function.arguments().length == arguments) return true;
343342
}
344343
return false;
345344
}
346345

347346
@Override
348-
public Function getFunction(String name) {
347+
public Function getFunction(String name, int arguments) {
349348
for (Function function : functions) {
350-
if (function.name().equals(name)) return function;
349+
if (function.name().equals(name) && function.arguments().length == arguments) return function;
351350
}
352351
return null;
353352
}
@@ -364,6 +363,7 @@ public void registerFunction(Function function) {
364363

365364
@Override
366365
public Function assertDefaultLocalFunction(String name) {
366+
if (true) throw new RuntimeException("i need to know what's using this"); // todo
367367
return new Function(name, type);
368368
}
369369
}

src/main/java/org/byteskript/skript/compiler/SkriptLangSpec.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ public Collection<PostCompileClass> getRuntime() {
266266
runtime.add(this.getData(Library.class));
267267
runtime.add(this.getData(Class.forName("skript")));
268268
runtime.add(this.getData(SkriptCompiler.class));
269+
runtime.add(this.getData(BridgeCompiler.class));
269270
runtime.add(this.getData(Compiler.class));
270271
runtime.add(this.getData(ScriptRunner.class));
271272
runtime.add(this.getData(SimpleThrottleController.class));

src/main/java/org/byteskript/skript/compiler/structure/Function.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,44 @@
66

77
package org.byteskript.skript.compiler.structure;
88

9+
import mx.kenzie.foundation.MethodErasure;
910
import mx.kenzie.foundation.Type;
1011
import mx.kenzie.foundation.WriteInstruction;
1112
import org.byteskript.skript.compiler.CommonTypes;
13+
import org.byteskript.skript.runtime.internal.Bootstrapper;
1214

13-
import java.util.Arrays;
14-
15-
public record Function(String name, Type provider) {
15+
public record Function(String name, Type provider, Type returnType, Type[] arguments, Type result, Type[] parameters) {
16+
17+
public Function(String name, Type provider, Type returnType, Type... arguments) {
18+
this(name, provider, returnType, arguments, returnType, arguments);
19+
}
20+
21+
public Function(String name, Type provider) {
22+
this(name, provider, CommonTypes.OBJECT);
23+
}
24+
25+
public Function(Type provider, MethodErasure erasure) {
26+
this(erasure.name(), provider, erasure.returnType(), erasure.parameterTypes(), erasure.returnType(), erasure.parameterTypes());
27+
}
28+
29+
public WriteInstruction invoke(String source) {
30+
final org.objectweb.asm.Type owner = org.objectweb.asm.Type.getType(provider.descriptorString());
31+
final org.objectweb.asm.Type[] types = convert(parameters);
32+
final org.objectweb.asm.Type blob = org.objectweb.asm.Type.getType(void.class);
33+
return WriteInstruction.invokeDynamic(CommonTypes.OBJECT, name, arguments, Bootstrapper.getBootstrapFunction(), source, owner, org.objectweb.asm.Type.getMethodDescriptor(blob, types));
34+
// return WriteInstruction.invokeStatic(provider, CommonTypes.OBJECT, name, types);
35+
}
36+
37+
private org.objectweb.asm.Type convert(Type type) {
38+
return org.objectweb.asm.Type.getType(type.descriptorString());
39+
}
1640

17-
public WriteInstruction invoke(int arguments) {
18-
final Type[] types = new Type[arguments];
19-
Arrays.fill(types, CommonTypes.OBJECT); // assert all types are actually object :)
20-
return WriteInstruction.invokeStatic(provider, CommonTypes.OBJECT, name, types);
41+
private org.objectweb.asm.Type[] convert(Type... arguments) {
42+
final org.objectweb.asm.Type[] types = new org.objectweb.asm.Type[arguments.length];
43+
for (int i = 0; i < arguments.length; i++) {
44+
types[i] = org.objectweb.asm.Type.getType(arguments[i].descriptorString());
45+
}
46+
return types;
2147
}
2248

2349
}

src/main/java/org/byteskript/skript/error/ScriptRuntimeError.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ protected ScriptRuntimeError(String message, Throwable cause, boolean enableSupp
3030

3131
@Override
3232
public synchronized Throwable fillInStackTrace() {
33-
return this;
33+
return super.fillInStackTrace();
3434
}
3535
}

src/main/java/org/byteskript/skript/lang/syntax/entry/Trigger.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import org.byteskript.skript.compiler.structure.TriggerTree;
1919
import org.byteskript.skript.lang.element.StandardElements;
2020
import org.byteskript.skript.runtime.type.AtomicVariable;
21-
import org.objectweb.asm.Opcodes;
2221

2322
public class Trigger extends Section {
2423

@@ -80,15 +79,7 @@ private WriteInstruction prepareVariables(TriggerTree context) {
8079
}
8180
}
8281
if (variable.parameter) {
83-
if (variable.atomic) {
84-
visitor.visitVarInsn(Opcodes.ALOAD, i);
85-
wrap.accept(writer, visitor);
86-
visitor.visitVarInsn(Opcodes.ASTORE, i);
87-
} else {
88-
visitor.visitVarInsn(Opcodes.ALOAD, i);
89-
unwrap.accept(writer, visitor);
90-
visitor.visitVarInsn(Opcodes.ASTORE, i);
91-
}
82+
// this is handled by the dynamic callsite now
9283
}
9384
i++;
9485
}

0 commit comments

Comments
 (0)