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 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.
For some reason, running Avian under the SVN version of Valgrind
caused mmap to fail, which caused tryAllocateExecutable to return a
null pointer, which led to a non-obvious crash later on. Adding an
expect to check the result immediately will at least make it obvious
what went wrong.
Turns out Function can do the jobs of both CallbackReceiver and
FunctionReceiver, so I've removed the latter two.
Also, shift and reset should work with a combination of types, not
just a single type, so I've expanded their generic signatures.
Tail call and dead code optimizations can cause code after a throw to
be eliminated, which confuses findUnwindTarget because it doesn't know
what code is throwing the exception. So we need at least one
instruction to follow the call to the throw_ thunk. Previously, we
only added such an instruction when we knew the throw was the last
instruction in the bytecode, but it turns out there are other cases
where it is needed, including certain try/finally situations.
There's a small optimization in compileDirectInvoke which tries to
avoid generating calls to empty methods. However, this causes
problems for code which uses such a call to ensure a class is
initialized -- if we omit that call, the class may not be
initialized and any side effects of that initialization may not
happen when the program expects them to.
This commit ensures that the compiler only omits empty method calls
when the target class does not need initialization. It also removes
commented-out code in classpath-openjdk.cpp which was responsible for
loading libmawt proactively; that was a hack to get JogAmp to work
before we understood what the real problem was.
Clang was complaining that newIp might be used uninitialized at the
bottom of our giant, unstructured compile loop, so I initialized it
with a bogus value, which means it will at least fail consistently if
Clang is right and there really is a path by which that code is
reached without otherwise initializing newIp.
Previously, I used a shell script to extract modification date ranges
from the Git history, but that was complicated and unreliable, so now
every file just gets the same year range in its copyright header. If
someone needs to know when a specific file was modified and by whom,
they can look at the Git history themselves; no need to include it
redundantly in the header.
Previously, we would attempt to initialize a class (e.g. call its
static initializer) whenever a method in that class was called, as
well as in any of the cases listed in
http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.
However, the above approach may lead to deadlock in an app which
relies on being able to call non-static methods in parallel with a
static initializer invocation in the same class. Thus, this commit
ensures that we initialize classes only in the cases defined by the
standard.
scalac may generate bytecode such that an exception is thrown within
the bounds of a handler for that exception such that the throw is the
last instruction in the method, which we weren't handling properly.
This ensures that, if an exception is thrown later but before the
method has been fully compiled, we will know exactly how much memory
to free. Previously, we would abort when trying to free the wrong
amount due to an assertion failure.
This is necessary to avoid name conflicts on various platforms. For
example, iOS has its own util.h, and Windows has a process.h. By
including our version as e.g. "avian/util.h", we avoid confusion with
the system version.