This is the simplest possible ConcurrentHashMap I could come up with
that works and is actually concurrent in the way one would expect.
It's pretty unconventional, being based on a persistent red-black
tree, and not particularly memory-efficient or cache-friendly. I
think this is a good place to start, though, and it should perform
reasonably well for most workloads. Patches for a more efficient
implementation are welcome!
I also implemented AtomicReferenceArray, since I was using it in my
first, naive attempt to implement ConcurrentHashMap.
I had to do a bit of refactoring, including moving some non-standard
stuff from java.util.Collections to avian.Data so I could make it
available to code outside the java.util package, which is why I had to
modify several unrelated files.
getDeclaredMethods was returning methods which were inherited from
interfaces but not (re)declared in the class itself, due to the VM's
internal use of VMClass.methodTable differing from its role in
reflection. For reflection, we must only include the declared
methods, not the inherited but un-redeclared ones.
Previously, we saved the original method table in
ClassAddendum.methodTable before creating a new one which contains
both declared and inherited methods. That wasted space, so this patch
replaces ClassAddendum.methodTable with
ClassAddendum.declaredMethodCount, which specifies how many of the
methods in VMClass.methodTable were declared in that class.
Alternatively, we could ensure that undeclared methods always have
their VMMethod.class_ field set to the declaring class instead of the
inheriting class. I tried this, but it led to subtle crashes in
interface method lookup. The rest of the VM relies not only on
VMClass.methodTable containing all inherited interface methods but
also that those methods point to the inheriting class, not the
declaring class. Changing those assumptions would be a much bigger
(and more dangerous in terms of regression potential) effort than I
care to take on right now. The solution I chose is a bit ugly, but
it's safe.
An inner class has two sets of modifier flags: one is declared in the
usual place in the class file and the other is part of the
InnerClasses attribute. Not only is that redundant, but they can
contradict, and the VM can't just pick one and roll with it. Instead,
Class.getModifiers must return the InnerClasses version, whereas
reflection must check the top-level version. So even if
Class.getModifiers says the class is protected, it might still be
public for the purpose of reflection depending on what the
InnerClasses attribute says. Crazy? Yes.
This makes them available in all class libraries, not just the OpenJDK
library. Note that I've also removed the unecessary idle statements,
per ab4adef.
Android's class library uses this to find out whether the VM supports
compareAndSwapLong natively. Avian does on all platforms, so we just
return true.
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.
Previously, we used "lzma:", which worked fine on Windows (where the
path separator is ";") but not on Unix-style OSes (where the path
separator is ":"). In the latter case, the VM would parse
"[lzma:bootJar]" as a path containing two elements, "[lzma" and
"bootJar]", which is not what was intended. So now we use "lzma." as
the prefix, which works on all OSes.
armv7 and later provide weaker cache coherency models than armv6 and
earlier, so we cannot just implement memory barriers as no-ops. This
patch uses the DMB instruction (or the equivalent OS-provided barrier
function) to implement barriers. This should fix concurrency issues
on newer chips such as the Apple A6 and A7.
If you still need to support ARMv6 devices, you should pass
"armv6=true" to make when building Avian. Ideally, the VM would
detect what kind of CPU it was executing on at runtime and direct the
JIT compiler accordingly, but I don't know how to do that on ARM.
Patches are welcome, though!
When calculating field offsets in the bootimage generator, we failed
to consider alignment at inheritence boundaries (i.e. the last field
inherited by from a superclass should be followed by enough padding to
align the first non-inherited field at a machine word boundary). This
led to a mismatch between native code and Java code in terms of class
layouts, including that of java.lang.reflect.Method.
The former just defers to the latter for now, since it provides
strictly weaker guarantees. Thus it's correct to use full
volatile-style barriers, though not as efficient as it could be on
some architectures.
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.
It looks like the iOS 7 SDK doesn't have GCC anymore, so we need to
use clang instead. Also, thread_act.h and thread_status.h have moved,
so I updated arm.h accordingly. That might break the build for older
SDKs, but I don't have one available at the moment. If it does break,
I'll fix it.
Unsafe.compareAndSwapLong was moved from classpath-openjdk.cpp to
builtin.cpp, but the fieldForOffset helper function was not, which
only caused problems when I tried to build for ARM. This commit moves
said helper function, along with Unsafe.getVolatileLong, which also
uses it.