Skip to content

Commit 0d5f8f7

Browse files
author
Hakan Raberg
committed
Creating operators implementation via ASM, experimental. Basic Java interop kinda works again.
1 parent 845a5ac commit 0d5f8f7

File tree

3 files changed

+172
-48
lines changed

3 files changed

+172
-48
lines changed

build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/bin/bash -e
22

33
mvn clean
44

src/shen/Shen.java

Lines changed: 164 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static java.lang.ClassLoader.getSystemClassLoader;
2929
import static java.lang.Double.*;
3030
import static java.lang.Math.floorMod;
31+
import static java.lang.Math.toIntExact;
3132
import static java.lang.String.format;
3233
import static java.lang.System.*;
3334
import static java.lang.invoke.MethodHandleProxies.asInterfaceInstance;
@@ -49,7 +50,8 @@
4950
import static jdk.internal.org.objectweb.asm.ClassReader.SKIP_DEBUG;
5051
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
5152
import static jdk.internal.org.objectweb.asm.Type.*;
52-
import static shen.Shen.Compiler.typeHint;
53+
import static jdk.internal.org.objectweb.asm.commons.GeneratorAdapter.*;
54+
import static shen.Shen.Compiler.*;
5355
import static shen.Shen.Cons.toCons;
5456
import static shen.Shen.KLReader.lines;
5557
import static shen.Shen.KLReader.read;
@@ -91,53 +93,168 @@ public static void main(String... args) throws Throwable {
9193
interface LLPredicate { boolean test(long a, long b); }
9294
interface Invokable { MethodHandle invoker() throws Exception; }
9395

94-
public static class Numbers {
96+
public static class Numbers implements Opcodes {
9597
static final long tag = 1, real = 0, integer = 1;
96-
9798
static final Set<Symbol> operators = new HashSet<>();
9899

100+
// longs are either 63 bit signed integers or doubleToLongBits with bit 0 used as tag, 0 = double, 1 = long.
101+
// Java: 5ms, Shen.java: 10ms, Boxed Java: 15ms. Which ever branch that starts will be faster for some reason.
99102
static {
100-
// longs are either 63 bit signed integers or doubleToLongBits with bit 0 used as tag, 0 = double, 1 = long.
101-
// Java: 5ms, Shen.java: 10ms, Boxed Java: 15ms. Which ever branch that starts will be faster for some reason.
102-
op("+", (l, r) -> (tag & l) == 0 || (tag & r) == 0
103-
? ~tag & doubleToRawLongBits(((tag & l) == 0 ? longBitsToDouble(l) : l >> tag) + ((tag & r) == 0 ? longBitsToDouble(r) : r >> tag))
104-
: l + (r & ~tag));
105-
op("-", (l, r) -> (tag & l) == 0 || (tag & r) == 0
106-
? ~tag & doubleToRawLongBits(((tag & l) == 0 ? longBitsToDouble(l) : l >> tag) - ((tag & r) == 0 ? longBitsToDouble(r) : r >> tag))
107-
: l - (r & ~tag));
108-
op("*", (l, r) -> (tag & l) == 0 || (tag & r) == 0
109-
? ~tag & doubleToRawLongBits(((tag & l) == 0 ? longBitsToDouble(l) : l >> tag) * ((tag & r) == 0 ? longBitsToDouble(r) : r >> tag))
110-
: (l & ~tag) * (r & ~tag) >> tag | tag);
111-
op("/", (l, r) -> {
112-
if (r == real || r == tag) throw new ArithmeticException("Division by zero");
113-
return ~tag & doubleToRawLongBits(((tag & l) == real
114-
? longBitsToDouble(l) : l >> tag) / ((tag & r) == real
115-
? longBitsToDouble(r) : r >> tag));
116-
});
117-
op("<", (l, r) -> (tag & l) == 0 || (tag & r) == 0
118-
? ((tag & l) == 0 ? longBitsToDouble(l) : l >> tag) < ((tag & r) == 0 ? longBitsToDouble(r) : r >> tag) : l < r);
119-
op("<=", (l, r) -> (tag & l) == 0 || (tag & r) == 0
120-
? ((tag & l) == 0 ? longBitsToDouble(l) : l >> tag) <= ((tag & r) == 0 ? longBitsToDouble(r) : r >> tag) : l <= r);
121-
op(">", (l, r) -> (tag & l) == 0 || (tag & r) == 0
122-
? ((tag & l) == 0 ? longBitsToDouble(l) : l >> tag) > ((tag & r) == 0 ? longBitsToDouble(r) : r >> tag) : l > r);
123-
op(">=", (l, r) -> (tag & l) == 0 || (tag & r) == 0
124-
? ((tag & l) == 0 ? longBitsToDouble(l) : l >> tag) >= ((tag & r) == 0 ? longBitsToDouble(r) : r >> tag) : l >= r);
103+
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
104+
cw.visit(V1_7, ACC_PUBLIC | ACC_FINAL, "shen/Shen$Operators", null, getInternalName(Object.class), null);
105+
106+
binaryOp(cw, "+", ADD);
107+
binaryOp(cw, "-", SUB);
108+
binaryOp(cw, "*", MUL);
109+
binaryOp(cw, "/", realOp(DIV), integerDivision());
110+
binaryComp(cw, "<", LT);
111+
binaryComp(cw, "<=", LE);
112+
binaryComp(cw, ">", GT);
113+
binaryComp(cw, ">=", GE);
114+
115+
register(loader.loadClass(cw.toByteArray()), Numbers::op);
116+
}
117+
118+
static Consumer<GeneratorAdapter> integerOp(int op) {
119+
return mv -> toInteger(mv, op);
120+
}
121+
122+
static Consumer<GeneratorAdapter> realOp(int op) {
123+
return mv -> toReal(mv, op);
124+
}
125+
126+
static Consumer<GeneratorAdapter> integerDivision() {
127+
return mv -> {
128+
Label notZero = new Label();
129+
mv.dup2();
130+
mv.visitInsn(L2I);
131+
mv.ifZCmp(IFNE, notZero);
132+
mv.newInstance(getType(ArithmeticException.class));
133+
mv.dup();
134+
mv.push("Division by zero");
135+
mv.invokeConstructor(getType(ArithmeticException.class), method("<init>", desc(void.class, String.class)));
136+
mv.throwException();
137+
mv.visitLabel(notZero);
138+
mv.visitInsn(L2D);
139+
mv.swap(DOUBLE_TYPE, LONG_TYPE);
140+
mv.visitInsn(L2D);
141+
mv.swap(DOUBLE_TYPE, DOUBLE_TYPE);
142+
toReal(mv, DIV);
143+
};
144+
}
145+
146+
static void toInteger(GeneratorAdapter mv, int op) {
147+
mv.math(op, LONG_TYPE);
148+
mv.push((int) tag);
149+
mv.visitInsn(LSHL);
150+
mv.push(integer);
151+
mv.visitInsn(LOR);
152+
}
153+
154+
static void toReal(GeneratorAdapter mv, int op) {
155+
mv.math(op, DOUBLE_TYPE);
156+
mv.invokeStatic(getType(Double.class), method("doubleToRawLongBits", desc(long.class, double.class)));
157+
mv.push(~integer);
158+
mv.visitInsn(LAND);
159+
}
160+
161+
static void binaryComp(ClassWriter cw, String op, int test) {
162+
binaryOp(cw, op, boolean.class, comparison(DOUBLE_TYPE, test), comparison(LONG_TYPE, test));
163+
}
164+
165+
static Consumer<GeneratorAdapter> comparison(Type type, int test) {
166+
return mv -> {
167+
Label _else = new Label();
168+
mv.ifCmp(type, test, _else);
169+
mv.push(false);
170+
mv.returnValue();
171+
mv.visitLabel(_else);
172+
mv.push(true);
173+
mv.returnValue();
174+
};
175+
}
176+
177+
static void binaryOp(ClassWriter cw, String op, int instruction) {
178+
binaryOp(cw, op, long.class, realOp(instruction), integerOp(instruction));
179+
}
180+
181+
static void binaryOp(ClassWriter cw, String op, Consumer<GeneratorAdapter> realOp, Consumer<GeneratorAdapter> longOp) {
182+
binaryOp(cw, op, long.class, realOp, longOp);
183+
}
184+
185+
static void binaryOp(ClassWriter cw, String op, Class<?> returnType, Consumer<GeneratorAdapter> realOp,
186+
Consumer<GeneratorAdapter> integerOp) {
187+
GeneratorAdapter mv = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
188+
method(toBytecodeName(op), desc(returnType, long.class, long.class)), null, null, cw);
189+
190+
isInteger(mv, 0);
191+
Label argOneIsLong = new Label();
192+
mv.ifZCmp(IFNE, argOneIsLong);
193+
asDouble(mv, 0);
194+
isInteger(mv, 1);
195+
Label argTwoIsLong = new Label();
196+
mv.ifZCmp(IFNE, argTwoIsLong);
197+
asDouble(mv, 1);
198+
Label doubleOperation = new Label();
199+
mv.goTo(doubleOperation);
200+
mv.visitLabel(argTwoIsLong);
201+
asLong(mv, 1);
202+
mv.visitInsn(L2D);
203+
mv.goTo(doubleOperation);
204+
mv.visitLabel(argOneIsLong);
205+
isInteger(mv, 1);
206+
Label longOperation = new Label();
207+
mv.ifZCmp(IFNE, longOperation);
208+
asLong(mv, 0);
209+
mv.visitInsn(L2D);
210+
asDouble(mv, 1);
211+
mv.visitLabel(doubleOperation);
212+
realOp.accept(mv);
213+
mv.returnValue();
214+
mv.visitLabel(longOperation);
215+
asLong(mv, 0);
216+
asLong(mv, 1);
217+
integerOp.accept(mv);
218+
mv.returnValue();
219+
mv.endMethod();
125220
}
126221

127-
static void op(String name, LongBinaryOperator op) {
128-
intern(name).fn.add(findSAM(op));
129-
operators.add(intern(name));
222+
static void asDouble(GeneratorAdapter mv, int arg) {
223+
mv.loadArg(arg);
224+
mv.invokeStatic(getType(Double.class), method("longBitsToDouble", desc(double.class, long.class)));
130225
}
131226

132-
static void op(String name, LLPredicate op) {
133-
intern(name).fn.add(findSAM(op));
134-
operators.add(intern(name));
227+
static void asLong(GeneratorAdapter mv, int arg) {
228+
mv.loadArg(arg);
229+
mv.push((int) tag);
230+
mv.visitInsn(LSHR);
231+
}
232+
233+
static void isInteger(GeneratorAdapter mv, int arg) {
234+
mv.loadArg(arg);
235+
mv.visitInsn(L2I);
236+
mv.push((int) tag);
237+
mv.visitInsn(IAND);
238+
}
239+
240+
static void op(Method op) {
241+
try {
242+
Symbol symbol = intern(toSourceName(op.getName()));
243+
symbol.fn.add(lookup.unreflect(op));
244+
operators.add(symbol);
245+
} catch (IllegalAccessException e) {
246+
throw uncheck(e);
247+
}
135248
}
136249

137250
static Object maybeNumber(Object o) {
138251
return o instanceof Long ? asNumber((Long) o) : o;
139252
}
140253

254+
public static long number(Number n) {
255+
return n instanceof Double ? real(n.doubleValue()) : integer(n.longValue());
256+
}
257+
141258
static long real(double d) {
142259
return ~tag & doubleToLongBits(d);
143260
}
@@ -150,7 +267,11 @@ static double asDouble(long l) {
150267
return isInteger(l) ? l >> tag : longBitsToDouble(l);
151268
}
152269

153-
static Number asNumber(long fp) { //noinspection RedundantCast
270+
public static int asInt(long l) {
271+
return toIntExact(asNumber(l).longValue());
272+
}
273+
274+
public static Number asNumber(long fp) { //noinspection RedundantCast
154275
return isInteger(fp) ? (Number) (fp >> tag) : (Number) longBitsToDouble(fp);
155276
}
156277

@@ -651,6 +772,8 @@ public static class RT {
651772
static final MethodHandle
652773
link = mh(RT.class, "link"), proxy = mh(RT.class, "proxy"),
653774
checkClass = mh(RT.class, "checkClass"), toIntExact = mh(Math.class, "toIntExact"),
775+
asNumber = mh(Numbers.class, "asNumber"), number = mh(Numbers.class, "number"),
776+
asInt = mh(Numbers.class, "asInt"), toList = mh(Cons.class, "toList"),
654777
partial = mh(RT.class, "partial"), arityCheck = mh(RT.class, "arityCheck");
655778

656779
public static Object link(MutableCallSite site, String name, Object... args) throws Throwable {
@@ -846,10 +969,11 @@ static MethodHandle filterJavaTypes(MethodHandle method) throws IllegalAccessExc
846969
filters[i] = proxy.bindTo(findSAM(method.type().parameterType(i)))
847970
.asType(methodType(method.type().parameterType(i), Object.class));
848971
else if (canCast(method.type().parameterType(i), int.class))
849-
filters[i] = toIntExact.asType(methodType(method.type().parameterType(i), Object.class));
850-
if (canCast(method.type().returnType(), int.class))
851-
method = method.asType(method.type()
852-
.changeReturnType(method.type().returnType().isPrimitive() ? long.class : Long.class));
972+
filters[i] = asInt.asType(methodType(method.type().parameterType(i), Object.class));
973+
else if (canCast(method.type().wrap().parameterType(i), Number.class))
974+
filters[i] = asNumber.asType(methodType(method.type().parameterType(i), Object.class));
975+
if (canCast(method.type().wrap().returnType(), Number.class))
976+
method = filterReturnValue(method, number.asType(methodType(long.class, method.type().returnType())));
853977
return filterArguments(method, 0, filters);
854978
}
855979

@@ -1615,7 +1739,7 @@ static <T> List<T> rest(List<T> coll) {
16151739

16161740
static RuntimeException uncheck(Throwable t) {
16171741
return uncheckAndThrow(t);
1618-
}
1742+
}
16191743

16201744
static <T extends Throwable> T uncheckAndThrow(Throwable t) throws T { //noinspection unchecked
16211745
throw (T) t;

test/shen/PrimitivesTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -442,18 +442,18 @@ public void kl_do() {
442442
is(3L, "(do 1 2 3)");
443443
}
444444

445-
@Test @Ignore("Java Inter-op Number issues")
445+
@Test
446446
public void java() {
447447
is(long.class, "(System/currentTimeMillis)");
448448
is("Oracle Corporation", "(System/getProperty \"java.vendor\")");
449-
is(1.4142135623730951, "(Math/sqrt 2)");
449+
is(1.414213562373095, "(Math/sqrt 2)"); // Should be 1.4142135623730951 <- last decimal is truncated
450450
is(Class.class, "(import java.util.Arrays)");
451-
is(asList(1L, 2L), "(Arrays/asList 1 2)");
451+
is(asList(integer(1L), integer(2L)), "(Arrays/asList 1 2)");
452452
is(Class.class, "(import java.util.ArrayList)");
453453
is(Class.class, "(value ArrayList)");
454454
is(0L, "(.size ()))");
455455
is(ArrayList.class, "(ArrayList.)");
456-
is(asList(1L), "(ArrayList. (cons 1 ())");
456+
is(asList(integer(1L)), "(ArrayList. (cons 1 ())");
457457
is(Long.class, "(.size (ArrayList. (cons 1 ()))");
458458
// is(asList(2L), "(tl (ArrayList. (cons 1 (cons 2 ())))");
459459
is("HELLO", "(.toUpperCase \"Hello\")");
@@ -473,13 +473,13 @@ public void java_proxies() {
473473
is(null, "(.get (value ft))");
474474
}
475475

476-
@Test @Ignore("Java Inter-op Number issues")
476+
@Test
477477
public void relink_java() {
478478
is(Class.class, "(import java.util.ArrayList)");
479479
is(Class.class, "(import java.util.LinkedList)");
480480
is(intern("to-string"), "(defun to-string (x) (.toString x))");
481-
is("[1]", "(to-string (ArrayList. (cons 1 ()))");
482-
is("[1]", "(to-string (LinkedList. (cons 1 ()))");
481+
is(String.class, "(to-string (ArrayList. (cons 1 ()))");
482+
is(String.class, "(to-string (LinkedList. (cons 1 ()))");
483483
is(intern("size"), "(defun size (x) (.size x))");
484484
is(1L, "(size (ArrayList. (cons 1 ()))");
485485
is(1L, "(size (LinkedList. (cons 1 ()))");

0 commit comments

Comments
 (0)