From 9b2a02e92bc6e30bd2c9f01854558ac10c8c8e88 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Sat, 12 Sep 2015 19:58:50 -0600 Subject: [PATCH 01/11] avoid calling unrelated JNI methods during class initialization The main goal here is to avoid making JNI calls from code that really shouldn't need JNI (e.g. before this patch, ArrayList.add called Math.max, which called Math., which called Random., which called System.currentTimeMillis). Besides following the "pay for only what you need" principle, this change ensures we can call LambdaMetaFactory methods during AOT compilation with a minimal VM (i.e. without compiling in JNI methods we don't need). --- classpath/java/lang/Math.java | 7 +++++-- classpath/java/lang/System.java | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/classpath/java/lang/Math.java b/classpath/java/lang/Math.java index eb79c3cc1b..c1d1c0bdaf 100644 --- a/classpath/java/lang/Math.java +++ b/classpath/java/lang/Math.java @@ -15,7 +15,10 @@ import java.util.Random; public final class Math { public static final double E = 2.718281828459045; public static final double PI = 3.141592653589793; - private static final Random random = new Random(); + + private static class Static { + public static final Random random = new Random(); + } private Math() { } @@ -84,7 +87,7 @@ public final class Math { } public static double random() { - return random.nextDouble(); + return Static.random.nextDouble(); } public static native double floor(double v); diff --git a/classpath/java/lang/System.java b/classpath/java/lang/System.java index 10886dbb03..f6ceabad6b 100644 --- a/classpath/java/lang/System.java +++ b/classpath/java/lang/System.java @@ -22,7 +22,9 @@ import java.util.Hashtable; import java.util.Properties; public abstract class System { - private static final long NanoTimeBaseInMillis = currentTimeMillis(); + private static class NanoTime { + public static final long BaseInMillis = currentTimeMillis(); + } private static class Static { public static Properties properties = makeProperties(); @@ -94,7 +96,7 @@ public abstract class System { public static native int identityHashCode(Object o); public static long nanoTime() { - return (currentTimeMillis() - NanoTimeBaseInMillis) * 1000000; + return (currentTimeMillis() - NanoTime.BaseInMillis) * 1000000; } public static String mapLibraryName(String name) { From 763aada4b060fd9fb53f4292d417faaca1ef02b9 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Sat, 12 Sep 2015 20:08:54 -0600 Subject: [PATCH 02/11] optionally specify reentrancy when creating a System object This allows multiple Avian VMs to share the same process space, provided they don't try to use functionality that involves global shared resources (e.g. signal handling). --- include/avian/system/system.h | 2 +- src/system/posix.cpp | 51 ++++++++++++++++++++--------------- src/system/windows.cpp | 19 ++++++++----- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/include/avian/system/system.h b/include/avian/system/system.h index c1f1dc1489..e2c4ff5022 100644 --- a/include/avian/system/system.h +++ b/include/avian/system/system.h @@ -176,7 +176,7 @@ inline void NO_RETURN sysAbort(System* s) // #endif // not NDEBUG -AVIAN_EXPORT System* makeSystem(); +AVIAN_EXPORT System* makeSystem(bool reentrant = false); } // namespace vm diff --git a/src/system/posix.cpp b/src/system/posix.cpp index 256f479bee..64a667887c 100644 --- a/src/system/posix.cpp +++ b/src/system/posix.cpp @@ -92,7 +92,7 @@ const int signals[] = {VisitSignal, InterruptSignal, PipeSignal}; const unsigned SignalCount = 3; class MySystem; -MySystem* system; +MySystem* globalSystem; void handleSignal(int signal, siginfo_t* info, void* context); @@ -624,16 +624,18 @@ class MySystem : public System { System::Library* next_; }; - MySystem() : threadVisitor(0), visitTarget(0) + MySystem(bool reentrant) : reentrant(reentrant), threadVisitor(0), visitTarget(0) { - expect(this, system == 0); - system = this; + if (not reentrant) { + expect(this, globalSystem == 0); + globalSystem = this; - expect(this, registerHandler(InterruptSignalIndex)); - expect(this, registerHandler(VisitSignalIndex)); - expect(this, registerHandler(PipeSignalIndex)); - - expect(this, make(&visitLock) == 0); + expect(this, registerHandler(InterruptSignalIndex)); + expect(this, registerHandler(VisitSignalIndex)); + expect(this, registerHandler(PipeSignalIndex)); + + expect(this, make(&visitLock) == 0); + } } // Returns true on success, false on failure @@ -708,6 +710,8 @@ class MySystem : public System { System::Thread* sTarget, ThreadVisitor* visitor) { + expect(this, not reentrant); + assertT(this, st != sTarget); Thread* target = static_cast(sTarget); @@ -767,7 +771,7 @@ class MySystem : public System { threadVisitor = 0; - system->visitLock->notifyAll(t); + globalSystem->visitLock->notifyAll(t); return result; #endif // not __APPLE__ @@ -938,18 +942,21 @@ class MySystem : public System { virtual void dispose() { - visitLock->dispose(); + if (not reentrant) { + visitLock->dispose(); - expect(this, unregisterHandler(InterruptSignalIndex)); - expect(this, unregisterHandler(VisitSignalIndex)); - expect(this, unregisterHandler(PipeSignalIndex)); - system = 0; + expect(this, unregisterHandler(InterruptSignalIndex)); + expect(this, unregisterHandler(VisitSignalIndex)); + expect(this, unregisterHandler(PipeSignalIndex)); + globalSystem = 0; + } ::free(this); } struct sigaction oldHandlers[SignalCount]; + bool reentrant; ThreadVisitor* threadVisitor; Thread* visitTarget; System::Monitor* visitLock; @@ -965,13 +972,13 @@ void handleSignal(int signal, siginfo_t*, void* context) switch (signal) { case VisitSignal: { - system->threadVisitor->visit(ip, stack, link); + globalSystem->threadVisitor->visit(ip, stack, link); - System::Thread* t = system->visitTarget; - system->visitTarget = 0; + System::Thread* t = globalSystem->visitTarget; + globalSystem->visitTarget = 0; - ACQUIRE_MONITOR(t, system->visitLock); - system->visitLock->notifyAll(t); + ACQUIRE_MONITOR(t, globalSystem->visitLock); + globalSystem->visitLock->notifyAll(t); } break; case InterruptSignal: @@ -987,9 +994,9 @@ void handleSignal(int signal, siginfo_t*, void* context) namespace vm { -AVIAN_EXPORT System* makeSystem() +AVIAN_EXPORT System* makeSystem(bool reentrant) { - return new (malloc(sizeof(MySystem))) MySystem(); + return new (malloc(sizeof(MySystem))) MySystem(reentrant); } } // namespace vm diff --git a/src/system/windows.cpp b/src/system/windows.cpp index 041c20995f..2d5dfa4707 100644 --- a/src/system/windows.cpp +++ b/src/system/windows.cpp @@ -115,7 +115,7 @@ class MutexResource { }; class MySystem; -MySystem* system; +MySystem* globalSystem; DWORD WINAPI run(void* r) { @@ -655,10 +655,12 @@ class MySystem : public System { System::Library* next_; }; - MySystem() + MySystem(bool reentrant): reentrant(reentrant) { - expect(this, system == 0); - system = this; + if (not reentrant) { + expect(this, globalSystem == 0); + globalSystem = this; + } mutex = CreateMutex(0, false, 0); assertT(this, mutex); @@ -1007,7 +1009,10 @@ class MySystem : public System { virtual void dispose() { - system = 0; + if (not reentrant) { + globalSystem = 0; + } + CloseHandle(mutex); ::free(this); } @@ -1019,9 +1024,9 @@ class MySystem : public System { namespace vm { -AVIAN_EXPORT System* makeSystem() +AVIAN_EXPORT System* makeSystem(bool reentrant) { - return new (malloc(sizeof(MySystem))) MySystem(); + return new (malloc(sizeof(MySystem))) MySystem(reentrant); } } // namespace vm From d5a5b5309a43028363750d5a9ec7a10b052366dc Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Sat, 12 Sep 2015 20:15:46 -0600 Subject: [PATCH 03/11] support AOT-compilation of Java 8 lambda expressions These expressions are tricky because they rely on invokedynamic, which normally implies runtime code generation. However, since lambdas don't actually use the "dynamicness" of invokedynamic, we can convert them into static calls to synthetic classes at compile time. Since I had already written code to synthesize such classes in Java and I didn't want to rewrite it in C++, I needed to add support for running Java code to the bootimage generator. And since the primary VM used by the generator is purpose-built to generate AOT-compiled code for a specific target architecture and is not capable of generating or running JIT-compiled code for the host architecture, I added support for loading a second, independent, host-specific VM for running Java code. The rest of the patch handles the fact that each method compilation might cause new, synthetic classes to be created, so we need to make sure those classes and their methods are included in the final heap and code images. This required breaking some giant code blocks out of makeCodeImage into their own methods, which makes the diff look scarier than it really is. --- README.md | 15 +- classpath/avian/Classes.java | 21 + classpath/avian/SystemClassLoader.java | 2 + .../java/lang/invoke/LambdaMetafactory.java | 45 +- classpath/java/lang/invoke/MethodHandle.java | 15 + classpath/java/lang/invoke/MethodType.java | 7 + makefile | 10 +- src/avian/bootimage.h | 3 + src/avian/jnienv.h | 1 + src/avian/machine.h | 29 +- src/avian/processor.h | 3 +- src/builtin.cpp | 6 + src/compile.cpp | 221 +++- src/interpret.cpp | 3 +- src/jnienv.cpp | 6 +- src/machine.cpp | 12 +- src/system/windows.cpp | 1 + src/tools/bootimage-generator/main.cpp | 972 ++++++++++-------- 18 files changed, 893 insertions(+), 479 deletions(-) diff --git a/README.md b/README.md index 9e6430e23f..91bbcc0113 100644 --- a/README.md +++ b/README.md @@ -168,8 +168,8 @@ Library" below for details. These flags determine the name of the directory used for the build. The name always starts with _${platform}-${arch}_, and each non-default build option is appended to the name. For example, a debug build with -bootimage enabled on Linux/i386 would be built in -_build/linux-i386-debug-bootimage_. This allows you to build with +bootimage enabled on Linux/x86_64 would be built in +_build/linux-x86_64-debug-bootimage_. This allows you to build with several different sets of options independently and even simultaneously without doing a clean build each time. @@ -575,7 +575,7 @@ For boot image builds: Note you can use ProGuard without using a boot image and vice-versa, as desired. -The following instructions assume we are building for Linux/i386. +The following instructions assume we are building for Linux/x86_64. Please refer to the previous example for guidance on other platforms. __1.__ Build Avian, create a new directory, and populate it with the @@ -584,13 +584,13 @@ VM object files. $ make bootimage=true $ mkdir hello $ cd hello - $ ar x ../build/linux-i386-bootimage/libavian.a + $ ar x ../build/linux-x86_64-bootimage/libavian.a __2.__ Create a stage1 directory and extract the contents of the class library jar into it. $ mkdir stage1 - $ (cd stage1 && jar xf ../../build/linux-i386-bootimage/classpath.jar) + $ (cd stage1 && jar xf ../../build/linux-x86_64-bootimage/classpath.jar) __3.__ Build the Java code and add it to stage1. @@ -630,10 +630,11 @@ using the OpenJDK library.) __6.__ Build the boot and code images. - $ ../build/linux-i386-bootimage/bootimage-generator \ + $ ../build/linux-x86_64-bootimage/bootimage-generator \ -cp stage2 \ -bootimage bootimage-bin.o \ - -codeimage codeimage-bin.o + -codeimage codeimage-bin.o \ + -hostvm ../build/linux-x86_64-interpret/libjvm.so Note that you can override the default names for the start and end symbols in the boot/code image by also passing: diff --git a/classpath/avian/Classes.java b/classpath/avian/Classes.java index 7d428b28d4..b55408834c 100644 --- a/classpath/avian/Classes.java +++ b/classpath/avian/Classes.java @@ -423,6 +423,27 @@ public class Classes { } } + public static VMMethod findMethod(ClassLoader loader, + String class_, + String name, + String spec) + throws ClassNotFoundException + { + VMClass c = SystemClassLoader.vmClass(loader.loadClass(class_)); + VMMethod[] methodTable = c.methodTable; + if (methodTable != null) { + link(c); + + for (int i = 0; i < methodTable.length; ++i) { + VMMethod m = methodTable[i]; + if (toString(m.name).equals(name) && toString(m.spec).equals(spec)) { + return m; + } + } + } + return null; + } + public static int findMethod(VMClass vmClass, String name, Class[] parameterTypes) { diff --git a/classpath/avian/SystemClassLoader.java b/classpath/avian/SystemClassLoader.java index e9970e021d..84b76c9b92 100644 --- a/classpath/avian/SystemClassLoader.java +++ b/classpath/avian/SystemClassLoader.java @@ -20,6 +20,8 @@ import java.util.Enumeration; import java.util.NoSuchElementException; public class SystemClassLoader extends ClassLoader { + public static native ClassLoader appLoader(); + private native VMClass findVMClass(String name) throws ClassNotFoundException; diff --git a/classpath/java/lang/invoke/LambdaMetafactory.java b/classpath/java/lang/invoke/LambdaMetafactory.java index 5a814d1649..f0d57163b3 100644 --- a/classpath/java/lang/invoke/LambdaMetafactory.java +++ b/classpath/java/lang/invoke/LambdaMetafactory.java @@ -187,14 +187,28 @@ public class LambdaMetafactory { return result; } - - public static CallSite metafactory(MethodHandles.Lookup caller, - String invokedName, - MethodType invokedType, - MethodType methodType, - MethodHandle methodImplementation, - MethodType instantiatedMethodType) - throws LambdaConversionException + + public static byte[] makeLambda(String invokedName, + String invokedType, + String methodType, + String implementationClass, + String implementationName, + String implementationSpec, + int implementationKind) + { + return makeLambda(invokedName, + new MethodType(invokedType), + new MethodType(methodType), + new MethodHandle(implementationClass, + implementationName, + implementationSpec, + implementationKind)); + } + + private static byte[] makeLambda(String invokedName, + MethodType invokedType, + MethodType methodType, + MethodHandle methodImplementation) { String className; { int number; @@ -265,8 +279,19 @@ public class LambdaMetafactory { throw error; } - byte[] classData = out.toByteArray(); - + return out.toByteArray(); + } + + public static CallSite metafactory(MethodHandles.Lookup caller, + String invokedName, + MethodType invokedType, + MethodType methodType, + MethodHandle methodImplementation, + MethodType instantiatedMethodType) + throws LambdaConversionException + { + byte[] classData = makeLambda(invokedName, invokedType, methodType, methodImplementation); + try { return new CallSite (new MethodHandle diff --git a/classpath/java/lang/invoke/MethodHandle.java b/classpath/java/lang/invoke/MethodHandle.java index 4efbfbabd6..c676cadeed 100644 --- a/classpath/java/lang/invoke/MethodHandle.java +++ b/classpath/java/lang/invoke/MethodHandle.java @@ -1,6 +1,7 @@ package java.lang.invoke; import avian.Classes; +import avian.SystemClassLoader; public class MethodHandle { static final int REF_invokeStatic = 6; @@ -17,6 +18,20 @@ public class MethodHandle { this.method = method; } + MethodHandle(String class_, + String name, + String spec, + int kind) + { + this.kind = kind; + this.loader = SystemClassLoader.appLoader(); + try { + this.method = Classes.findMethod(this.loader, class_, name, spec); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + public String toString() { StringBuilder sb = new StringBuilder(); if (method.class_ != null) { diff --git a/classpath/java/lang/invoke/MethodType.java b/classpath/java/lang/invoke/MethodType.java index c8bf5bce62..e2030403d9 100644 --- a/classpath/java/lang/invoke/MethodType.java +++ b/classpath/java/lang/invoke/MethodType.java @@ -4,6 +4,7 @@ import static avian.Assembler.*; import avian.Assembler; import avian.Classes; +import avian.SystemClassLoader; import avian.VMClass; import java.util.List; @@ -25,6 +26,12 @@ public final class MethodType implements java.io.Serializable { this.spec = spec; } + MethodType(String spec) { + this.loader = SystemClassLoader.appLoader(); + this.spec = new byte[spec.length() + 1]; + spec.getBytes(0, spec.length(), this.spec, 0); + } + public String toMethodDescriptorString() { return Classes.makeString(spec, 0, spec.length - 1); } diff --git a/makefile b/makefile index ddf9a55bd3..1e7ef0ef4e 100755 --- a/makefile +++ b/makefile @@ -1333,6 +1333,12 @@ bootimage-generator-objects = \ $(call cpp-objects,$(bootimage-generator-sources),$(src),$(build)) bootimage-generator = $(build)/bootimage-generator +ifneq ($(mode),fast) + host-vm-options := -$(mode) +endif + +host-vm = build/$(build-platform)-$(build-arch)-interpret$(host-vm-options)/libjvm.so + bootimage-object = $(build)/bootimage-bin.o codeimage-object = $(build)/codeimage-bin.o @@ -2010,7 +2016,8 @@ $(bootimage-object) $(codeimage-object): $(bootimage-generator) \ @echo "generating bootimage and codeimage binaries from $(classpath-build) using $(<)" $(<) -cp $(classpath-build) -bootimage $(bootimage-object) -codeimage $(codeimage-object) \ -bootimage-symbols $(bootimage-symbols) \ - -codeimage-symbols $(codeimage-symbols) + -codeimage-symbols $(codeimage-symbols) \ + -hostvm $(host-vm) executable-objects = $(vm-objects) $(classpath-objects) $(driver-object) \ $(vm-heapwalk-objects) $(boot-object) $(vm-classpath-objects) \ @@ -2065,6 +2072,7 @@ $(unittest-executable): $(unittest-executable-objects) $(bootimage-generator): $(bootimage-generator-objects) $(vm-objects) echo building $(bootimage-generator) arch=$(build-arch) platform=$(bootimage-platform) + $(MAKE) process=interpret bootimage= mode=$(mode) $(MAKE) mode=$(mode) \ build=$(host-build-root) \ arch=$(build-arch) \ diff --git a/src/avian/bootimage.h b/src/avian/bootimage.h index 9f6cf36b7c..d14755f8b3 100644 --- a/src/avian/bootimage.h +++ b/src/avian/bootimage.h @@ -55,10 +55,13 @@ class BootImage { } PACKED; class GcField; +class GcClass; class OffsetResolver { public: virtual unsigned fieldOffset(Thread*, GcField*) = 0; + + virtual void addClass(Thread*, GcClass*, const uint8_t*, size_t) = 0; }; #define NAME(x) Target##x diff --git a/src/avian/jnienv.h b/src/avian/jnienv.h index b6e5967d3b..9947ade6ec 100644 --- a/src/avian/jnienv.h +++ b/src/avian/jnienv.h @@ -20,6 +20,7 @@ #define EMBED_PREFIX_PROPERTY "avian.embed.prefix" #define CLASSPATH_PROPERTY "java.class.path" #define JAVA_HOME_PROPERTY "java.home" +#define REENTRANT_PROPERTY "avian.reentrant" #define BOOTCLASSPATH_PREPEND_OPTION "bootclasspath/p" #define BOOTCLASSPATH_OPTION "bootclasspath" #define BOOTCLASSPATH_APPEND_OPTION "bootclasspath/a" diff --git a/src/avian/machine.h b/src/avian/machine.h index 9f5654303d..40b627cdbc 100644 --- a/src/avian/machine.h +++ b/src/avian/machine.h @@ -198,6 +198,14 @@ const unsigned ConstructorFlag = 1 << 1; #define JNI_VERSION_1_6 0x00010006 #endif +#ifndef JNI_TRUE +#define JNI_TRUE 1 +#endif + +#ifndef JNI_OK +#define JNI_OK 0 +#endif + typedef Machine JavaVM; typedef Thread JNIEnv; @@ -207,6 +215,19 @@ struct JNINativeMethod { void* function; }; +struct JavaVMOption { + char* optionString; + void* extraInfo; +}; + +struct JavaVMInitArgs { + jint version; + + jint nOptions; + JavaVMOption* options; + jboolean ignoreUnrecognized; +}; + struct JavaVMVTable { void* reserved0; void* reserved1; @@ -3737,10 +3758,10 @@ void populateMultiArray(Thread* t, GcMethod* getCaller(Thread* t, unsigned target, bool skipMethodInvoke = false); -object defineClass(Thread* t, - GcClassLoader* loader, - const uint8_t* buffer, - unsigned length); +GcClass* defineClass(Thread* t, + GcClassLoader* loader, + const uint8_t* buffer, + unsigned length); inline GcMethod* methodClone(Thread* t, GcMethod* method) { diff --git a/src/avian/processor.h b/src/avian/processor.h index 56db019ace..7e9df08ae3 100644 --- a/src/avian/processor.h +++ b/src/avian/processor.h @@ -170,7 +170,8 @@ class Processor { GcTriple** calls, avian::codegen::DelayedPromise** addresses, GcMethod* method, - OffsetResolver* resolver) = 0; + OffsetResolver* resolver, + Machine* hostVM) = 0; virtual void visitRoots(Thread* t, HeapWalker* w) = 0; diff --git a/src/builtin.cpp b/src/builtin.cpp index 8cecd382ad..46b82f4748 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -183,6 +183,12 @@ extern "C" AVIAN_EXPORT int64_t JNICALL t->m->classpath->makeString(t, array, offset, length)); } +extern "C" AVIAN_EXPORT int64_t JNICALL + Avian_avian_SystemClassLoader_appLoader(Thread* t, object, uintptr_t*) +{ + return reinterpret_cast(roots(t)->appLoader()); +} + extern "C" AVIAN_EXPORT int64_t JNICALL Avian_avian_SystemClassLoader_findLoadedVMClass(Thread* t, object, diff --git a/src/compile.cpp b/src/compile.cpp index 74605cc9b5..b9899d3124 100644 --- a/src/compile.cpp +++ b/src/compile.cpp @@ -909,14 +909,16 @@ class BootContext { GcTriple* calls, avian::codegen::DelayedPromise* addresses, Zone* zone, - OffsetResolver* resolver) + OffsetResolver* resolver, + JavaVM* hostVM) : protector(t, this), constants(constants), calls(calls), addresses(addresses), addressSentinal(addresses), zone(zone), - resolver(resolver) + resolver(resolver), + hostVM(hostVM) { } @@ -927,6 +929,7 @@ class BootContext { avian::codegen::DelayedPromise* addressSentinal; Zone* zone; OffsetResolver* resolver; + JavaVM* hostVM; }; class Context { @@ -3991,6 +3994,34 @@ void checkField(Thread* t, GcField* field, bool shouldBeStatic) } } +bool isLambda(Thread* t, + GcClassLoader* loader, + GcCharArray* bootstrapArray, + GcInvocation* invocation) +{ + GcMethod* bootstrap = cast(t, + resolve(t, + loader, + invocation->pool(), + bootstrapArray->body()[0], + findMethodInClass, + GcNoSuchMethodError::Type)); + PROTECT(t, bootstrap); + + return vm::strcmp(reinterpret_cast( + "java/lang/invoke/LambdaMetafactory"), + bootstrap->class_()->name()->body().begin()) == 0 + and vm::strcmp(reinterpret_cast("metafactory"), + bootstrap->name()->body().begin()) == 0 + and vm::strcmp( + reinterpret_cast( + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/" + "String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/" + "MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/" + "invoke/MethodType;)Ljava/lang/invoke/CallSite;"), + bootstrap->spec()->body().begin()) == 0; +} + void compile(MyThread* t, Frame* initialFrame, unsigned initialIp, @@ -5054,36 +5085,168 @@ loop: invocation->setClass(t, context->method->class_()); - unsigned index = addDynamic(t, invocation); + BootContext* bc = context->bootContext; + if (bc) { + // When we're AOT-compiling an application, we can't handle + // invokedynamic in general, since it usually implies runtime + // code generation. However, Java 8 lambda expressions are a + // special case for which we can generate code ahead of time. + // + // The only tricky part about it is that the class synthesis + // code resides in LambdaMetaFactory, which means we need to + // call out to a separate Java VM to execute it (the VM we're + // currently executing in won't work because it only knows how + // to compile code for the target machine, which might not be + // the same as the host; plus we don't want to pollute the + // runtime heap image with stuff that's only needed at compile + // time). - GcMethod* template_ = invocation->template_(); - unsigned returnCode = template_->returnCode(); - unsigned rSize = resultSize(t, returnCode); - unsigned parameterFootprint = template_->parameterFootprint(); + GcClass* c = context->method->class_(); + PROTECT(t, c); - // TODO: can we allow tailCalls in general? - // e.g. what happens if the call site is later bound to a method that can't be tail called? - // NOTE: calling isTailCall right now would cause an segfault, since - // invocation->template_()->class_() will be null. - // bool tailCall - // = isTailCall(t, code, ip, context->method, invocation->template_()); - bool tailCall = false; + GcCharArray* bootstrapArray = cast( + t, + cast(t, c->addendum()->bootstrapMethodTable()) + ->body()[invocation->bootstrap()]); + PROTECT(t, bootstrapArray); - // todo: do we need to tell the compiler to add a load barrier - // here for VolatileCallSite instances? + if (isLambda(t, c->loader(), bootstrapArray, invocation)) { + JNIEnv* e; + if (bc->hostVM->vtable->AttachCurrentThread(bc->hostVM, &e, 0) == 0) { + e->vtable->PushLocalFrame(e, 256); - ir::Value* result = c->stackCall( - c->memory(c->memory(c->threadRegister(), ir::Type::object(), - TARGET_THREAD_DYNAMICTABLE), - ir::Type::object(), index * TargetBytesPerWord), - tailCall ? Compiler::TailJump : 0, frame->trace(0, 0), - operandTypeForFieldCode(t, returnCode), - frame->peekMethodArguments(parameterFootprint)); + jclass lmfClass + = e->vtable->FindClass(e, "java/lang/invoke/LambdaMetafactory"); + jmethodID makeLambda = e->vtable->GetStaticMethodID( + e, + lmfClass, + "makeLambda", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/" + "lang/String;Ljava/lang/String;Ljava/lang/String;I)[B"); - frame->popFootprint(parameterFootprint); + GcReference* reference = cast( + t, + singletonObject( + t, invocation->pool(), bootstrapArray->body()[2])); + int kind = reference->kind(); - if (rSize) { - frame->pushReturnValue(returnCode, result); + GcMethod* method + = cast(t, + resolve(t, + c->loader(), + invocation->pool(), + bootstrapArray->body()[2], + findMethodInClass, + GcNoSuchMethodError::Type)); + + jarray lambda = e->vtable->CallStaticObjectMethod( + e, + lmfClass, + makeLambda, + e->vtable->NewStringUTF( + e, + reinterpret_cast( + invocation->template_()->name()->body().begin())), + e->vtable->NewStringUTF( + e, + reinterpret_cast( + invocation->template_()->spec()->body().begin())), + e->vtable->NewStringUTF( + e, + reinterpret_cast( + cast( + t, + singletonObject(t, + invocation->pool(), + bootstrapArray->body()[1])) + ->body() + .begin())), + e->vtable->NewStringUTF( + e, + reinterpret_cast( + method->class_()->name()->body().begin())), + e->vtable->NewStringUTF(e, + reinterpret_cast( + method->name()->body().begin())), + e->vtable->NewStringUTF(e, + reinterpret_cast( + method->spec()->body().begin())), + kind); + + uint8_t* bytes = reinterpret_cast( + e->vtable->GetPrimitiveArrayCritical(e, lambda, 0)); + + GcClass* lambdaClass + = defineClass(t, + roots(t)->appLoader(), + bytes, + e->vtable->GetArrayLength(e, lambda)); + + bc->resolver->addClass( + t, lambdaClass, bytes, e->vtable->GetArrayLength(e, lambda)); + + e->vtable->ReleasePrimitiveArrayCritical(e, lambda, bytes, 0); + + e->vtable->PopLocalFrame(e, 0); + + THREAD_RUNTIME_ARRAY( + t, char, spec, invocation->template_()->spec()->length()); + memcpy(RUNTIME_ARRAY_BODY(spec), + invocation->template_()->spec()->body().begin(), + invocation->template_()->spec()->length()); + + GcMethod* target = resolveMethod( + t, lambdaClass, "make", RUNTIME_ARRAY_BODY(spec)); + + bool tailCall = isTailCall(t, code, ip, context->method, target); + compileDirectInvoke(t, frame, target, tailCall); + } else { + throwNew( + t, GcVirtualMachineError::Type, "unable to attach to host VM"); + } + } else { + throwNew(t, + GcVirtualMachineError::Type, + "invokedynamic not supported for AOT-compiled code except " + "in the case of lambda expressions"); + } + } else { + unsigned index = addDynamic(t, invocation); + + GcMethod* template_ = invocation->template_(); + unsigned returnCode = template_->returnCode(); + unsigned rSize = resultSize(t, returnCode); + unsigned parameterFootprint = template_->parameterFootprint(); + + // TODO: can we allow tailCalls in general? + // e.g. what happens if the call site is later bound to a method that + // can't be tail called? + // NOTE: calling isTailCall right now would cause an segfault, since + // invocation->template_()->class_() will be null. + // bool tailCall + // = isTailCall(t, code, ip, context->method, + // invocation->template_()); + bool tailCall = false; + + // todo: do we need to tell the compiler to add a load barrier + // here for VolatileCallSite instances? + + ir::Value* result + = c->stackCall(c->memory(c->memory(c->threadRegister(), + ir::Type::object(), + TARGET_THREAD_DYNAMICTABLE), + ir::Type::object(), + index * TargetBytesPerWord), + tailCall ? Compiler::TailJump : 0, + frame->trace(0, 0), + operandTypeForFieldCode(t, returnCode), + frame->peekMethodArguments(parameterFootprint)); + + frame->popFootprint(parameterFootprint); + + if (rSize) { + frame->pushReturnValue(returnCode, result); + } } } break; @@ -9134,10 +9297,12 @@ class MyProcessor : public Processor { GcTriple** calls, avian::codegen::DelayedPromise** addresses, GcMethod* method, - OffsetResolver* resolver) + OffsetResolver* resolver, + JavaVM* hostVM) { MyThread* t = static_cast(vmt); - BootContext bootContext(t, *constants, *calls, *addresses, zone, resolver); + BootContext bootContext( + t, *constants, *calls, *addresses, zone, resolver, hostVM); compile(t, &codeAllocator, &bootContext, method); diff --git a/src/interpret.cpp b/src/interpret.cpp index 67871b40d8..7dac9262b7 100644 --- a/src/interpret.cpp +++ b/src/interpret.cpp @@ -3512,7 +3512,8 @@ class MyProcessor : public Processor { GcTriple**, avian::codegen::DelayedPromise**, GcMethod*, - OffsetResolver*) + OffsetResolver*, + JavaVM*) { abort(s); } diff --git a/src/jnienv.cpp b/src/jnienv.cpp index fdfe120347..82eb3d3169 100644 --- a/src/jnienv.cpp +++ b/src/jnienv.cpp @@ -3617,6 +3617,7 @@ extern "C" AVIAN_EXPORT jint JNICALL const char* bootLibraries = 0; const char* classpath = 0; const char* javaHome = AVIAN_JAVA_HOME; + bool reentrant = false; const char* embedPrefix = AVIAN_EMBED_PREFIX; const char* bootClasspathPrepend = ""; const char* bootClasspath = 0; @@ -3667,6 +3668,9 @@ extern "C" AVIAN_EXPORT jint JNICALL } else if (strncmp(p, JAVA_HOME_PROPERTY "=", sizeof(JAVA_HOME_PROPERTY)) == 0) { javaHome = p + sizeof(JAVA_HOME_PROPERTY); + } else if (strncmp(p, REENTRANT_PROPERTY "=", sizeof(REENTRANT_PROPERTY)) + == 0) { + reentrant = strcmp(p + sizeof(REENTRANT_PROPERTY), "true") == 0; } else if (strncmp(p, EMBED_PREFIX_PROPERTY "=", sizeof(EMBED_PREFIX_PROPERTY)) == 0) { @@ -3689,7 +3693,7 @@ extern "C" AVIAN_EXPORT jint JNICALL ++propertyCount; } - System* s = makeSystem(); + System* s = makeSystem(reentrant); Heap* h = makeHeap(s, heapLimit); Classpath* c = makeClasspath(s, h, javaHome, embedPrefix); diff --git a/src/machine.cpp b/src/machine.cpp index 87cdf5f626..4fbae697eb 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -5889,14 +5889,14 @@ GcMethod* getCaller(Thread* t, unsigned target, bool skipMethodInvoke) return v.method; } -object defineClass(Thread* t, - GcClassLoader* loader, - const uint8_t* buffer, - unsigned length) +GcClass* defineClass(Thread* t, + GcClassLoader* loader, + const uint8_t* buffer, + unsigned length) { PROTECT(t, loader); - object c = parseClass(t, loader, buffer, length); + GcClass* c = parseClass(t, loader, buffer, length); // char name[byteArrayLength(t, className(t, c))]; // memcpy(name, &byteArrayBody(t, className(t, c), 0), @@ -5915,7 +5915,7 @@ object defineClass(Thread* t, PROTECT(t, c); - saveLoadedClass(t, loader, cast(t, c)); + saveLoadedClass(t, loader, c); return c; } diff --git a/src/system/windows.cpp b/src/system/windows.cpp index 2d5dfa4707..5833c28274 100644 --- a/src/system/windows.cpp +++ b/src/system/windows.cpp @@ -1018,6 +1018,7 @@ class MySystem : public System { } HANDLE mutex; + bool reentrant; }; } // namespace diff --git a/src/tools/bootimage-generator/main.cpp b/src/tools/bootimage-generator/main.cpp index 9d5ab08769..1b271a22dd 100644 --- a/src/tools/bootimage-generator/main.cpp +++ b/src/tools/bootimage-generator/main.cpp @@ -306,10 +306,414 @@ unsigned targetFieldOffset(Thread* t, GcHashMap* typeMaps, GcField* field) return offset; } +void addClass(Thread* t, + GcClass* c, + const uint8_t* start, + size_t length, + GcHashMap* typeMaps) +{ + PROTECT(t, c); + PROTECT(t, typeMaps); + + { + class Client : public Stream::Client { + public: + Client(Thread* t) : t(t) + { + } + + virtual void NO_RETURN handleError() + { + abort(t); + } + + private: + Thread* t; + } client(t); + + Stream s(&client, start, length); + + uint32_t magic = s.read4(); + expect(t, magic == 0xCAFEBABE); + s.read2(); // minor version + s.read2(); // major version + + unsigned count = s.read2() - 1; + if (count) { + THREAD_RUNTIME_ARRAY(t, Type, types, count + 2); + RUNTIME_ARRAY_BODY(types)[0] = Type_object; + RUNTIME_ARRAY_BODY(types)[1] = Type_intptr_t; + + for (unsigned i = 2; i < count + 2; ++i) { + unsigned constType = s.read1(); + switch (constType) { + case CONSTANT_Class: + case CONSTANT_String: + RUNTIME_ARRAY_BODY(types)[i] = Type_object; + s.skip(2); + break; + + case CONSTANT_Integer: + case CONSTANT_Float: + RUNTIME_ARRAY_BODY(types)[i] = Type_int32_t; + s.skip(4); + break; + + case CONSTANT_NameAndType: + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + RUNTIME_ARRAY_BODY(types)[i] = Type_object; + s.skip(4); + break; + + case CONSTANT_Long: + RUNTIME_ARRAY_BODY(types)[i++] = Type_int64_t; + RUNTIME_ARRAY_BODY(types)[i] = Type_int64_t_pad; + s.skip(8); + break; + + case CONSTANT_Double: + RUNTIME_ARRAY_BODY(types)[i++] = Type_double; + RUNTIME_ARRAY_BODY(types)[i] = Type_double_pad; + s.skip(8); + break; + + case CONSTANT_Utf8: + RUNTIME_ARRAY_BODY(types)[i] = Type_object; + s.skip(s.read2()); + break; + + case CONSTANT_MethodHandle: + RUNTIME_ARRAY_BODY(types)[i] = Type_object; + s.skip(3); + break; + + case CONSTANT_MethodType: + RUNTIME_ARRAY_BODY(types)[i] = Type_object; + s.skip(2); + break; + + case CONSTANT_InvokeDynamic: + RUNTIME_ARRAY_BODY(types)[i] = Type_object; + s.skip(4); + break; + + default: + fprintf(stderr, "unknown class constant: %d\n", constType); + abort(t); + } + } + + GcByteArray* array + = makeByteArray(t, TypeMap::sizeInBytes(count + 2, count + 2)); + + TypeMap* map = new (array->body().begin()) + TypeMap(count + 2, count + 2, count + 2, TypeMap::PoolKind); + + for (unsigned i = 0; i < count + 2; ++i) { + expect(t, i < map->buildFixedSizeInWords); + + map->targetFixedOffsets()[i * BytesPerWord] = i * TargetBytesPerWord; + + init(new (map->fixedFields() + i) Field, + RUNTIME_ARRAY_BODY(types)[i], + i* BytesPerWord, + BytesPerWord, + i* TargetBytesPerWord, + TargetBytesPerWord); + } + + hashMapInsert( + t, + typeMaps, + reinterpret_cast(hashMapFind(t, + roots(t)->poolMap(), + reinterpret_cast(c), + objectHash, + objectEqual)), + reinterpret_cast(array), + objectHash); + } + } + + { + GcByteArray* array = 0; + PROTECT(t, array); + + unsigned count = 0; + GcVector* fields = allFields(t, typeMaps, c, &count, &array); + PROTECT(t, fields); + + THREAD_RUNTIME_ARRAY(t, Field, memberFields, count + 1); + + unsigned memberIndex; + unsigned buildMemberOffset; + unsigned targetMemberOffset; + + if (array) { + memberIndex = 0; + buildMemberOffset = 0; + targetMemberOffset = 0; + + TypeMap* map = reinterpret_cast(array->body().begin()); + + for (unsigned j = 0; j < map->fixedFieldCount; ++j) { + Field* f = map->fixedFields() + j; + + RUNTIME_ARRAY_BODY(memberFields)[memberIndex] = *f; + + targetMemberOffset = f->targetOffset + f->targetSize; + + ++memberIndex; + } + } else { + init(new (RUNTIME_ARRAY_BODY(memberFields)) Field, + Type_object, + 0, + BytesPerWord, + 0, + TargetBytesPerWord); + + memberIndex = 1; + buildMemberOffset = BytesPerWord; + targetMemberOffset = TargetBytesPerWord; + } + + const unsigned StaticHeader = 3; + + THREAD_RUNTIME_ARRAY(t, Field, staticFields, count + StaticHeader); + + init(new (RUNTIME_ARRAY_BODY(staticFields)) Field, + Type_object, + 0, + BytesPerWord, + 0, + TargetBytesPerWord); + + init(new (RUNTIME_ARRAY_BODY(staticFields) + 1) Field, + Type_intptr_t, + BytesPerWord, + BytesPerWord, + TargetBytesPerWord, + TargetBytesPerWord); + + init(new (RUNTIME_ARRAY_BODY(staticFields) + 2) Field, + Type_object, + BytesPerWord * 2, + BytesPerWord, + TargetBytesPerWord * 2, + TargetBytesPerWord); + + unsigned staticIndex = StaticHeader; + unsigned buildStaticOffset = BytesPerWord * StaticHeader; + unsigned targetStaticOffset = TargetBytesPerWord * StaticHeader; + + for (unsigned i = 0; i < fields->size(); ++i) { + GcField* field = cast(t, fields->body()[i]); + if (field) { + unsigned buildSize = fieldSize(t, field->code()); + unsigned targetSize = buildSize; + + Type type; + switch (field->code()) { + case ObjectField: + type = Type_object; + targetSize = TargetBytesPerWord; + break; + + case ByteField: + case BooleanField: + type = Type_int8_t; + break; + + case CharField: + case ShortField: + type = Type_int8_t; + break; + + case FloatField: + case IntField: + type = Type_int32_t; + break; + + case LongField: + case DoubleField: + type = Type_int64_t; + break; + + default: + abort(t); + } + + if (field->flags() & ACC_STATIC) { + targetStaticOffset = pad(targetStaticOffset, targetSize); + + buildStaticOffset = field->offset(); + + init(new (RUNTIME_ARRAY_BODY(staticFields) + staticIndex) Field, + type, + buildStaticOffset, + buildSize, + targetStaticOffset, + targetSize); + + targetStaticOffset += targetSize; + + ++staticIndex; + } else { + targetMemberOffset = pad(targetMemberOffset, targetSize); + + buildMemberOffset = field->offset(); + + init(new (RUNTIME_ARRAY_BODY(memberFields) + memberIndex) Field, + type, + buildMemberOffset, + buildSize, + targetMemberOffset, + targetSize); + + targetMemberOffset += targetSize; + + ++memberIndex; + } + } else { + targetMemberOffset = pad(targetMemberOffset, TargetBytesPerWord); + } + } + + if (hashMapFind( + t, typeMaps, reinterpret_cast(c), objectHash, objectEqual) + == 0) { + GcByteArray* array = makeByteArray( + t, + TypeMap::sizeInBytes(ceilingDivide(c->fixedSize(), BytesPerWord), + memberIndex)); + + TypeMap* map = new (array->body().begin()) + TypeMap(ceilingDivide(c->fixedSize(), BytesPerWord), + ceilingDivide(targetMemberOffset, TargetBytesPerWord), + memberIndex); + + for (unsigned i = 0; i < memberIndex; ++i) { + Field* f = RUNTIME_ARRAY_BODY(memberFields) + i; + + expect(t, f->buildOffset < map->buildFixedSizeInWords * BytesPerWord); + + map->targetFixedOffsets()[f->buildOffset] = f->targetOffset; + + map->fixedFields()[i] = *f; + } + + hashMapInsert(t, + typeMaps, + reinterpret_cast(c), + reinterpret_cast(array), + objectHash); + } + + if (c->staticTable()) { + GcByteArray* array = makeByteArray( + t, + TypeMap::sizeInBytes(singletonCount(t, c->staticTable()) + 2, + staticIndex)); + + TypeMap* map = new (array->body().begin()) + TypeMap(singletonCount(t, c->staticTable()) + 2, + ceilingDivide(targetStaticOffset, TargetBytesPerWord), + staticIndex, + TypeMap::SingletonKind); + + for (unsigned i = 0; i < staticIndex; ++i) { + Field* f = RUNTIME_ARRAY_BODY(staticFields) + i; + + expect(t, f->buildOffset < map->buildFixedSizeInWords * BytesPerWord); + + map->targetFixedOffsets()[f->buildOffset] = f->targetOffset; + + map->fixedFields()[i] = *f; + } + + hashMapInsert(t, + typeMaps, + reinterpret_cast(c->staticTable()), + reinterpret_cast(array), + objectHash); + } + } +} + +void compileMethods(Thread* t, + GcClass* c, + Zone* zone, + GcTriple** constants, + GcTriple** calls, + GcPair** methods, + DelayedPromise** addresses, + OffsetResolver* resolver, + JavaVM* hostVM, + const char* methodName, + const char* methodSpec) +{ + PROTECT(t, c); + + if (GcArray* mtable = cast(t, c->methodTable())) { + PROTECT(t, mtable); + for (unsigned i = 0; i < mtable->length(); ++i) { + GcMethod* method = cast(t, mtable->body()[i]); + if (((methodName == 0 + or ::strcmp(reinterpret_cast(method->name()->body().begin()), + methodName) == 0) + and (methodSpec == 0 + or ::strcmp( + reinterpret_cast(method->spec()->body().begin()), + methodSpec) == 0))) { + if (method->code() or (method->flags() & ACC_NATIVE)) { + PROTECT(t, method); + + t->m->processor->compileMethod( + t, zone, constants, calls, addresses, method, resolver, hostVM); + + if (method->code()) { + *methods = makePair(t, + reinterpret_cast(method), + reinterpret_cast(*methods)); + } + } + + GcMethodAddendum* addendum = method->addendum(); + if (addendum and addendum->exceptionTable()) { + PROTECT(t, addendum); + GcShortArray* exceptionTable + = cast(t, addendum->exceptionTable()); + PROTECT(t, exceptionTable); + + // resolve exception types now to avoid trying to update + // immutable references at runtime + for (unsigned i = 0; i < exceptionTable->length(); ++i) { + uint16_t index = exceptionTable->body()[i] - 1; + + object o = singletonObject(t, addendum->pool(), index); + + if (objectClass(t, o) == type(t, GcReference::Type)) { + o = reinterpret_cast(resolveClass( + t, roots(t)->bootLoader(), cast(t, o)->name())); + + addendum->pool()->setBodyElement( + t, index, reinterpret_cast(o)); + } + } + } + } + } + } +} + GcTriple* makeCodeImage(Thread* t, Zone* zone, BootImage* image, uint8_t* code, + JavaVM* hostVM, const char* className, const char* methodName, const char* methodSpec, @@ -319,20 +723,13 @@ GcTriple* makeCodeImage(Thread* t, t->m->classpath->interceptMethods(t); - GcTriple* constants = 0; - PROTECT(t, constants); - - GcTriple* calls = 0; - PROTECT(t, calls); - - GcPair* methods = 0; - PROTECT(t, methods); - - DelayedPromise* addresses = 0; + GcPair* classes = 0; + PROTECT(t, classes); class MyOffsetResolver : public OffsetResolver { public: - MyOffsetResolver(GcHashMap** typeMaps) : typeMaps(typeMaps) + MyOffsetResolver(GcHashMap** typeMaps, GcPair** classes) + : typeMaps(typeMaps), classes(classes) { } @@ -341,8 +738,22 @@ GcTriple* makeCodeImage(Thread* t, return targetFieldOffset(t, *typeMaps, field); } + virtual void addClass(Thread* t, + GcClass* c, + const uint8_t* start, + size_t length) + { + PROTECT(t, c); + + *classes = makePair( + t, reinterpret_cast(c), reinterpret_cast(*classes)); + + return ::addClass(t, c, start, length, *typeMaps); + } + GcHashMap** typeMaps; - } resolver(&typeMaps); + GcPair** classes; + } resolver(&typeMaps, &classes); Finder* finder = static_cast( roots(t)->bootLoader()->as(t)->finder()); @@ -356,354 +767,32 @@ GcTriple* makeCodeImage(Thread* t, if (false) { fprintf(stderr, "pass 1 %.*s\n", (int)nameSize - 6, name); } + GcClass* c = resolveSystemClass(t, roots(t)->bootLoader(), makeByteArray(t, "%.*s", nameSize - 6, name), true); - PROTECT(t, c); - System::Region* region = finder->find(name); - { - THREAD_RESOURCE(t, System::Region*, region, region->dispose()); + THREAD_RESOURCE(t, System::Region*, region, region->dispose()); - class Client : public Stream::Client { - public: - Client(Thread* t) : t(t) - { - } - - virtual void NO_RETURN handleError() - { - abort(t); - } - - private: - Thread* t; - } client(t); - - Stream s(&client, region->start(), region->length()); - - uint32_t magic = s.read4(); - expect(t, magic == 0xCAFEBABE); - s.read2(); // minor version - s.read2(); // major version - - unsigned count = s.read2() - 1; - if (count) { - THREAD_RUNTIME_ARRAY(t, Type, types, count + 2); - RUNTIME_ARRAY_BODY(types)[0] = Type_object; - RUNTIME_ARRAY_BODY(types)[1] = Type_intptr_t; - - for (unsigned i = 2; i < count + 2; ++i) { - unsigned constType = s.read1(); - switch (constType) { - case CONSTANT_Class: - case CONSTANT_String: - RUNTIME_ARRAY_BODY(types)[i] = Type_object; - s.skip(2); - break; - - case CONSTANT_Integer: - case CONSTANT_Float: - RUNTIME_ARRAY_BODY(types)[i] = Type_int32_t; - s.skip(4); - break; - - case CONSTANT_NameAndType: - case CONSTANT_Fieldref: - case CONSTANT_Methodref: - case CONSTANT_InterfaceMethodref: - RUNTIME_ARRAY_BODY(types)[i] = Type_object; - s.skip(4); - break; - - case CONSTANT_Long: - RUNTIME_ARRAY_BODY(types)[i++] = Type_int64_t; - RUNTIME_ARRAY_BODY(types)[i] = Type_int64_t_pad; - s.skip(8); - break; - - case CONSTANT_Double: - RUNTIME_ARRAY_BODY(types)[i++] = Type_double; - RUNTIME_ARRAY_BODY(types)[i] = Type_double_pad; - s.skip(8); - break; - - case CONSTANT_Utf8: - RUNTIME_ARRAY_BODY(types)[i] = Type_object; - s.skip(s.read2()); - break; - - - - case CONSTANT_MethodHandle: - RUNTIME_ARRAY_BODY(types)[i] = Type_object; - s.skip(3); - break; - - case CONSTANT_MethodType: - RUNTIME_ARRAY_BODY(types)[i] = Type_object; - s.skip(2); - break; - - case CONSTANT_InvokeDynamic: - RUNTIME_ARRAY_BODY(types)[i] = Type_object; - s.skip(4); - break; - - default: - fprintf(stderr, "unknown class constant: %d\n", constType); - abort(t); - } - } - - GcByteArray* array - = makeByteArray(t, TypeMap::sizeInBytes(count + 2, count + 2)); - - TypeMap* map = new (array->body().begin()) - TypeMap(count + 2, count + 2, count + 2, TypeMap::PoolKind); - - for (unsigned i = 0; i < count + 2; ++i) { - expect(t, i < map->buildFixedSizeInWords); - - map->targetFixedOffsets()[i * BytesPerWord] = i - * TargetBytesPerWord; - - init(new (map->fixedFields() + i) Field, - RUNTIME_ARRAY_BODY(types)[i], - i* BytesPerWord, - BytesPerWord, - i* TargetBytesPerWord, - TargetBytesPerWord); - } - - hashMapInsert( - t, - typeMaps, - reinterpret_cast(hashMapFind(t, - roots(t)->poolMap(), - reinterpret_cast(c), - objectHash, - objectEqual)), - reinterpret_cast(array), - objectHash); - } - } - - { - GcByteArray* array = 0; - PROTECT(t, array); - - unsigned count = 0; - GcVector* fields = allFields(t, typeMaps, c, &count, &array); - PROTECT(t, fields); - - THREAD_RUNTIME_ARRAY(t, Field, memberFields, count + 1); - - unsigned memberIndex; - unsigned buildMemberOffset; - unsigned targetMemberOffset; - - if (array) { - memberIndex = 0; - buildMemberOffset = 0; - targetMemberOffset = 0; - - TypeMap* map = reinterpret_cast(array->body().begin()); - - for (unsigned j = 0; j < map->fixedFieldCount; ++j) { - Field* f = map->fixedFields() + j; - - RUNTIME_ARRAY_BODY(memberFields)[memberIndex] = *f; - - targetMemberOffset = f->targetOffset + f->targetSize; - - ++memberIndex; - } - } else { - init(new (RUNTIME_ARRAY_BODY(memberFields)) Field, - Type_object, - 0, - BytesPerWord, - 0, - TargetBytesPerWord); - - memberIndex = 1; - buildMemberOffset = BytesPerWord; - targetMemberOffset = TargetBytesPerWord; - } - - const unsigned StaticHeader = 3; - - THREAD_RUNTIME_ARRAY(t, Field, staticFields, count + StaticHeader); - - init(new (RUNTIME_ARRAY_BODY(staticFields)) Field, - Type_object, - 0, - BytesPerWord, - 0, - TargetBytesPerWord); - - init(new (RUNTIME_ARRAY_BODY(staticFields) + 1) Field, - Type_intptr_t, - BytesPerWord, - BytesPerWord, - TargetBytesPerWord, - TargetBytesPerWord); - - init(new (RUNTIME_ARRAY_BODY(staticFields) + 2) Field, - Type_object, - BytesPerWord * 2, - BytesPerWord, - TargetBytesPerWord * 2, - TargetBytesPerWord); - - unsigned staticIndex = StaticHeader; - unsigned buildStaticOffset = BytesPerWord * StaticHeader; - unsigned targetStaticOffset = TargetBytesPerWord * StaticHeader; - - for (unsigned i = 0; i < fields->size(); ++i) { - GcField* field = cast(t, fields->body()[i]); - if (field) { - unsigned buildSize = fieldSize(t, field->code()); - unsigned targetSize = buildSize; - - Type type; - switch (field->code()) { - case ObjectField: - type = Type_object; - targetSize = TargetBytesPerWord; - break; - - case ByteField: - case BooleanField: - type = Type_int8_t; - break; - - case CharField: - case ShortField: - type = Type_int8_t; - break; - - case FloatField: - case IntField: - type = Type_int32_t; - break; - - case LongField: - case DoubleField: - type = Type_int64_t; - break; - - default: - abort(t); - } - - if (field->flags() & ACC_STATIC) { - targetStaticOffset = pad(targetStaticOffset, targetSize); - - buildStaticOffset = field->offset(); - - init(new (RUNTIME_ARRAY_BODY(staticFields) + staticIndex) Field, - type, - buildStaticOffset, - buildSize, - targetStaticOffset, - targetSize); - - targetStaticOffset += targetSize; - - ++staticIndex; - } else { - targetMemberOffset = pad(targetMemberOffset, targetSize); - - buildMemberOffset = field->offset(); - - init(new (RUNTIME_ARRAY_BODY(memberFields) + memberIndex) Field, - type, - buildMemberOffset, - buildSize, - targetMemberOffset, - targetSize); - - targetMemberOffset += targetSize; - - ++memberIndex; - } - } else { - targetMemberOffset = pad(targetMemberOffset, TargetBytesPerWord); - } - } - - if (hashMapFind(t, - typeMaps, - reinterpret_cast(c), - objectHash, - objectEqual) == 0) { - GcByteArray* array = makeByteArray( - t, - TypeMap::sizeInBytes(ceilingDivide(c->fixedSize(), BytesPerWord), - memberIndex)); - - TypeMap* map = new (array->body().begin()) - TypeMap(ceilingDivide(c->fixedSize(), BytesPerWord), - ceilingDivide(targetMemberOffset, TargetBytesPerWord), - memberIndex); - - for (unsigned i = 0; i < memberIndex; ++i) { - Field* f = RUNTIME_ARRAY_BODY(memberFields) + i; - - expect(t, - f->buildOffset < map->buildFixedSizeInWords * BytesPerWord); - - map->targetFixedOffsets()[f->buildOffset] = f->targetOffset; - - map->fixedFields()[i] = *f; - } - - hashMapInsert(t, - typeMaps, - reinterpret_cast(c), - reinterpret_cast(array), - objectHash); - } - - if (c->staticTable()) { - GcByteArray* array = makeByteArray( - t, - TypeMap::sizeInBytes(singletonCount(t, c->staticTable()) + 2, - staticIndex)); - - TypeMap* map = new (array->body().begin()) - TypeMap(singletonCount(t, c->staticTable()) + 2, - ceilingDivide(targetStaticOffset, TargetBytesPerWord), - staticIndex, - TypeMap::SingletonKind); - - for (unsigned i = 0; i < staticIndex; ++i) { - Field* f = RUNTIME_ARRAY_BODY(staticFields) + i; - - expect(t, - f->buildOffset < map->buildFixedSizeInWords * BytesPerWord); - - map->targetFixedOffsets()[f->buildOffset] = f->targetOffset; - - map->fixedFields()[i] = *f; - } - - hashMapInsert(t, - typeMaps, - reinterpret_cast(c->staticTable()), - reinterpret_cast(array), - objectHash); - } - } + addClass(t, c, region->start(), region->length(), typeMaps); } } + GcTriple* constants = 0; + PROTECT(t, constants); + + GcTriple* calls = 0; + PROTECT(t, calls); + + GcPair* methods = 0; + PROTECT(t, methods); + + DelayedPromise* addresses = 0; + for (Finder::Iterator it(finder); it.hasMore();) { size_t nameSize = 0; const char* name = it.next(&nameSize); @@ -713,73 +802,39 @@ GcTriple* makeCodeImage(Thread* t, if (false) { fprintf(stderr, "pass 2 %.*s\n", (int)nameSize - 6, name); } - GcClass* c = 0; - PROTECT(t, c); - c = resolveSystemClass(t, - roots(t)->bootLoader(), - makeByteArray(t, "%.*s", nameSize - 6, name), - true); + GcClass* c + = resolveSystemClass(t, + roots(t)->bootLoader(), + makeByteArray(t, "%.*s", nameSize - 6, name), + true); - if (GcArray* mtable = cast(t, c->methodTable())) { - PROTECT(t, mtable); - for (unsigned i = 0; i < mtable->length(); ++i) { - GcMethod* method = cast(t, mtable->body()[i]); - if (((methodName == 0 - or ::strcmp( - reinterpret_cast(method->name()->body().begin()), - methodName) == 0) - and (methodSpec == 0 - or ::strcmp(reinterpret_cast( - method->spec()->body().begin()), - methodSpec) == 0))) { - if (method->code() or (method->flags() & ACC_NATIVE)) { - PROTECT(t, method); + classes = makePair( + t, reinterpret_cast(c), reinterpret_cast(classes)); + } + } - t->m->processor->compileMethod( - t, - zone, - reinterpret_cast(&constants), - reinterpret_cast(&calls), - &addresses, - method, - &resolver); + // Each method compilation may result in the creation of new, + // synthetic classes (e.g. for lambda expressions), so we must + // iterate until we've visited them all: + while (classes) { + GcPair* myClasses = classes; + PROTECT(t, myClasses); - if (method->code()) { - methods = makePair(t, - reinterpret_cast(method), - reinterpret_cast(methods)); - } - } + classes = 0; - GcMethodAddendum* addendum = method->addendum(); - if (addendum and addendum->exceptionTable()) { - PROTECT(t, addendum); - GcShortArray* exceptionTable - = cast(t, addendum->exceptionTable()); - PROTECT(t, exceptionTable); - - // resolve exception types now to avoid trying to update - // immutable references at runtime - for (unsigned i = 0; i < exceptionTable->length(); ++i) { - uint16_t index = exceptionTable->body()[i] - 1; - - object o = singletonObject(t, addendum->pool(), index); - - if (objectClass(t, o) == type(t, GcReference::Type)) { - o = reinterpret_cast( - resolveClass(t, - roots(t)->bootLoader(), - cast(t, o)->name())); - - addendum->pool()->setBodyElement( - t, index, reinterpret_cast(o)); - } - } - } - } - } - } + for (; myClasses; myClasses = cast(t, myClasses->second())) { + compileMethods(t, + cast(t, myClasses->first()), + zone, + &constants, + &calls, + &methods, + &addresses, + &resolver, + hostVM, + methodName, + methodSpec); } } @@ -1436,6 +1491,7 @@ void writeBootImage2(Thread* t, OutputStream* codeOutput, BootImage* image, uint8_t* code, + JavaVM* hostVM, const char* className, const char* methodName, const char* methodSpec, @@ -1656,8 +1712,15 @@ void writeBootImage2(Thread* t, objectHash); } - constants = makeCodeImage( - t, &zone, image, code, className, methodName, methodSpec, typeMaps); + constants = makeCodeImage(t, + &zone, + image, + code, + hostVM, + className, + methodName, + methodSpec, + typeMaps); PROTECT(t, constants); @@ -1927,21 +1990,23 @@ uint64_t writeBootImage(Thread* t, uintptr_t* arguments) OutputStream* codeOutput = reinterpret_cast(arguments[1]); BootImage* image = reinterpret_cast(arguments[2]); uint8_t* code = reinterpret_cast(arguments[3]); - const char* className = reinterpret_cast(arguments[4]); - const char* methodName = reinterpret_cast(arguments[5]); - const char* methodSpec = reinterpret_cast(arguments[6]); + JavaVM* hostVM = reinterpret_cast(arguments[4]); + const char* className = reinterpret_cast(arguments[5]); + const char* methodName = reinterpret_cast(arguments[6]); + const char* methodSpec = reinterpret_cast(arguments[7]); - const char* bootimageStart = reinterpret_cast(arguments[7]); - const char* bootimageEnd = reinterpret_cast(arguments[8]); - const char* codeimageStart = reinterpret_cast(arguments[9]); - const char* codeimageEnd = reinterpret_cast(arguments[10]); - bool useLZMA = arguments[11]; + const char* bootimageStart = reinterpret_cast(arguments[8]); + const char* bootimageEnd = reinterpret_cast(arguments[9]); + const char* codeimageStart = reinterpret_cast(arguments[10]); + const char* codeimageEnd = reinterpret_cast(arguments[11]); + bool useLZMA = arguments[12]; writeBootImage2(t, bootimageOutput, codeOutput, image, code, + hostVM, className, methodName, methodSpec, @@ -1969,6 +2034,8 @@ class Arguments { const char* bootimage; const char* codeimage; + const char* hostvm; + char* entryClass; char* entryMethod; char* entrySpec; @@ -2008,6 +2075,7 @@ class Arguments { Arg classpath(parser, true, "cp", ""); Arg bootimage(parser, true, "bootimage", ""); Arg codeimage(parser, true, "codeimage", ""); + Arg hostvm(parser, false, "hostvm", ""); Arg entry( parser, false, "entry", "[.[]]"); Arg bootimageSymbols(parser, @@ -2028,6 +2096,7 @@ class Arguments { this->classpath = classpath.value; this->bootimage = bootimage.value; this->codeimage = codeimage.value; + this->hostvm = hostvm.value; this->useLZMA = useLZMA.value != 0; if (entry.value) { @@ -2100,6 +2169,7 @@ class Arguments { "classpath = %s\n" "bootimage = %s\n" "codeimage = %s\n" + "hostvm = %s\n" "entryClass = %s\n" "entryMethod = %s\n" "entrySpec = %s\n" @@ -2110,6 +2180,7 @@ class Arguments { classpath, bootimage, codeimage, + hostvm, entryClass, entryMethod, entrySpec, @@ -2168,10 +2239,66 @@ int main(int ac, const char** av) return -1; } + JavaVM* hostVM = 0; + System::Library* hostVMLibrary = 0; + if (args.hostvm) { + if (s->success(s->load(&hostVMLibrary, args.hostvm))) { + typedef jint(JNICALL * CreateVM)(Machine**, Thread**, void*); + const char* name = "JNI_CreateJavaVM"; + CreateVM createVM + = reinterpret_cast(hostVMLibrary->resolve(name)); + + if (createVM) { + JavaVMInitArgs vmArgs; + vmArgs.version = JNI_VERSION_1_6; + vmArgs.nOptions = 2; + vmArgs.ignoreUnrecognized = JNI_TRUE; + +#define CLASSPATH_PROPERTY "-Xbootclasspath:" + + const char* classpath = args.classpath; + size_t classpathSize = strlen(classpath); + size_t classpathPropertyBufferSize = sizeof(CLASSPATH_PROPERTY) + + classpathSize; + + RUNTIME_ARRAY( + char, classpathPropertyBuffer, classpathPropertyBufferSize); + memcpy(RUNTIME_ARRAY_BODY(classpathPropertyBuffer), + CLASSPATH_PROPERTY, + sizeof(CLASSPATH_PROPERTY) - 1); + memcpy(RUNTIME_ARRAY_BODY(classpathPropertyBuffer) + + sizeof(CLASSPATH_PROPERTY) - 1, + classpath, + classpathSize + 1); + + JavaVMOption options[2]; + options[0].optionString = RUNTIME_ARRAY_BODY(classpathPropertyBuffer); + options[1].optionString = const_cast("-Davian.reentrant=true"); + + vmArgs.options = options; + + Thread* dummy; + if (JNI_OK != createVM(&hostVM, &dummy, &vmArgs)) { + fprintf(stderr, "unable to initialize host VM\n"); + hostVMLibrary->disposeAll(); + return -1; + } + } else { + fprintf(stderr, "unable to find %s in %s\n", name, args.hostvm); + hostVMLibrary->disposeAll(); + return -1; + } + } else { + fprintf(stderr, "unable to open %s\n", args.hostvm); + return -1; + } + } + uintptr_t arguments[] = {reinterpret_cast(&bootimageOutput), reinterpret_cast(&codeOutput), reinterpret_cast(&image), reinterpret_cast(code.begin()), + reinterpret_cast(hostVM), reinterpret_cast(args.entryClass), reinterpret_cast(args.entryMethod), reinterpret_cast(args.entrySpec), @@ -2183,6 +2310,11 @@ int main(int ac, const char** av) run(t, writeBootImage, arguments); + if (hostVM) { + hostVM->vtable->DestroyJavaVM(hostVM); + hostVMLibrary->disposeAll(); + } + if (t->exception) { printTrace(t, t->exception); return -1; From 809feace0fd77800251f7f6a16f2b5c42d395aa6 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Mon, 28 Sep 2015 02:15:49 +0000 Subject: [PATCH 04/11] reorganize docker files, upgrade to java 8, gcc 4.9 --- docker/Dockerfile | 70 +++++++++++++++++++++++++++++++++++---- docker/i386/Dockerfile | 12 ------- docker/openjdk/Dockerfile | 17 ---------- docker/windows/Dockerfile | 23 ------------- 4 files changed, 63 insertions(+), 59 deletions(-) delete mode 100644 docker/i386/Dockerfile delete mode 100644 docker/openjdk/Dockerfile delete mode 100644 docker/windows/Dockerfile diff --git a/docker/Dockerfile b/docker/Dockerfile index 04796d08e8..f4aaa75032 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,13 +1,18 @@ FROM debian:jessie MAINTAINER Joshua Warner, joshuawarner32@gmail.com +RUN echo 'deb http://http.debian.net/debian jessie-backports main' >> /etc/apt/sources.list && \ + echo 'deb-src http://http.debian.net/debian jessie-backports main' >> /etc/apt/sources.list && \ + dpkg --add-architecture i386 && \ + apt-get update && \ + mkdir /var/src/ + # Install base dependencies and build tools, general debugging tools -RUN apt-get update && \ - apt-get install -y \ +RUN apt-get install -y \ build-essential \ - g++-4.8 \ + g++-4.9 \ zlib1g-dev \ - openjdk-7-jdk \ + openjdk-8-jdk \ locales \ --no-install-recommends && \ apt-get clean all @@ -20,8 +25,59 @@ RUN dpkg-reconfigure locales && \ ENV LC_ALL C.UTF-8 # Set JAVA_HOME for avian's benefit -ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64 +ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 + +# Add i386 libraries +RUN apt-get install -y \ + libc6-dev-i386 && \ + apt-get download \ + zlib1g-dev:i386 && \ + dpkg -x *.deb / && \ + rm *.deb && \ + apt-get clean all + +# Install cross-compile toolchain and emulator for testing +RUN apt-get install -y \ + mingw-w64 \ + wget \ + unzip \ + --no-install-recommends && \ + apt-get clean all + +# Download win32 and win64 adjacent to avian +RUN cd /var/src/ && \ + wget https://github.com/ReadyTalk/win32/archive/master.zip -O win32.zip && \ + unzip win32.zip && \ + rm win32.zip && \ + mv win32-* win32 && \ + wget https://github.com/ReadyTalk/win64/archive/master.zip -O win64.zip && \ + unzip win64.zip && \ + rm win64.zip && \ + mv win64-* win64 + +# Add openjdk-src stuff +RUN apt-get install -y \ + libcups2-dev \ + libgconf2-dev && \ + mkdir /var/src/openjdk/ && \ + cd /var/src/openjdk/ && \ + apt-get source openjdk-8 && \ + apt-get clean all && \ + find /var/src/openjdk && \ + rm /var/src/openjdk/*.gz /var/src/openjdk/*.dsc && \ + cd /var/src/openjdk/ && \ + tar -xf /var/src/openjdk/openjdk*/jdk.tar.xz && \ + mv /var/src/openjdk/jdk-*/src /var/src/openjdk-src && \ + rm -rf /var/src/openjdk && \ + apt-get clean all + +# Download/extract lzma source +RUN mkdir /var/src/lzma && \ + cd /var/src/lzma && \ + apt-get install -y p7zip && \ + wget http://www.7-zip.org/a/lzma1507.7z -O lzma.7z && \ + p7zip -d lzma.7z # Avian build location -VOLUME /var/avian -WORKDIR /var/avian +VOLUME /var/src/avian +WORKDIR /var/src/avian diff --git a/docker/i386/Dockerfile b/docker/i386/Dockerfile deleted file mode 100644 index eb9e5c2b0c..0000000000 --- a/docker/i386/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM joshuawarner32/avian-build -MAINTAINER Joshua Warner, joshuawarner32@gmail.com - -RUN dpkg --add-architecture i386 && \ - apt-get update && \ - apt-get install -y \ - libc6-dev-i386 && \ - apt-get download \ - zlib1g-dev:i386 && \ - dpkg -x *.deb / && \ - rm *.deb && \ - apt-get clean all diff --git a/docker/openjdk/Dockerfile b/docker/openjdk/Dockerfile deleted file mode 100644 index bda3a5d95d..0000000000 --- a/docker/openjdk/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM joshuawarner32/avian-build-i386 -MAINTAINER Joshua Warner, joshuawarner32@gmail.com - -RUN echo 'deb-src http://http.debian.net/debian jessie main' >> /etc/apt/sources.list && \ - apt-get update && \ - apt-get install -y \ - libcups2-dev \ - libgconf2-dev && \ - mkdir /var/openjdk/ && \ - cd /var/openjdk/ && \ - apt-get source openjdk-7 && \ - apt-get clean all && \ - rm /var/openjdk/*.gz /var/openjdk/*.dsc && \ - cd /var/openjdk/ && \ - tar -xzf /var/openjdk/openjdk*/jdk.tar.gz && \ - mv /var/openjdk/jdk-*/src /var/openjdk-src && \ - rm -rf /var/openjdk \ No newline at end of file diff --git a/docker/windows/Dockerfile b/docker/windows/Dockerfile deleted file mode 100644 index 88f039846a..0000000000 --- a/docker/windows/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM joshuawarner32/avian-build -MAINTAINER Joshua Warner, joshuawarner32@gmail.com - -# Install cross-compile toolchain and emulator for testing -RUN apt-get update && \ - apt-get install -y \ - mingw-w64 \ - wget \ - unzip \ - --no-install-recommends && \ - apt-get clean all - -# Download win32 and win64 adjacent to avian -RUN cd .. && \ - wget https://github.com/ReadyTalk/win32/archive/master.zip -O win32.zip && \ - unzip win32.zip && \ - rm win32.zip && \ - mv win32-* win32 && \ - wget https://github.com/ReadyTalk/win64/archive/master.zip -O win64.zip && \ - unzip win64.zip && \ - rm win64.zip && \ - mv win64-* win64 - From 5399c6bdbb349e2c699d440daa8ba1b627168d35 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 12 Oct 2015 12:15:14 -0600 Subject: [PATCH 05/11] disable TravisCI artifact publication This stopped working recently, apparently due to obsolete jfrog.org credentials. Since no-one is actually using these artifacts as far as we are aware, I'm just disabling the task so the build doesn't continue to fail. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d18b9c7fd3..42265efb47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,9 @@ os: env: matrix: - BUILD_STEP="" - - BUILD_STEP="PUBLISH" +# disabled until/unless jfrog.org credentials are updated and someone +# decides they care about published artifacts: +# - BUILD_STEP="PUBLISH" global: - TERM=dumb - secure: rh1utD4shKmYtokItuRYEF9WsfTnvZO5XqnTU4DHTS7quHHgLihtOO2/3+B+2W2hEd5Obr2or8zx+zmzWcNUyLokZ0j/FRLWSScNkLzTtm12pupLrncY+/g1NIdfbhn+OLRIzBz6zB6m6a2qWFEJ+bScUNGD/7wZVtzkujqlDEE= From 9abba8fe241d435a0a170e8aa35743da911fcf98 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 19 Oct 2015 15:10:53 -0600 Subject: [PATCH 06/11] add bootimage-test option to makefile This option specifies that the test classes should be AOT-compiled along with the class library, which allows us to test that everything works in AOT-compiled form as well as JIT-compiled form. This is primarily motivated by the need to test d906db6 (support for AOT-compilation of Java 8 lambda expressions). Note that I had to tweak Misc because it tested something that couldn't be done in an AOT build without a lot of extra work, and SystemClassLoader.getPackage because it was returning null when the requested package could not be populated with JAR manifest metadata. Technically, we probably *should* return null for packages that don't exist at all (in the sense that no classes have been loaded from such a package), but tracking that kind of thing seems like more trouble than it's worth unless someone complains about it. --- classpath/avian/SystemClassLoader.java | 2 ++ makefile | 16 +++++++++++++--- test/Misc.java | 15 ++++++++++++--- test/ci.sh | 1 + 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/classpath/avian/SystemClassLoader.java b/classpath/avian/SystemClassLoader.java index 84b76c9b92..5dd2ee4352 100644 --- a/classpath/avian/SystemClassLoader.java +++ b/classpath/avian/SystemClassLoader.java @@ -83,6 +83,8 @@ public class SystemClassLoader extends ClassLoader { if (source != null) { // todo: load attributes from JAR manifest definePackage(name, null, null, null, null, null, null, null); + } else { + definePackage(name, null, null, null, null, null, null, null); } } diff --git a/makefile b/makefile index 3fd2ad2a08..17353594ba 100755 --- a/makefile +++ b/makefile @@ -45,6 +45,11 @@ ifneq ($(lzma),) endif ifeq ($(bootimage),true) options := $(options)-bootimage + ifeq ($(bootimage-test),true) + # this option indicates that we should AOT-compile the test + # classes as well as the class library + options := $(options)-test + endif endif ifeq ($(tails),true) options := $(options)-tails @@ -109,6 +114,12 @@ wp8 ?= $(root)/wp8 classpath = avian +bootimage-classpath = $(classpath-build) + +ifeq ($(bootimage-test),true) + bootimage-classpath = $(classpath-build):$(test-build) +endif + test-executable = $(shell pwd)/$(executable) boot-classpath = $(classpath-build) embed-prefix = /avian-embedded @@ -1647,7 +1658,6 @@ debug: build vg: build $(library-path) $(vg) $(test-executable) $(test-args) - .PHONY: test test: build-test run-test @@ -2066,9 +2076,9 @@ else endif $(bootimage-object) $(codeimage-object): $(bootimage-generator) \ - $(classpath-jar-dep) + $(classpath-jar-dep) $(test-dep) @echo "generating bootimage and codeimage binaries from $(classpath-build) using $(<)" - $(<) -cp $(classpath-build) -bootimage $(bootimage-object) -codeimage $(codeimage-object) \ + $(<) -cp $(bootimage-classpath) -bootimage $(bootimage-object) -codeimage $(codeimage-object) \ -bootimage-symbols $(bootimage-symbols) \ -codeimage-symbols $(codeimage-symbols) \ -hostvm $(host-vm) diff --git a/test/Misc.java b/test/Misc.java index ec3bd0bd25..42d452a37a 100644 --- a/test/Misc.java +++ b/test/Misc.java @@ -291,9 +291,18 @@ public class Misc { test = true; } } - expect(count == 2); - expect(test); - expect(extraDir); + // This test is only relevant if multi-classpath-test.txt + // actually exists in somewhere under the classpath from which + // Misc.class was loaded. Since we run this test from an + // AOT-compiled boot image as well as straight from the + // filesystem, and the boot image does not contain + // multi-classpath-test.txt, we'll skip the test if it's not + // present. + if (count != 0) { + expect(count == 2); + expect(test); + expect(extraDir); + } } catch (IOException e) { throw new RuntimeException(e); } diff --git a/test/ci.sh b/test/ci.sh index 69c89d273f..c52ca7cf01 100755 --- a/test/ci.sh +++ b/test/ci.sh @@ -97,6 +97,7 @@ else if has_flag openjdk-src || ! has_flag openjdk; then run make ${flags} mode=debug bootimage=true ${make_target} run make ${flags} bootimage=true ${make_target} + run make ${flags} bootimage=true bootimage-test=true ${make_target} fi if ! has_flag openjdk && ! has_flag android && ! has_flag arch; then From b351c9d9618061b02c3d43328e860fc84b549edd Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 20 Oct 2015 08:33:17 -0600 Subject: [PATCH 07/11] exit with error if bootimage-test=true specified without bootimage=true --- makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/makefile b/makefile index 17353594ba..dd0348d30b 100755 --- a/makefile +++ b/makefile @@ -97,6 +97,12 @@ ifeq ($(platform),ios) endif endif +ifeq ($(bootimage-test),true) + ifneq ($(bootimage),true) + x := $(error "bootimage-test=true only works when bootimage=true") + endif +endif + aot-only = false root := $(shell (cd .. && pwd)) build = build/$(platform)-$(arch)$(options) @@ -2136,7 +2142,7 @@ $(unittest-executable): $(unittest-executable-objects) $(bootimage-generator): $(bootimage-generator-objects) $(vm-objects) echo building $(bootimage-generator) arch=$(build-arch) platform=$(bootimage-platform) - $(MAKE) process=interpret bootimage= mode=$(mode) + $(MAKE) process=interpret bootimage= bootimage-test= mode=$(mode) $(MAKE) mode=$(mode) \ build=$(host-build-root) \ arch=$(build-arch) \ From dc010f9a92a43e461f9e0f0d1966c2b2fbb8e741 Mon Sep 17 00:00:00 2001 From: Benjamin Stadin Date: Sun, 1 Nov 2015 20:36:47 +0100 Subject: [PATCH 08/11] - fixed building for iOS 9 - handle sim flag to better distinguish between ios simulator / ios - added ios_deployment_target to set ios min version - default to 64 bit build for iOS --- makefile | 86 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/makefile b/makefile index dd0348d30b..a7f39a8879 100755 --- a/makefile +++ b/makefile @@ -763,13 +763,14 @@ ifeq ($(kernel),darwin) rpath = ifeq ($(platform),ios) - ifeq (,$(filter arm arm64,$(arch))) + ifeq ($(sim),true) target = iPhoneSimulator sdk = iphonesimulator$(ios-version) ifeq ($(arch),i386) arch-flag = -arch i386 else arch-flag = -arch x86_64 + arch = x86_64 endif release = Release-iphonesimulator else @@ -779,6 +780,7 @@ ifeq ($(kernel),darwin) arch-flag = -arch armv7 else arch-flag = -arch arm64 + arch = arm64 endif release = Release-iphoneos endif @@ -787,7 +789,9 @@ ifeq ($(kernel),darwin) sdk-dir = $(platform-dir)/Developer/SDKs ios-version := $(shell \ - if test -d $(sdk-dir)/$(target)8.3.sdk; then echo 8.3; \ + if test -L $(sdk-dir)/$(target)9.1.sdk; then echo 9.1; \ + elif test -L $(sdk-dir)/$(target)9.0.sdk; then echo 9.0; \ + elif test -d $(sdk-dir)/$(target)8.3.sdk; then echo 8.3; \ elif test -d $(sdk-dir)/$(target)8.2.sdk; then echo 8.2; \ elif test -d $(sdk-dir)/$(target)8.1.sdk; then echo 8.1; \ elif test -d $(sdk-dir)/$(target)8.0.sdk; then echo 8.0; \ @@ -828,39 +832,57 @@ ifeq ($(kernel),darwin) cflags += $(flags) asmflags += $(flags) lflags += $(flags) - endif - - ifeq ($(arch),i386) - ifeq ($(platform),ios) - classpath-extra-cflags += \ - -arch i386 -miphoneos-version-min=$(ios-version) - cflags += -arch i386 -miphoneos-version-min=$(ios-version) - asmflags += -arch i386 -miphoneos-version-min=$(ios-version) - lflags += -arch i386 -miphoneos-version-min=$(ios-version) + + ios-version-min=$(ios-version) + ifdef ios_deployment_target + ios-version-min=ios_deployment_target + endif + + ifeq ($(sim),true) + ifeq ($(arch),x86_64) + classpath-extra-cflags += \ + -arch x86_64 -miphoneos-version-min=$(ios-version-min) + cflags += -arch x86_64 -miphoneos-version-min=$(ios-version-min) + asmflags += -arch x86_64 -miphoneos-version-min=$(ios-version-min) + lflags += -arch x86_64 -miphoneos-version-min=$(ios-version-min) + else + classpath-extra-cflags += \ + -arch i386 -miphoneos-version-min=$(ios-version-min) + cflags += -arch i386 -miphoneos-version-min=$(ios-version-min) + asmflags += -arch i386 -miphoneos-version-min=$(ios-version-min) + lflags += -arch i386 -miphoneos-version-min=$(ios-version-min) + endif else - classpath-extra-cflags += \ - -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} - cflags += -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} - asmflags += -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} - lflags += -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} + ifeq ($(arch),arm64) + classpath-extra-cflags += \ + -arch arm64 -miphoneos-version-min=$(ios-version-min) + cflags += -arch arm64 -miphoneos-version-min=$(ios-version-min) + asmflags += -arch arm64 -miphoneos-version-min=$(ios-version-min) + lflags += -arch arm64 -miphoneos-version-min=$(ios-version-min) + else + classpath-extra-cflags += \ + -arch armv7 -miphoneos-version-min=$(ios-version-min) + cflags += -arch armv7 -miphoneos-version-min=$(ios-version-min) + asmflags += -arch armv7 -miphoneos-version-min=$(ios-version-min) + lflags += -arch armv7 -miphoneos-version-min=$(ios-version-min) + endif + endif + else # not ios + ifeq ($(arch),i386) + classpath-extra-cflags += \ + -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} + cflags += -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} + asmflags += -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} + lflags += -arch i386 -mmacosx-version-min=${OSX_SDK_VERSION} + endif + + ifeq ($(arch),x86_64) + classpath-extra-cflags += -arch x86_64 + cflags += -arch x86_64 + asmflags += -arch x86_64 + lflags += -arch x86_64 endif endif - - ifeq ($(arch),x86_64) - ifeq ($(platform),ios) - classpath-extra-cflags += \ - -arch x86_64 -miphoneos-version-min=$(ios-version) - cflags += -arch x86_64 -miphoneos-version-min=$(ios-version) - asmflags += -arch x86_64 -miphoneos-version-min=$(ios-version) - lflags += -arch x86_64 -miphoneos-version-min=$(ios-version) - else - classpath-extra-cflags += -arch x86_64 - cflags += -arch x86_64 - asmflags += -arch x86_64 - lflags += -arch x86_64 - endif - endif - cflags += -I$(JAVA_HOME)/include/darwin endif From 19945346fc64448555dbf4c95b1075a3136bf3ee Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 9 Nov 2015 16:33:01 -0700 Subject: [PATCH 09/11] update README to remove references to oss.readytalk.com --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 91bbcc0113..18dd2576db 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Introduction Avian is a lightweight virtual machine and class library designed to provide a useful subset of Java's features, suitable for building self-contained applications. More information is available at the -project [web site](http://oss.readytalk.com/avian). +project [web site](http://readytalk.github.io/avian). If you have any trouble building, running, or embedding Avian, please post a message to our [discussion group](http://groups.google.com/group/avian). @@ -293,9 +293,10 @@ You can reduce the size futher for embedded builds by using ProGuard and the supplied openjdk.pro configuration file (see "Embedding with ProGuard and a Boot Image" below). Note that you'll still need to use vm.pro in that case -- openjdk.pro just adds additional constraints -specific to the OpenJDK port. Also see app.mk in -_git://oss.readytalk.com/avian-swt-examples.git_ for an example of using -Avian, OpenJDK, ProGuard, and UPX in concert. +specific to the OpenJDK port. Also see +[app.mk](https://github.com/ReadyTalk/avian-swt-examples/blob/master/app.mk) +in the _avian-swt-examples_ project for an example of using Avian, +OpenJDK, ProGuard, and UPX in concert. Here are some examples of how to install OpenJDK and build Avian with it on various OSes: From d9f6cabcf694ec593c159a66f7cd8106e3edc1a7 Mon Sep 17 00:00:00 2001 From: Thomas Farr Date: Tue, 22 Dec 2015 20:36:50 +1300 Subject: [PATCH 10/11] Change Avian src volume path to match Dockerfile --- docker/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/build.sh b/docker/build.sh index b67b599f1d..ef61007dce 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -38,4 +38,4 @@ fi DIR=$(cd $(dirname "$0") && cd .. && pwd) -docker run --rm -i -t -v "${DIR}":/var/avian ${THE_USER} "${CONTAINER}" "${@}" +docker run --rm -i -t -v "${DIR}":/var/src/avian ${THE_USER} "${CONTAINER}" "${@}" From 524f034bacf44d93087c32023f1f16383b4ff5f5 Mon Sep 17 00:00:00 2001 From: keinhaar Date: Sun, 20 Dec 2015 12:42:36 +0100 Subject: [PATCH 11/11] Added support for HTTP URL connections, and fixed SocketInputStream and BufferedInputStream. Did some cleanup as proposed by the main developers. - Bigger HTTP Header Buffer - Exception if Header is anyway exceeded. - Linebreaks on HTTP Request fixed to standard. - Only stop header reading on \r\n\r\n and no longer on \n\n\n\n - Simplyfied the code to stop if buffer could not be filled. - Handle special case if buffer has length 0, like specified in the Java API - Socket will no longer fill the buffer completely --- classpath/avian/http/Handler.java | 114 +++++++++++++++++++-- classpath/java/io/BufferedInputStream.java | 29 +++--- classpath/java/net/Socket.java | 16 ++- test/BufferedInputStreamTest.java | 80 +++++++++++++++ 4 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 test/BufferedInputStreamTest.java diff --git a/classpath/avian/http/Handler.java b/classpath/avian/http/Handler.java index e022b96940..28f36d4194 100644 --- a/classpath/avian/http/Handler.java +++ b/classpath/avian/http/Handler.java @@ -10,17 +10,111 @@ package avian.http; -import java.net.URL; -import java.net.URLStreamHandler; -import java.net.URLConnection; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.FileNotFoundException; -import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.HashMap; +import java.util.Map; -public class Handler extends URLStreamHandler { - protected URLConnection openConnection(URL url) { - throw new UnsupportedOperationException(); - } +public class Handler extends URLStreamHandler +{ + public URLConnection openConnection(URL url) throws IOException + { + return new HttpURLConnection(url); + } + + class HttpURLConnection extends URLConnection + { + Socket socket; + private BufferedWriter writer; + private InputStream bin; + private Map header = new HashMap(); + private int status; + + protected HttpURLConnection(URL url) + { + super(url); + } + + @Override + public void connect() throws IOException + { + if(socket == null) + { + URLConnection con = null; + String host = url.getHost(); + int port =url.getPort(); + if(port < 0) port = 80; + socket = new Socket(host, port); + OutputStream out = socket.getOutputStream(); + writer = new BufferedWriter(new OutputStreamWriter(out)); + writer.write("GET " + url.getPath() + " HTTP/1.1"); + writer.write("\r\nHost: " + host); + writer.write("\r\n\r\n"); + writer.flush(); + bin = new BufferedInputStream(socket.getInputStream()); + readHeader(); +// System.out.println("Status: " + status); +// System.out.println("Headers: " + header); + } + } + + private void readHeader() throws IOException + { + byte[] buf = new byte[8192]; + int b = 0; + int index = 0; + while(b >= 0) + { + if(index >= 4 && buf[index-4] == '\r' && buf[index-3] == '\n' && buf[index-2] == '\r' && buf[index-1] == '\n') + { + break; + } + b = bin.read(); + buf[index] = (byte) b; + index++; + if(index >= buf.length) + { + throw new IOException("Header exceeded maximum size of 8k."); + } + } + BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, index))); + String line = reader.readLine(); + int x = line.indexOf(' '); + status = Integer.parseInt(line.substring(x + 1 , line.indexOf(' ', x+1))); + while(line != null) + { + int i = line.indexOf(':'); + if(i > 0) + { + header.put(line.substring(0, i), line.substring(i + 1) .trim()); + } + line = reader.readLine(); + } + reader.close(); + } + + @Override + public InputStream getInputStream() throws IOException + { + connect(); + return bin; + } + + @Override + public OutputStream getOutputStream() throws IOException + { + throw new UnsupportedOperationException("Can' write to HTTP Connection"); + } + } } diff --git a/classpath/java/io/BufferedInputStream.java b/classpath/java/io/BufferedInputStream.java index ca4b7033ec..25a7441304 100644 --- a/classpath/java/io/BufferedInputStream.java +++ b/classpath/java/io/BufferedInputStream.java @@ -7,9 +7,11 @@ There is NO WARRANTY for this software. See license.txt for details. */ - package java.io; +import java.io.IOException; +import java.io.InputStream; + public class BufferedInputStream extends InputStream { private final InputStream in; private final byte[] buffer; @@ -25,17 +27,16 @@ public class BufferedInputStream extends InputStream { this(in, 4096); } - private void fill() throws IOException { + private int fill() throws IOException { position = 0; limit = in.read(buffer); + + return limit; } public int read() throws IOException { - if (position >= limit) { - fill(); - if (limit == -1) { - return -1; - } + if (position >= limit && fill() == -1) { + return -1; } return buffer[position++] & 0xFF; @@ -43,7 +44,9 @@ public class BufferedInputStream extends InputStream { public int read(byte[] b, int offset, int length) throws IOException { int count = 0; - + if (position >= limit && fill() == -1) { + return -1; + } if (position < limit) { int remaining = limit - position; if (remaining > length) { @@ -57,8 +60,8 @@ public class BufferedInputStream extends InputStream { offset += remaining; length -= remaining; } - - while (length > 0) { + while (length > 0 && in.available() > 0) + { int c = in.read(b, offset, length); if (c == -1) { if (count == 0) { @@ -69,13 +72,8 @@ public class BufferedInputStream extends InputStream { offset += c; count += c; length -= c; - - if (in.available() <= 0) { - break; - } } } - return count; } @@ -87,3 +85,4 @@ public class BufferedInputStream extends InputStream { in.close(); } } + diff --git a/classpath/java/net/Socket.java b/classpath/java/net/Socket.java index e486f7f4e6..9a32b88f7c 100644 --- a/classpath/java/net/Socket.java +++ b/classpath/java/net/Socket.java @@ -86,18 +86,16 @@ public class Socket implements Closeable, AutoCloseable { @Override public int read(byte[] buffer) throws IOException { + if(buffer.length == 0) return 0; //spec says return 0 if buffer length is zero. int fullSize = buffer.length; - int index = 0; int size; - do { - size = recv(sock, buffer, index, Math.min(fullSize, Socket.BUFFER_SIZE)); - fullSize -= size; - index += size; - } while (fullSize != 0 && size != 0); - return index; + size = recv(sock, buffer, 0, Math.min(fullSize, Socket.BUFFER_SIZE)); + fullSize -= size; + //removed loop, because otherwise interactive protocols will not work. + if(size < 0) throw new IOException("Error while reading stream"); //as the manpage of recv says, a value below zero indicates an error. + if(size == 0) return -1; // if the stream is closed (size == 0), then return -1 to indicate end of stream. + return size; } - - } private class SocketOutputStream extends OutputStream { diff --git a/test/BufferedInputStreamTest.java b/test/BufferedInputStreamTest.java new file mode 100644 index 0000000000..ba978cd5ef --- /dev/null +++ b/test/BufferedInputStreamTest.java @@ -0,0 +1,80 @@ + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.BufferedInputStream; + +/** + * Checks that BufferedInputStream does not block if data is available in it's internal buffer. + */ +public class BufferedInputStreamTest +{ + public static void main(String[] args) throws IOException + { + MyByteArrayStream in = new MyByteArrayStream(new byte[100]); + + BufferedInputStream bin = new BufferedInputStream(in); + //read a single byte to fill the buffer + int b = bin.read(); + byte[] buf = new byte[10]; + //now try to read 10 bytes. this should a least return the content of the buffer. On OpenJDK this are + //4 bytes (the rest of the buffer returned by MyByteArrayStream in the first call). + //It should definately NOT block. + int count = bin.read(buf); + System.out.println("Read bytes: " + count); + } + + /** + * Internal Stream used to show the BufferedInputStream behaviour. + */ + static class MyByteArrayStream extends ByteArrayInputStream + { + boolean stopReading = false; + + /** + * @param buf + */ + public MyByteArrayStream(byte[] buf) + { + super(buf); + } + + /* (non-Javadoc) + * @see java.io.ByteArrayInputStream#read(byte[], int, int) + */ + @Override + public synchronized int read(byte[] b, int off, int len) + { + if(stopReading == false) + { //On the first call 5 bytes are returned. + stopReading = true; + return super.read(b, off, 5); + } + //on all following calls block. The spec says, that a least one byte is returned, if the + //stream is not at EOF. + while(available() == 0) + { + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + } + } + return 0; + } + + /* (non-Javadoc) + * @see java.io.ByteArrayInputStream#available() + */ + @Override + public synchronized int available() + { + if(stopReading) + { + return 0; + } + return super.available(); + } + } +}