From 246545907978bf29c0906b161b863089792936d7 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 5 Aug 2015 15:55:52 -0600 Subject: [PATCH] implement basic Java 8 lambda support The two big pieces here are basic invokedynamic support and a working version of LambdaMetaFactory.metafactory. The latter works by dynamically building a synthetic class with three methods: a static factory method, a constructor for the factory method to call, and a method to satisfy the requested interface which defers to the specified MethodHandle. This work relies heavily on Avian's specific MethodType and MethodHandle implementations, which provide extra, non-standard features to make code generation easier. That means we'll probably need to use Avian's versions of java.lang.invoke.* even when building with the OpenJDK or Android class libraries. --- classpath/avian/Assembler.java | 21 +- classpath/avian/ClassAddendum.java | 2 + classpath/avian/Classes.java | 6 +- classpath/java/lang/invoke/CallSite.java | 7 +- .../java/lang/invoke/LambdaMetafactory.java | 272 +++++++++++++++++- classpath/java/lang/invoke/MethodHandle.java | 22 +- classpath/java/lang/invoke/MethodHandles.java | 13 + classpath/java/lang/invoke/MethodType.java | 258 +++++++++++++++++ classpath/java/lang/reflect/Method.java | 14 +- classpath/java/lang/reflect/Proxy.java | 2 + src/avian/machine.h | 13 +- src/compile.cpp | 215 +++++++++++++- src/machine.cpp | 21 +- src/types.def | 16 +- test/InvokeDynamic.java | 12 +- test/Subroutine.java | 3 +- 16 files changed, 870 insertions(+), 27 deletions(-) diff --git a/classpath/avian/Assembler.java b/classpath/avian/Assembler.java index 5b69314a91..64a06fdf81 100644 --- a/classpath/avian/Assembler.java +++ b/classpath/avian/Assembler.java @@ -60,6 +60,7 @@ public class Assembler { int name, int super_, int[] interfaces, + FieldData[] fields, MethodData[] methods) throws IOException { @@ -83,7 +84,13 @@ public class Assembler { write2(out, i + 1); } - write2(out, 0); // field count + write2(out, fields.length); + for (FieldData f: fields) { + write2(out, f.flags); + write2(out, f.nameIndex + 1); + write2(out, f.specIndex + 1); + write2(out, 0); // attribute count + } write2(out, methods.length); for (MethodData m: methods) { @@ -113,4 +120,16 @@ public class Assembler { this.code = code; } } + + public static class FieldData { + public final int flags; + public final int nameIndex; + public final int specIndex; + + public FieldData(int flags, int nameIndex, int specIndex) { + this.flags = flags; + this.nameIndex = nameIndex; + this.specIndex = specIndex; + } + } } diff --git a/classpath/avian/ClassAddendum.java b/classpath/avian/ClassAddendum.java index 94a9f09ab1..156c4df4a1 100644 --- a/classpath/avian/ClassAddendum.java +++ b/classpath/avian/ClassAddendum.java @@ -25,4 +25,6 @@ public class ClassAddendum extends Addendum { public byte[] enclosingClass; public Pair enclosingMethod; + + public VMMethod[] bootstrapMethodTable; } diff --git a/classpath/avian/Classes.java b/classpath/avian/Classes.java index 2b84f1cbd4..def0047be5 100644 --- a/classpath/avian/Classes.java +++ b/classpath/avian/Classes.java @@ -48,8 +48,8 @@ public class Classes { private static native VMClass resolveVMClass(ClassLoader loader, byte[] spec) throws ClassNotFoundException; - private static VMClass loadVMClass(ClassLoader loader, - byte[] nameBytes, int offset, int length) + public static VMClass loadVMClass(ClassLoader loader, + byte[] nameBytes, int offset, int length) { byte[] spec = new byte[length + 1]; System.arraycopy(nameBytes, offset, spec, 0, length); @@ -576,6 +576,6 @@ public class Classes { private static native void acquireClassLock(); private static native void releaseClassLock(); - + public static native String makeString(byte[] array, int offset, int length); } diff --git a/classpath/java/lang/invoke/CallSite.java b/classpath/java/lang/invoke/CallSite.java index a6c221e8de..a78ed5dcd2 100644 --- a/classpath/java/lang/invoke/CallSite.java +++ b/classpath/java/lang/invoke/CallSite.java @@ -1,4 +1,9 @@ package java.lang.invoke; -public abstract class CallSite { +public class CallSite { + private final MethodHandle target; + + CallSite(MethodHandle target) { + this.target = target; + } } diff --git a/classpath/java/lang/invoke/LambdaMetafactory.java b/classpath/java/lang/invoke/LambdaMetafactory.java index 88c4b9129f..fc26c54fa9 100644 --- a/classpath/java/lang/invoke/LambdaMetafactory.java +++ b/classpath/java/lang/invoke/LambdaMetafactory.java @@ -1,5 +1,275 @@ package java.lang.invoke; +import static avian.Stream.write1; +import static avian.Stream.write2; +import static avian.Stream.write4; +import static avian.Stream.set4; +import static avian.Assembler.*; + +import java.lang.reflect.Proxy; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import avian.Classes; +import avian.ConstantPool; +import avian.Assembler; +import avian.ConstantPool.PoolEntry; +import avian.SystemClassLoader; + public class LambdaMetafactory { - public static CallSite metafactory(MethodHandles.Lookup l, String s, MethodType mt, MethodType mt2, MethodHandle mh, MethodType mt3) throws LambdaConversionException { throw new RuntimeException(); } + private static int nextNumber = 0; + + private static Class resolveReturnInterface(MethodType type) { + int index = 1; + byte[] s = type.spec; + + while (s[index] != ')') ++ index; + + if (s[++ index] != 'L') throw new AssertionError(); + + ++ index; + + int end = index + 1; + while (s[end] != ';') ++ end; + + Class c = SystemClassLoader.getClass + (Classes.loadVMClass(type.loader, s, index, end - index)); + + if (! c.isInterface()) throw new AssertionError(); + + return c; + } + + private static int indexOf(int c, byte[] array) { + int i = 0; + while (array[i] != c) ++i; + return i; + } + + private static String constructorSpec(MethodType type) { + return Classes.makeString(type.spec, 0, indexOf(')', type.spec) + 1) + "V"; + } + + private static byte[] makeFactoryCode(List pool, + String className, + String constructorSpec, + MethodType type) + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + write2(out, type.footprint() + 2); // max stack + write2(out, type.footprint()); // max locals + write4(out, 0); // length (we'll set the real value later) + + write1(out, new_); + write2(out, ConstantPool.addClass(pool, className) + 1); + write1(out, dup); + + for (MethodType.Parameter p: type.parameters()) { + write1(out, p.load()); + write1(out, p.position()); + } + + write1(out, invokespecial); + write2(out, ConstantPool.addMethodRef + (pool, className, "", constructorSpec) + 1); + + write1(out, areturn); + + write2(out, 0); // exception handler table length + write2(out, 0); // attribute count + + byte[] result = out.toByteArray(); + set4(result, 4, result.length - 12); + + return result; + } + + private static byte[] makeConstructorCode(List pool, + String className, + MethodType type) + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + write2(out, 3); // max stack + write2(out, type.footprint() + 1); // max locals + write4(out, 0); // length (we'll set the real value later) + + write1(out, aload_0); + write1(out, invokespecial); + write2(out, ConstantPool.addMethodRef + (pool, "java/lang/Object", "", "()V") + 1); + + for (MethodType.Parameter p: type.parameters()) { + write1(out, aload_0); + write1(out, p.load()); + write1(out, p.position() + 1); + write1(out, putfield); + write2(out, ConstantPool.addFieldRef + (pool, className, "field" + p.index(), p.spec()) + 1); + } + + write1(out, return_); + + write2(out, 0); // exception handler table length + write2(out, 0); // attribute count + + byte[] result = out.toByteArray(); + set4(result, 4, result.length - 12); + + return result; + } + + private static byte[] makeInvocationCode(List pool, + String className, + String constructorSpec, + MethodType fieldType, + MethodType localType, + MethodHandle implementation) + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + write2(out, fieldType.footprint() + + localType.footprint() + 2); // max stack + write2(out, localType.footprint() + 1); // max locals + write4(out, 0); // length (we'll set the real value later) + + write1(out, aload_0); + + for (MethodType.Parameter p: fieldType.parameters()) { + write1(out, aload_0); + write1(out, getfield); + write2(out, ConstantPool.addFieldRef + (pool, className, "field" + p.index(), p.spec()) + 1); + } + + for (MethodType.Parameter p: localType.parameters()) { + write1(out, p.load()); + write1(out, p.position() + 1); + } + + write1(out, invokestatic); + write2(out, ConstantPool.addMethodRef + (pool, + Classes.makeString(implementation.method.class_.name, 0, + implementation.method.class_.name.length - 1), + Classes.makeString(implementation.method.name, 0, + implementation.method.name.length - 1), + Classes.makeString(implementation.method.spec, 0, + implementation.method.spec.length - 1)) + 1); + + write1(out, implementation.type().result().return_()); + + write2(out, 0); // exception handler table length + write2(out, 0); // attribute count + + byte[] result = out.toByteArray(); + set4(result, 4, result.length - 12); + + return result; + } + + public static CallSite metafactory(MethodHandles.Lookup caller, + String invokedName, + MethodType invokedType, + MethodType methodType, + MethodHandle methodImplementation, + MethodType instantiatedMethodType) + throws LambdaConversionException + { + String className; + { int number; + synchronized (LambdaMetafactory.class) { + number = nextNumber++; + } + className = "Lambda-" + number; + } + + List pool = new ArrayList(); + + int interfaceIndex = ConstantPool.addClass + (pool, invokedType.returnType().getName().replace('.', '/')); + + List fieldTable = new ArrayList(); + + for (MethodType.Parameter p: invokedType.parameters()) { + fieldTable.add + (new FieldData(0, + ConstantPool.addUtf8(pool, "field" + p.index()), + ConstantPool.addUtf8(pool, p.spec()))); + } + + String constructorSpec = constructorSpec(invokedType); + + List methodTable = new ArrayList(); + + try { + methodTable.add + (new MethodData + (Modifier.STATIC, + ConstantPool.addUtf8(pool, "make"), + ConstantPool.addUtf8(pool, Classes.makeString + (invokedType.spec, 0, + invokedType.spec.length - 1)), + makeFactoryCode(pool, className, constructorSpec, invokedType))); + + methodTable.add + (new MethodData + (Modifier.PUBLIC, + ConstantPool.addUtf8(pool, ""), + ConstantPool.addUtf8(pool, constructorSpec), + makeConstructorCode(pool, className, invokedType))); + + methodTable.add + (new MethodData + (Modifier.PUBLIC, + ConstantPool.addUtf8(pool, invokedName), + ConstantPool.addUtf8(pool, Classes.makeString + (methodType.spec, 0, + methodType.spec.length - 1)), + makeInvocationCode(pool, className, constructorSpec, invokedType, + methodType, methodImplementation))); + } catch (IOException e) { + AssertionError error = new AssertionError(); + error.initCause(e); + throw error; + } + + int nameIndex = ConstantPool.addClass(pool, className); + int superIndex = ConstantPool.addClass(pool, "java/lang/Object"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + Assembler.writeClass + (out, pool, nameIndex, superIndex, new int[] { interfaceIndex }, + fieldTable.toArray(new FieldData[fieldTable.size()]), + methodTable.toArray(new MethodData[methodTable.size()])); + } catch (IOException e) { + AssertionError error = new AssertionError(); + error.initCause(e); + throw error; + } + + byte[] classData = out.toByteArray(); + + try { + return new CallSite + (new MethodHandle + (invokedType.loader, avian.SystemClassLoader.getClass + (avian.Classes.defineVMClass + (invokedType.loader, classData, 0, classData.length)) + .getMethod("make", invokedType.parameterArray()).vmMethod)); + } catch (NoSuchMethodException e) { + AssertionError error = new AssertionError(); + error.initCause(e); + throw error; + } + } } diff --git a/classpath/java/lang/invoke/MethodHandle.java b/classpath/java/lang/invoke/MethodHandle.java index c0505c07fd..386d2354d4 100644 --- a/classpath/java/lang/invoke/MethodHandle.java +++ b/classpath/java/lang/invoke/MethodHandle.java @@ -1,3 +1,23 @@ package java.lang.invoke; -public abstract class MethodHandle { + +public class MethodHandle { + private final ClassLoader loader; + final avian.VMMethod method; + private volatile MethodType type; + + MethodHandle(ClassLoader loader, avian.VMMethod method) { + this.loader = loader; + this.method = method; + } + + public String toString() { + return new java.lang.reflect.Method(method).toString(); + } + + public MethodType type() { + if (type == null) { + type = new MethodType(loader, method.spec); + } + return type; + } } diff --git a/classpath/java/lang/invoke/MethodHandles.java b/classpath/java/lang/invoke/MethodHandles.java index bbe782dd3a..f17c05daab 100644 --- a/classpath/java/lang/invoke/MethodHandles.java +++ b/classpath/java/lang/invoke/MethodHandles.java @@ -1,5 +1,18 @@ package java.lang.invoke; + public class MethodHandles { public static class Lookup { + final avian.VMClass class_; + private final int modes; + + private Lookup(avian.VMClass class_, int modes) { + this.class_ = class_; + this.modes = modes; + } + + public String toString() { + return "lookup[" + avian.SystemClassLoader.getClass(class_) + ", " + + modes + "]"; + } } } diff --git a/classpath/java/lang/invoke/MethodType.java b/classpath/java/lang/invoke/MethodType.java index 58e59022db..44dd79e3cc 100644 --- a/classpath/java/lang/invoke/MethodType.java +++ b/classpath/java/lang/invoke/MethodType.java @@ -1,3 +1,261 @@ package java.lang.invoke; + +import static avian.Assembler.*; + +import avian.Classes; + +import java.util.List; +import java.util.ArrayList; + public final class MethodType implements java.io.Serializable { + final ClassLoader loader; + final byte[] spec; + private volatile List parameters; + private volatile Result result; + private volatile int footprint; + + MethodType(ClassLoader loader, byte[] spec) { + this.loader = loader; + this.spec = spec; + } + + public String toString() { + return Classes.makeString(spec, 0, spec.length - 1); + } + + public int footprint() { + parameters(); // ensure spec is parsed + + return footprint; + } + + public Class returnType() { + parameters(); // ensure spec is parsed + + return result.type; + } + + public Class[] parameterArray() { + parameters(); // ensure spec is parsed + + Class[] array = new Class[parameters.size()]; + for (int i = 0; i < parameters.size(); ++i) { + array[i] = parameters.get(i).type; + } + + return array; + } + + public Iterable parameters() { + if (parameters == null) { + List list = new ArrayList(); + int i; + int index = 0; + int position = 0; + for (i = 1; spec[i] != ')'; ++i) { + switch (spec[i]) { + case 'L': { + int start = i; + ++ i; + while (spec[i] != ';') ++ i; + + list.add(new Parameter + (index, + position, + Classes.makeString(spec, start, (i - start) + 1), + aload)); + } break; + + case '[': { + int start = i; + ++ i; + while (spec[i] == '[') ++ i; + + switch (spec[i]) { + case 'L': + ++ i; + while (spec[i] != ';') ++ i; + break; + + default: + break; + } + + list.add(new Parameter + (index, + position, + Classes.makeString(spec, start, (i - start) + 1), + aload)); + } break; + + case 'Z': + case 'B': + case 'S': + case 'C': + case 'I': + list.add(new Parameter + (index, + position, + Classes.makeString(spec, i, 1), + iload)); + break; + + case 'F': + list.add(new Parameter + (index, + position, + Classes.makeString(spec, i, 1), + fload)); + break; + + case 'J': + list.add(new Parameter + (index, + position, + Classes.makeString(spec, i, 1), + lload)); + + ++ position; + break; + + case 'D': + list.add(new Parameter + (index, + position, + Classes.makeString(spec, i, 1), + dload)); + + ++ position; + break; + + default: throw new AssertionError(); + } + + ++ index; + ++ position; + } + + footprint = position; + + ++ i; + + switch (spec[i]) { + case 'L': { + int start = i; + ++ i; + while (spec[i] != ';') ++ i; + + result = new Result + (Classes.makeString(spec, start, (i - start) + 1), areturn); + } break; + + case '[': { + int start = i; + ++ i; + while (spec[i] == '[') ++ i; + + switch (spec[i]) { + case 'L': + ++ i; + while (spec[i] != ';') ++ i; + break; + + default: + break; + } + + result = new Result(Classes.makeString(spec, start, (i - start) + 1), + areturn); + } break; + + case 'V': + result = new Result(Classes.makeString(spec, i, 1), return_); + break; + + case 'Z': + case 'B': + case 'S': + case 'C': + case 'I': + result = new Result(Classes.makeString(spec, i, 1), ireturn); + break; + + case 'F': + result = new Result(Classes.makeString(spec, i, 1), freturn); + break; + + case 'J': + result = new Result(Classes.makeString(spec, i, 1), lreturn); + break; + + case 'D': + result = new Result(Classes.makeString(spec, i, 1), dreturn); + break; + + default: throw new AssertionError(); + } + + parameters = list; + } + + return parameters; + } + + public Result result() { + parameters(); // ensure spec has been parsed + + return result; + } + + public class Parameter { + private final int index; + private final int position; + private final String spec; + private final Class type; + private final int load; + + private Parameter(int index, + int position, + String spec, + int load) + { + this.index = index; + this.position = position; + this.spec = spec; + this.type = Classes.forCanonicalName(loader, spec); + this.load = load; + } + + public int index() { + return index; + } + + public int position() { + return position; + } + + public String spec() { + return spec; + } + + public int load() { + return load; + } + } + + public class Result { + private final String spec; + private final Class type; + private final int return_; + + public Result(String spec, int return_) { + this.spec = spec; + this.type = Classes.forCanonicalName(loader, spec); + this.return_ = return_; + } + + public int return_() { + return return_; // :) + } + } } diff --git a/classpath/java/lang/reflect/Method.java b/classpath/java/lang/reflect/Method.java index 98b545ac56..58d788d5dd 100644 --- a/classpath/java/lang/reflect/Method.java +++ b/classpath/java/lang/reflect/Method.java @@ -18,7 +18,7 @@ import avian.Classes; import java.lang.annotation.Annotation; public class Method extends AccessibleObject implements Member { - private final VMMethod vmMethod; + public final VMMethod vmMethod; private boolean accessible; public Method(VMMethod vmMethod) { @@ -59,6 +59,18 @@ public class Method extends AccessibleObject implements Member { return getSpec(vmMethod); } + public String toString() { + StringBuilder sb = new StringBuilder(); + if (vmMethod.class_ != null) { + sb.append(Classes.makeString(vmMethod.class_.name, 0, + vmMethod.class_.name.length - 1)); + sb.append("."); + } + sb.append(getName()); + sb.append(getSpec()); + return sb.toString(); + } + public static String getSpec(VMMethod vmMethod) { return Classes.makeString(vmMethod.spec, 0, vmMethod.spec.length - 1); } diff --git a/classpath/java/lang/reflect/Proxy.java b/classpath/java/lang/reflect/Proxy.java index 306a48d916..60bbedc7ec 100644 --- a/classpath/java/lang/reflect/Proxy.java +++ b/classpath/java/lang/reflect/Proxy.java @@ -23,6 +23,7 @@ import avian.ConstantPool; import avian.ConstantPool.PoolEntry; import avian.Assembler; +import avian.Assembler.FieldData; import avian.Assembler.MethodData; import java.util.List; @@ -402,6 +403,7 @@ public class Proxy { ByteArrayOutputStream out = new ByteArrayOutputStream(); Assembler.writeClass (out, pool, nameIndex, superIndex, interfaceIndexes, + new FieldData[0], methodTable.toArray(new MethodData[methodTable.size()])); byte[] classData = out.toByteArray(); diff --git a/src/avian/machine.h b/src/avian/machine.h index f56b9720ac..b028981c76 100644 --- a/src/avian/machine.h +++ b/src/avian/machine.h @@ -3408,18 +3408,18 @@ inline GcClass* resolveClassInPool(Thread* t, inline object resolve( Thread* t, GcClassLoader* loader, - GcMethod* method, + GcSingleton* pool, unsigned index, object (*find)(vm::Thread*, GcClass*, GcByteArray*, GcByteArray*), Gc::Type errorType, bool throw_ = true) { - object o = singletonObject(t, method->code()->pool(), index); + object o = singletonObject(t, pool, index); loadMemoryBarrier(); if (objectClass(t, o) == type(t, GcReference::Type)) { - PROTECT(t, method); + PROTECT(t, pool); GcReference* reference = cast(t, o); PROTECT(t, reference); @@ -3439,8 +3439,7 @@ inline object resolve( if (o) { storeStoreMemoryBarrier(); - method->code()->pool()->setBodyElement( - t, index, reinterpret_cast(o)); + pool->setBodyElement(t, index, reinterpret_cast(o)); } } else { o = 0; @@ -3459,7 +3458,7 @@ inline GcField* resolveField(Thread* t, return cast(t, resolve(t, loader, - method, + method->code()->pool(), index, findFieldInClass, GcNoSuchFieldError::Type, @@ -3562,7 +3561,7 @@ inline GcMethod* resolveMethod(Thread* t, return cast(t, resolve(t, loader, - method, + method->code()->pool(), index, findMethodInClass, GcNoSuchMethodError::Type, diff --git a/src/compile.cpp b/src/compile.cpp index be2cc48d4e..3825132c8e 100644 --- a/src/compile.cpp +++ b/src/compile.cpp @@ -1284,6 +1284,8 @@ unsigned& dynamicIndex(MyThread* t); void**& dynamicTable(MyThread* t); +unsigned& dynamicTableSize(MyThread* t); + void updateDynamicTable(MyThread* t, MyThread* o) { o->dynamicTable = dynamicTable(t); @@ -1301,6 +1303,7 @@ uintptr_t compileVirtualThunk(MyThread* t, uintptr_t thunk, const char* baseName); +Allocator* allocator(MyThread* t); unsigned addDynamic(MyThread* t, GcInvocation* invocation) { @@ -1319,7 +1322,7 @@ unsigned addDynamic(MyThread* t, GcInvocation* invocation) unsigned newCapacity = oldCapacity ? 2 * oldCapacity : 4096; void** newTable = static_cast( - t->m->heap->allocate(newCapacity * BytesPerWord)); + allocator(t)->allocate(newCapacity * BytesPerWord)); GcArray* newData = makeArray(t, newCapacity); PROTECT(t, newData); @@ -1345,7 +1348,11 @@ unsigned addDynamic(MyThread* t, GcInvocation* invocation) ENTER(t, Thread::ExclusiveState); + if (dynamicTable(t)) { + allocator(t)->free(dynamicTable(t), dynamicTableSize(t)); + } dynamicTable(t) = newTable; + dynamicTableSize(t) = newCapacity * BytesPerWord; roots(t)->setInvocations(t, newData); updateDynamicTable(static_cast(t->m->rootThread), t); @@ -7050,13 +7057,13 @@ void finish(MyThread* t, FixedAllocator* allocator, Context* context) reinterpret_cast(context->method->spec()->body().begin())); // for debugging: - if (true + if (false and ::strcmp(reinterpret_cast( context->method->class_()->name()->body().begin()), - "InvokeDynamic") == 0 + "java/lang/System") == 0 and ::strcmp(reinterpret_cast( context->method->name()->body().begin()), - "main") == 0) { + "") == 0) { trap(); } syncInstructionCache(start, codeSize); @@ -7271,6 +7278,187 @@ uint64_t compileVirtualMethod(MyThread* t) return reinterpret_cast(compileVirtualMethod2(t, class_, index)); } +GcCallSite* resolveDynamic(MyThread* t, GcInvocation* invocation) +{ + PROTECT(t, invocation); + + GcClass* c = invocation->class_(); + PROTECT(t, c); + + GcCharArray* bootstrapArray = cast( + t, + cast(t, c->addendum()->bootstrapMethodTable()) + ->body()[invocation->bootstrap()]); + + PROTECT(t, bootstrapArray); + + GcMethod* bootstrap = cast(t, + resolve(t, + c->loader(), + invocation->pool(), + bootstrapArray->body()[0], + findMethodInClass, + GcNoSuchMethodError::Type)); + PROTECT(t, bootstrap); + + assertT(t, bootstrap->parameterCount() == 2 + bootstrapArray->length()); + + GcLookup* lookup + = makeLookup(t, c, ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC); + PROTECT(t, lookup); + + GcByteArray* nameBytes = invocation->template_()->name(); + GcString* name + = t->m->classpath->makeString(t, nameBytes, 0, nameBytes->length() - 1); + PROTECT(t, name); + + GcMethodType* type = makeMethodType( + t, c->loader(), invocation->template_()->spec(), 0, 0, 0); + PROTECT(t, type); + + GcArray* array = makeArray(t, bootstrap->parameterCount()); + PROTECT(t, array); + + unsigned argument = 0; + array->setBodyElement(t, argument++, lookup); + array->setBodyElement(t, argument++, name); + array->setBodyElement(t, argument++, type); + + MethodSpecIterator it( + t, reinterpret_cast(bootstrap->spec()->body().begin())); + + for (unsigned i = 0; i < argument; ++i) + it.next(); + + unsigned i = 0; + while (it.hasNext()) { + const char* p = it.next(); + switch (*p) { + case 'L': { + const char* const methodType = "Ljava/lang/invoke/MethodType;"; + const char* const methodHandle = "Ljava/lang/invoke/MethodHandle;"; + if (strncmp(p, methodType, strlen(methodType)) == 0) { + GcMethodType* type = makeMethodType( + t, + c->loader(), + cast( + t, + singletonObject( + t, invocation->pool(), bootstrapArray->body()[i + 1])), + 0, + 0, + 0); + + array->setBodyElement(t, i + argument, type); + } else if (strncmp(p, methodHandle, strlen(methodHandle)) == 0) { + GcMethod* method = cast(t, + resolve(t, + c->loader(), + invocation->pool(), + bootstrapArray->body()[i + 1], + findMethodInClass, + GcNoSuchMethodError::Type)); + + GcMethodHandle* handle = makeMethodHandle(t, c->loader(), method, 0); + + array->setBodyElement(t, i + argument, handle); + } else { + abort(t); + } + } break; + + case 'I': + case 'F': { + GcInt* box = makeInt( + t, + singletonValue(t, invocation->pool(), bootstrapArray->body()[i + 1])); + + array->setBodyElement(t, i + argument, box); + } break; + + case 'J': + case 'D': { + uint64_t v; + memcpy( + &v, + &singletonValue(t, invocation->pool(), bootstrapArray->body()[i + 1]), + 8); + + GcLong* box = makeLong(t, v); + + array->setBodyElement(t, i + argument, box); + } break; + + default: + abort(t); + } + + ++i; + } + + GcMethodHandle* handle = (bootstrap->flags() & ACC_STATIC) + ? 0 + : makeMethodHandle(t, c->loader(), bootstrap, 0); + + return cast( + t, t->m->processor->invokeArray(t, bootstrap, handle, array)); +} + +void* linkDynamicMethod2(MyThread* t, unsigned index) +{ + GcInvocation* invocation + = cast(t, roots(t)->invocations()->body()[index]); + + GcCallSite* site = invocation->site(); + + loadMemoryBarrier(); + + if (site == 0) { + t->trace->targetMethod = invocation->template_(); + + THREAD_RESOURCE0(t, static_cast(t)->trace->targetMethod = 0;); + + PROTECT(t, invocation); + + site = resolveDynamic(t, invocation); + PROTECT(t, site); + + compile(t, codeAllocator(t), 0, site->target()->method()); + + ACQUIRE(t, t->m->classLock); + + if (invocation->site() == 0) { + void* address + = reinterpret_cast(methodAddress(t, site->target()->method())); + + if ((site->target()->method()->flags() & ACC_NATIVE) == 0) { + t->dynamicTable[index] = address; + } + } + + storeStoreMemoryBarrier(); + + invocation->setSite(t, site); + site->setInvocation(t, invocation); + } + + GcMethod* target = invocation->site()->target()->method(); + + if (target->flags() & ACC_NATIVE) { + t->trace->nativeMethod = target; + } + + return reinterpret_cast(methodAddress(t, target)); +} + +uint64_t linkDynamicMethod(MyThread* t) +{ + unsigned index = t->virtualCallIndex; + t->virtualCallIndex = 0; + + return reinterpret_cast(linkDynamicMethod2(t, index)); +} + uint64_t invokeNativeFast(MyThread* t, GcMethod* method, void* function) { FastNativeFunction f; @@ -8482,10 +8670,12 @@ class MyProcessor : public Processor { dynamicIndex(0), useNativeFeatures(useNativeFeatures), compilationHandlers(0), - dynamicTable(0) + dynamicTable(0), + dynamicTableSize(0) { thunkTable[compileMethodIndex] = voidPointer(local::compileMethod); thunkTable[compileVirtualMethodIndex] = voidPointer(compileVirtualMethod); + thunkTable[linkDynamicMethodIndex] = voidPointer(linkDynamicMethod); thunkTable[invokeNativeIndex] = voidPointer(invokeNative); thunkTable[throwArrayIndexOutOfBoundsIndex] = voidPointer(throwArrayIndexOutOfBounds); @@ -8956,6 +9146,10 @@ class MyProcessor : public Processor { signals.unregisterHandler(SignalRegistrar::DivideByZero); signals.setCrashDumpDirectory(0); + if (dynamicTable) { + allocator->free(dynamicTable, dynamicTableSize); + } + this->~MyProcessor(); allocator->free(this, sizeof(*this)); @@ -9244,6 +9438,7 @@ class MyProcessor : public Processor { void* thunkTable[dummyIndex + 1]; CompilationHandlerList* compilationHandlers; void** dynamicTable; + unsigned dynamicTableSize; }; unsigned& dynamicIndex(MyThread* t) @@ -9256,6 +9451,11 @@ void**& dynamicTable(MyThread* t) return static_cast(t->m->processor)->dynamicTable; } +unsigned& dynamicTableSize(MyThread* t) +{ + return static_cast(t->m->processor)->dynamicTableSize; +} + const char* stringOrNull(const char* str) { if (str) { @@ -10433,6 +10633,11 @@ avian::util::FixedAllocator* codeAllocator(MyThread* t) return &(processor(t)->codeAllocator); } +Allocator* allocator(MyThread* t) +{ + return processor(t)->allocator; +} + } // namespace local } // namespace diff --git a/src/machine.cpp b/src/machine.cpp index 7e2c95c9be..1cd581f9ba 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -1220,7 +1220,7 @@ GcClassAddendum* getClassAddendum(Thread* t, GcClass* class_, GcSingleton* pool) if (addendum == 0) { PROTECT(t, class_); - addendum = makeClassAddendum(t, pool, 0, 0, 0, 0, -1, 0, 0); + addendum = makeClassAddendum(t, pool, 0, 0, 0, 0, -1, 0, 0, 0); setField(t, class_, ClassAddendum, addendum); } return addendum; @@ -2811,6 +2811,25 @@ void parseAttributeTable(Thread* t, GcClassAddendum* addendum = getClassAddendum(t, class_, pool); addendum->setAnnotationTable(t, body); + } else if (vm::strcmp(reinterpret_cast("BootstrapMethods"), + name->body().begin()) == 0) { + unsigned count = s.read2(); + GcArray* array = makeArray(t, count); + PROTECT(t, array); + + for (unsigned i = 0; i < count; ++i) { + unsigned reference = s.read2() - 1; + unsigned argumentCount = s.read2(); + GcCharArray* element = makeCharArray(t, 1 + argumentCount); + element->body()[0] = reference; + for (unsigned ai = 0; ai < argumentCount; ++ai) { + element->body()[1 + ai] = s.read2() - 1; + } + array->setBodyElement(t, i, element); + } + + GcClassAddendum* addendum = getClassAddendum(t, class_, pool); + addendum->setBootstrapMethodTable(t, array); } else if (vm::strcmp(reinterpret_cast("EnclosingMethod"), name->body().begin()) == 0) { int16_t enclosingClass = s.read2(); diff --git a/src/types.def b/src/types.def index 3e2a341955..da247c9d26 100644 --- a/src/types.def +++ b/src/types.def @@ -22,6 +22,16 @@ (type cloneable java/lang/Cloneable) +(type callSite java/lang/invoke/CallSite + (require invocation invocation)) + +(type methodHandle java/lang/invoke/MethodHandle + (alias method method vmtarget)) + +(type methodType java/lang/invoke/MethodType) + +(type lookup java/lang/invoke/MethodHandles$Lookup) + (type singleton avian/Singleton (array maybe_object body)) @@ -76,10 +86,10 @@ (type invocation (uint16_t bootstrap) (int32_t index) - (object class) - (object pool) + (class class) + (singleton pool) (method template) - (object site)) + (callSite site)) (type triple (object first) diff --git a/test/InvokeDynamic.java b/test/InvokeDynamic.java index 6a222700ee..7f54990324 100644 --- a/test/InvokeDynamic.java +++ b/test/InvokeDynamic.java @@ -1,7 +1,15 @@ public class InvokeDynamic { + private interface Operation { + int operate(int a, int b); + } + private static void expect(boolean v) { + if (! v) throw new RuntimeException(); + } + public static void main(String[] args) { - Runnable r = () -> System.out.println("success"); - r.run(); + int c = 4; + Operation op = (a, b) -> a + b - c; + expect(op.operate(2, 3) == 1); } } diff --git a/test/Subroutine.java b/test/Subroutine.java index 5fb5bfa223..e03686a9f5 100644 --- a/test/Subroutine.java +++ b/test/Subroutine.java @@ -2,6 +2,7 @@ import avian.Stream; import avian.ConstantPool; import avian.ConstantPool.PoolEntry; import avian.Assembler; +import avian.Assembler.FieldData; import avian.Assembler.MethodData; import java.util.ArrayList; @@ -95,7 +96,7 @@ public class Subroutine { Assembler.writeClass (out, pool, ConstantPool.addClass(pool, name), ConstantPool.addClass(pool, "java/lang/Object"), - new int[0], new MethodData[] + new int[0], new FieldData[0], new MethodData[] { new MethodData(Assembler.ACC_STATIC | Assembler.ACC_PUBLIC, ConstantPool.addUtf8(pool, "test"), ConstantPool.addUtf8(pool, "()V"),