# Monday, 01 March 2004
« Object Model Mapping | Main | Object Model Mapping Part 3 »
Object Model Mapping Part 2

In yesterday's entry I didn't get to the stuff that kept me going in circles last week. I had decided on the mixed model a few months ago, but as usual the devil is in the details.

Initially I wanted to keep the map.xml format more or less the same and I think that put me on the wrong track. Let's start by taking a look at some of the current map.xml features. Within the <class> tag there are <constructor> and <method> tags. These can contain several different child tags:

  • Empty (i.e. no child tags)
    The method is identical to the corresponding method in the underlying type. Example: The constructor of java.lang.Object is identical to the constructor of System.Object, so the tag looks like this:
    <constructor sig="()V" modifiers="public" />
  • <redirect>
    The method is redirected to another method. This can be a static method in a helper class or a static or instance method in the underlying type. Example: java.lang.Object.notifyAll() is redirected to System.Threading.Monitor.PulseAll():
    <method name="notifyAll" sig="()V" modifiers="public final">
        <redirect class="System.Threading.Monitor, mscorlib" name="PulseAll" sig="(Ljava.lang.Object;)V" type="static" />
    </method>
  • <invokespecial>
    If the method is invoked using the invokespecial bytecode, this CIL sequence is emitted. Example: java.lang.Object.wait() is implemented by calling System.Threading.Monitor.Wait(), but Monitor.Wait returns a boolean that has to be discarded:
    <method name="wait" sig="()V" modifiers="public final">
        <invokespecial>
            <call type="System.Threading.Monitor, mscorlib" name="Wait" sig="(Ljava.lang.Object;)Z" />
            <pop />
        </invokespecial>
    </method>
  • <invokevirtual>
    Similar to <invokespecial>, but this defines the CIL that is emitted when the invokevirtual bytecode is used to call the method.
  • <override>
    Specifies that this method conceptually overrides the method named in the <override> tag. I say "conceptually" because in the equivalence model there is no real class. However, if a real subclass would override this method, it would actually be overriding the method named in the override tag. Example: java.lang.Object.hashCode() overrides System.Object.GetHashCode:
    <method name="hashCode" sig="()I" modifiers="public">
        <override name="GetHashCode" />
        <invokevirtual>
            <dup />
            <isinst type="System.String, mscorlib" />
            <brfalse name="skip" />
            <castclass type="System.String, mscorlib" />
            <call class="java.lang.StringHelper" name="hashCode" sig="(Ljava.lang.String;)I" />
            <br name="end" />
            <label name="skip" />
            <callvirt type="System.Object, mscorlib" name="GetHashCode" />
            <label name="end" />
        </invokevirtual>
    </method>
  • <newobj>
    Used in constructors to define the CIL that is emitted when a new instance is created. Example: java.lang.String has a default constructor, but System.String doesn't:
    <constructor sig="()V" modifiers="public">
        <newobj>
            <ldstr value="" />
            <call type="System.String, mscorlib" name="Copy" />
        </newobj>
    </constructor>

The thing to note is that some of the remapping implications are still handled manually in this scheme. For example, the <invokevirtual> of Object.hashCode has to check for string instances. This information can be derived from the remapping file and it shouldn't be necessary to do this explicitly.

I didn't really like the <invokespecial> and <invokevirtual> constructs and I explored the idea of only having a <body> tag that contains the method body. However, it soon became clear that that wouldn't be enough. For example, the implementation of java.lang.Object.clone needs to call the protected method System.Object.MemberwiseClone and this is only allowed in subclasses. So it wouldn't be possible to generate a verifiable helper method for that.

The obvious solution (in hindsight) came to me when I realised that there are actually two types of "subclasses" of java.lang.Object, the ones that really extend java.lang.Object (our generated .NET type) and the ones that don't (e.g. arrays, System.String and all other .NET types). I knew this before, of course, but I was trying to make the model too general. After this realisation, it became obvious that every method should have a <body> and an <alternateBody> (tentative names).

After I've modified the remapping engine to automatically handle all the overridden method implications, the <alternateBody> construct will not be needed for many methods. I think only for Object.clone and Object.finalize and both will be trivial. The <alternateBody> for clone will throw a CloneNotSupportedException (it could also check if the object implements ICloneable and if so, invoke that Clone method, but does this really help?) and the <alternateBody> for finalize will simply be empty, since there is no good reason to ever explicitly invoke the Finalize method of a .NET type.

As an aside, I'm also going to remove the <redirect> construct, because it doens't really add any value. It's just as easy to have a <body> with a <call>.

I'm not clear on the performance implications of these changes. In the existing model, many of the remapping constructs are inlined, but in the new model they won't be, invokespecial will end up calling the real method in the new classes and invokevirtual will call the static helper method. This will probably be slightly slower, but I think the other advantages easily outweigh this.

Another advantage of this scheme that I didn't yet mention is that reflection on remapped methods is now trivial. Currently, the following program doesn't work on IKVM, in the new model the call would simply end up at the static helper method for clone:

class ReflectClone
{
  public static void main(String[] args) throws Exception
  {
    java.lang.reflect.Method m;
    m = Object.class.getDeclaredMethod("clone", new Class[0]);
    m.setAccessible(true);
    System.out.println(m.invoke(args, new Object[0]));
  }
}

BTW, I originally tried this by getting the public clone method on the array class, but oddly enough on the Sun JVM array types don't appear to have a public clone method (even though you can call it just fine!).

Monday, 01 March 2004 10:59:33 (W. Europe Standard Time, UTC+01:00)  #    Comments [4]