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.
This would theoretically break compatibility with apps using embedded
classpaths, on big-endian architectures - because of the size type
extension. However, we don't currently support any big-endian
architectures, so it shouldn't be a problem.
In afbd4ff, I made a low-risk, but very specific fix for a more
general problem: "bootstrap" classes (i.e. classes which the VM has
built-in knowledge of) need to be loaded from the classpath before any
of their methods are called. Based on recent testing, I found there were
more cases than I previously thought where the VM tries to call methods on
"unloaded" bootstrap classes, so we needed a more general solution to
the problem.
This commit addresses it by closing the last (known) loophole by which
methods might be called on bootstrap classes: invokeinterface, and its
helper method findInterfaceMethod. The fix is to check for bootstrap
classes in findInterfaceMethod and load the full versions if
necessary. This process may lead to garbage collection and/or thrown
exceptions, which made me nervous about cases of direct or indirect
calls to findInterfaceMethod not expecting those events, which is why
I hadn't used that approach earlier. However, it turns out there were
only a few places that made non-GC-safe calls to findInterfaceMethod,
and a bit of code rearrangement fixed that.
This function allows you to call native code such that any
SIGSEGV/SIGBUS/SIGFPE/EXC_ACCESS_VIOLATION/etc. raised by that code is
transformed into a Java exception and thrown by tryNative. Note that
this effectively results in a longjmp out of whatever function raised
the exception, so any C++ destructors or other cleanup code will not
be run.
So there I was, planning to just fix one little bug: Thread.holdsLock
and Thread.yield were missing for the Android class library. Easy
enough, right? So, I added a test, got it passing, and figured I'd go
ahead and run ci.sh with all three class libraries. Big mistake.
Here's the stuff I found:
* minor inconsistency in README.md about OpenSSL version
* untested, broken Class.getEnclosingMethod (reported by Josh)
* JNI test failed for tails=true Android build
* Runtime.nativeExit missing for Android build
* obsolete assertion in CallEvent broke tails=true Android build
* obsolete superclass field offset padding broke bootimage=true Android build
* runtime annotation parsing broke bootimage=true Android build
(because we couldn't modify Addendum.annotationTable for classes in
the heap image)
* ci.sh tried building with both android=... and openjdk=..., which
the makefile rightfully balked at
Sorry this is all in a single commit; I didn't expect so many
unrelated issues, and I'm too lazy to break them apart.
If an JNI_OnLoad implementation calls FindClass when using the OpenJDK
class library, the calling method on the Java stack will be
ClassLoader.loadLibrary. However, we must use the class loader of the
class attempting to load the library in this case, not the system
classloader.
Therefore, we now maintain a stack such that the latest class to load
a library in the current thread is at the top, and we use that class
whenever FindClass is called by ClassLoader.loadLibrary (via
JNI_OnLoad).
Note that this patch does not attempt to address the same problem for
the Avian or Android class libraries, but the same strategy should
work for them as well.
The problem (which we've only been able to reproduce consistently with
the openjdk-src process=interpret build on Linux virtual machines) was
a race condition during VM shutdown. Thread "A" would exit, see there
were other threads still running and thus enter ZombieState, which
involves acquiring and releasing a lock using RawMonitorResource.
Then the last thread (thread "B") would exit, wait for thread "A" to
release the lock, then shut down the VM, freeing all memory. However,
thread "A" writes to its Thread object one last time after releasing
the lock (in ~Resource, the destructor of the superclass of
RawMonitorResource, which sets Thread::resource). If thread "B" frees
that Thread before ~Resource runs, we end up writing to freed memory.
Thus, we need to update Thread::resource before releasing the lock.
Apparently C++ destructors run in order from most derived to least
derived, which is not what we want here. My solution to split
Resource into two classes, one that has no destructor and another that
extends it (called AutoResource) which does hafe a destructor. Now
all the classes which used to extend Resource extend AutoResource,
except for RawMonitorResource, which extends Resource directly so it
can control the order of operations.
This was a bug, wherein upon throwing an exception, we would try to
allocate memory for the message - all while holding a critical
reference to the jbyteArray representing the exception string. This
caused an expect to fail in allocate3.
This ensures that all tests pass when Avian is built with an
openjdk=$path option such that $path points to either OpenJDK 7 or 8.
Note that I have not yet tried using the openjdk-src option with
OpenJDK 8. I'll work on that next.
617bd85 broke the Android build by creating an unresolvable
order-of-operations bug in classpath-android.cpp's
MyClasspath::preBoot method.
The problem is that, while JNIEnv::FindClass is supposed to initialize
the class that it finds, this causes JniConstants::init to indirectly
invoke native methods which are not registered until JNI_OnLoad is
called (which happens after JniConstants::init is called). However,
if we call JNI_OnLoad first, that causes methods to be invoked which
rely on JniConstants::init having already been run.
I haven't checked to see how Dalvik handles this, but I don't see any
way around the problem besides disabling initialization by
JNIEnv::FindClass until the preBoot phase is complete. Moreover, it's
dangerous to allow Java code to be invoked so early anyway, since the
VM is not yet fully initialized.
There's more work to do to derive all the properties of a given class
from its code source (e.g. JAR file), but this at least ensures that
ClassLoader.getPackage will actually return something non-null when
appropriate.