Happy New Year!
The common theme in today's snapshot is clearly design flaws. I have two of my own to discuss and two others. Note that this reflects only an small portion of all design flaws in my (and other) code, but these are the ones that are at least mildly interesting to read about (I hope).
When I originally designed the Thread.interrupt() support, it seemed like an obvious thing to map this onto the .NET support for interrupting threads. In retrospect I can see that this was a mistake (when something seems obvious it's tempting not to think about it anymore), but at the time it appeared to work fairly well. Only after a while I discovered that the monitorenter bytecode instruction (implemented by calling System.Threading.Monitor.Enter()) wasn't supposed to be interruptable in Java. I "solved" this by redirecting monitorenter to ByteCodeHelper.monitorenter() that repeatedly calls Monitor.Enter() (and catches any ThreadInterruptedExceptions) until it is successful. When I fixed this, I should have noticed a more fundamental problem with the Thread.interrupt() mapping approach, but I only realised the problem when I was working on the class loading deadlock bug a couple of weeks ago. Since java.lang.InterruptedException is a checked exception and can only occur in a couple of well defined places in the Java library, it simply wasn't a good fit to map it onto System.Threading.ThreadInterruptedException, which can occur any time a synchronized code block is entered (and this basically includes any .NET method).
Once I realised the problem, the fix was fairly easy. Java threads now maintain their own interrupt pending flag and whenever a Java thread enters one of the interruptable waits (Object.wait() or Thread.join()) it checks the flag and marks the thread as inside an interruptable wait. When another thread interrupts the waiting thread, it uses the .NET Thread.Interrupt() to interrupt the wait and the interrupted wait notices that the interrupt pending flag is set and catches the .NET System.Threading.ThreadInterruptedException and throws a java.lang.InterruptedException.
I did some heap analysis on a started Eclipse process and discovered that a ridiculous amount of memory was being held by the various MethodBuilder instances that are kept alive by the IKVM type information data structures. This wasn't a matter of a couple of wasted bytes, but somewhere around 20MB of useless objects that were still being referenced. I attempted several different workarounds, but the simplest and safest one turned out to be using reflection to clear all of the unused private fields in MethodBuilder after a dynamic type is baked. This is obviously a nasty hack, but the resulting memory reduction is so significant that I felt it was worth it. I filed a bug with Microsoft, so hopefully it'll get fixed for Whidbey. Another reflection design flaw is that System.Reflection.Emit.OpCode is a value type that contains a whole bunch of instance fields, instead of a lightweight enum. This means that whenever you use an OpCode (which is often when you're generating code with ILGenerator) you are carrying around about 40 bytes, instead of an int sized value.
After getting rid of (most of) the MethodBuilder overhead, I turned my attention to my own code and noticed that a lot of memory was being used up by the hashtables that the TypeWrapper instances used for the fields and methods. Using hashtables for this turned out to be a design flaw, because most classes don't have that many types or fields that the memory overhead of a hashtable of worthwhile (I'm doing a simple linear search through an array now, and it doesn't affect performance at all). Besides this change I also made a whole bunch of other changes to reduce memory consumption, the result is that starting up Eclipse now takes less than half the heap space than it did in the previous snapshot.
C# and the CLS
The C# team uses the CLS as a (lame) excuse not to have to support accessing static fields in interfaces, so you cannot access Java interface fields in C#. To work around this, I've added code to create an inner class named __Fields to every interface that has static fields and that inner class contains duplicates of the interface fields, so that they can be accessed from C#.
- Sync'ed with GNU Classpath cvs (IKVM.GNU.Classpath now contains GNU JAXP XML processing library). You can now run Eclipse without having to add the xml stuff to the bootclasspath:
eclipse -vm c:\ikvm\bin\ikvm.exe
- Moved ObjectHelper.wait() to VMThread.objectWait().
- Moved ObjectHelper.toStringSpecial() to map.xml.
- Removed java/lang/ObjectHelper.java
- Removed remapping of System.Threading.ThreadInterruptedException to java.lang.InterruptedException.
- Added code to set "user.language" and "user.region" system properties based on .NET culture.
- Fixed two shutdown hooks bugs that could cause the shutdown hooks not to run or run very slowly.
- Fixed Process.waitFor() to be interruptable.
- Fixed bug in VMThread.join() that could cause it not to return when a native thread detaches the thread.
- Totally rewrote Thread.interrupt() handling and detached Java thread interruption from .NET thread interruption.
- Removed the MethodDescriptor class.
- Removed ByteCodeHelper.monitorenter().
- Changed class file parser to intern all class, field, method names and signatures.
- Added check for class names that are too long for .NET to handle.
- Changed class loading locking so that no locks (outside of the lock on the ClassLoader) are held when Java code is called, to prevent potential deadlocks.
- Changed SimpleCallMethodWrapper and SmartCallMethodWrapper not to have two OpCode fields but use smaller enum type instead.
- Changed field resolving/linking to use the same strategy as methods.
- Unified NonPrimitiveValueTypeFieldWrapper, GhostFieldWrapper and SimpleFieldWrapper.
- Changed compilation of synchronized instance methods to use MethodImplAttribute(MethodImplOptions.Synchronized).
- Changed TypeWrapper to use simple arrays to store fields and methods, instead of hashtables.
- Integrated lazy member publishing into TypeWrapper and removed LazyTypeWrapper.
- Added BakedTypeCleanupHack to workaround bug/design flaw in Reflection.Emit that causes too much memory to be retained (the workaround is to use reflection to clear some private fields of MethodBuilder, ConstructorBuilder and FieldBuilder).
- Added workaround to make interface fields usable from C# (by duplicating them in an inner class named __Fields in the interface).
- Changed bytecode compiler to use CIL switch opcode for tableswitch bytecode (this is a bit more space efficient).
- Added conv_i1, conv_i2, conv_i4 and conv_i8 instruction support to remapper.
New snapshots: just the binaries and source plus binaries.