Defer marking of objects as finalizable

This shifts responsibility for marking an object as "finalizable" from
object creation to object initialization.  We want to make the object
finalizable when Object.<init> completes.  For performance reasons we
skip the call to the Object constructor (which doesn't do anything)
and just take the opportunity to check the class flag.

Handling of clone()d object isn't quite right yet.

Also, fixed a minor glitch in stubdefs.

Bug 3342343

Change-Id: I5b7b819079e5862dc9cbd1830bb445a852dc63bf
diff --git a/vm/alloc/Alloc.c b/vm/alloc/Alloc.c
index a95e63f..f77a232 100644
--- a/vm/alloc/Alloc.c
+++ b/vm/alloc/Alloc.c
@@ -171,15 +171,11 @@
 
     assert(dvmIsClassInitialized(clazz) || dvmIsClassInitializing(clazz));
 
-    if (IS_CLASS_FLAG_SET(clazz, CLASS_ISFINALIZABLE)) {
-        flags |= ALLOC_FINALIZABLE;
-    }
-
     /* allocate on GC heap; memory is zeroed out */
     newObj = (Object*)dvmMalloc(clazz->objectSize, flags);
     if (newObj != NULL) {
         DVM_OBJECT_INIT(newObj, clazz);
-        dvmTrackAllocation(clazz, clazz->objectSize);
+        dvmTrackAllocation(clazz, clazz->objectSize);   /* notify DDMS */
     }
 
     return newObj;
@@ -193,37 +189,48 @@
  */
 Object* dvmCloneObject(Object* obj)
 {
+    ClassObject* clazz;
     Object* copy;
-    int size;
-    int flags;
+    size_t size;
 
     assert(dvmIsValidObject(obj));
+    clazz = obj->clazz;
 
     /* Class.java shouldn't let us get here (java.lang.Class is final
      * and does not implement Clonable), but make extra sure.
      * A memcpy() clone will wreak havoc on a ClassObject's "innards".
      */
-    assert(obj->clazz != gDvm.classJavaLangClass);
+    assert(clazz != gDvm.classJavaLangClass);
 
-    if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE))
-        flags = ALLOC_DEFAULT | ALLOC_FINALIZABLE;
-    else
-        flags = ALLOC_DEFAULT;
-
-    if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISARRAY)) {
+    if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) {
         size = dvmArrayObjectSize((ArrayObject *)obj);
     } else {
-        size = obj->clazz->objectSize;
+        size = clazz->objectSize;
     }
 
-    copy = (Object*)dvmMalloc(size, flags);
+    copy = (Object*)dvmMalloc(size, ALLOC_DEFAULT);
     if (copy == NULL)
         return NULL;
 
+    /* We assume that memcpy will copy obj by words. */
     memcpy(copy, obj, size);
     DVM_LOCK_INIT(&copy->lock);
     dvmWriteBarrierObject(copy);
 
+    /*
+     * Mark the clone as finalizable if appropriate.
+     *
+     * TODO: this is wrong if the source object hasn't finished construction
+     * through Object.<init>.  We need to make "copy" finalizable iff
+     * "obj" is finalizable, i.e. it's present in gcHeap->finalizableRefs.
+     * Currently no quick way to do that.  See also b/2645458.
+     */
+    if (IS_CLASS_FLAG_SET(clazz, CLASS_ISFINALIZABLE)) {
+        dvmSetFinalizable(copy);
+    }
+
+    dvmTrackAllocation(clazz, size);    /* notify DDMS */
+
     return copy;
 }
 
diff --git a/vm/alloc/Alloc.h b/vm/alloc/Alloc.h
index e1b3e31..9747fc0 100644
--- a/vm/alloc/Alloc.h
+++ b/vm/alloc/Alloc.h
@@ -59,7 +59,6 @@
 enum {
     ALLOC_DEFAULT       = 0x00,
     ALLOC_DONT_TRACK    = 0x01,     /* don't add to internal tracking list */
-    ALLOC_FINALIZABLE   = 0x02,     /* call finalize() before freeing */
 };
 
 /*
@@ -93,6 +92,11 @@
 Object* dvmCloneObject(Object* obj);
 
 /*
+ * Make the object finalizable.
+ */
+void dvmSetFinalizable(Object* obj);
+
+/*
  * Determine the exact number of GC heap bytes used by an object.  (Internal
  * to heap code except for debugging.)
  */
diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c
index dcc6e42..7eb3ff2 100644
--- a/vm/alloc/Heap.c
+++ b/vm/alloc/Heap.c
@@ -416,7 +416,6 @@
  */
 void* dvmMalloc(size_t size, int flags)
 {
-    GcHeap *gcHeap = gDvm.gcHeap;
     void *ptr;
 
     dvmLockHeap();
@@ -427,19 +426,6 @@
     if (ptr != NULL) {
         /* We've got the memory.
          */
-        if ((flags & ALLOC_FINALIZABLE) != 0) {
-            /* This object is an instance of a class that
-             * overrides finalize().  Add it to the finalizable list.
-             */
-            if (!dvmHeapAddRefToLargeTable(&gcHeap->finalizableRefs,
-                                    (Object *)ptr))
-            {
-                LOGE_HEAP("dvmMalloc(): no room for any more "
-                        "finalizable objects\n");
-                dvmAbort();
-            }
-        }
-
         if (gDvm.allocProf.enabled) {
             Thread* self = dvmThreadSelf();
             gDvm.allocProf.allocCount++;
diff --git a/vm/alloc/MarkSweep.c b/vm/alloc/MarkSweep.c
index 9ef3da7..1fb3f24 100644
--- a/vm/alloc/MarkSweep.c
+++ b/vm/alloc/MarkSweep.c
@@ -880,6 +880,24 @@
 }
 
 /*
+ * This object is an instance of a class that overrides finalize().  Mark
+ * it as finalizable.
+ *
+ * This is called when Object.<init> completes normally.  It's also
+ * called for clones of finalizable objects.
+ */
+void dvmSetFinalizable(Object* obj)
+{
+    dvmLockHeap();
+    GcHeap* gcHeap = gDvm.gcHeap;
+    if (!dvmHeapAddRefToLargeTable(&gcHeap->finalizableRefs, obj)) {
+        LOGE_HEAP("No room for any more finalizable objects");
+        dvmAbort();
+    }
+    dvmUnlockHeap();
+}
+
+/*
  * Process reference class instances and schedule finalizations.
  */
 void dvmHeapProcessReferences(Object **softReferences, bool clearSoftRefs,
diff --git a/vm/analysis/Optimize.c b/vm/analysis/Optimize.c
index d7704a4..c502303 100644
--- a/vm/analysis/Optimize.c
+++ b/vm/analysis/Optimize.c
@@ -41,7 +41,7 @@
     Opcode volatileOpc);
 static bool rewriteStaticField(Method* method, u2* insns, Opcode volatileOpc);
 static bool rewriteVirtualInvoke(Method* method, u2* insns, Opcode newOpc);
-static bool rewriteEmptyDirectInvoke(Method* method, u2* insns);
+static bool rewriteInvokeObjectInit(Method* method, u2* insns);
 static bool rewriteExecuteInline(Method* method, u2* insns,
     MethodType methodType);
 static bool rewriteExecuteInlineRange(Method* method, u2* insns,
@@ -251,7 +251,7 @@
             break;
         case OP_INVOKE_DIRECT:
             if (!rewriteExecuteInline(method, insns, METHOD_DIRECT)) {
-                rewriteEmptyDirectInvoke(method, insns);
+                rewriteInvokeObjectInit(method, insns);
             }
             break;
         case OP_INVOKE_DIRECT_RANGE:
@@ -904,16 +904,18 @@
 }
 
 /*
- * Rewrite invoke-direct, which has the form:
+ * Rewrite invoke-direct of Object.<init>, which has the form:
  *   op vAA, meth@BBBB, reg stuff @CCCC
  *
- * There isn't a lot we can do to make this faster, but in some situations
- * we can make it go away entirely.
+ * This is useful as an optimization, because otherwise every object
+ * instantiation will cause us to call a method that does nothing.
+ * It also allows us to inexpensively mark objects as finalizable at the
+ * correct time.
  *
- * This must only be used when the invoked method does nothing and has
- * no return value (the latter being very important for verification).
+ * TODO: verifier should ensure Object.<init> contains only return-void,
+ * and issue a warning if not.
  */
-static bool rewriteEmptyDirectInvoke(Method* method, u2* insns)
+static bool rewriteInvokeObjectInit(Method* method, u2* insns)
 {
     ClassObject* clazz = method->clazz;
     Method* calledMethod;
@@ -922,27 +924,24 @@
     calledMethod = dvmOptResolveMethod(clazz, methodIdx, METHOD_DIRECT, NULL);
     if (calledMethod == NULL) {
         LOGD("DexOpt: unable to opt direct call 0x%04x at 0x%02x in %s.%s\n",
-            methodIdx,
-            (int) (insns - method->insns), clazz->descriptor,
-            method->name);
+            methodIdx, (int) (insns - method->insns),
+            clazz->descriptor, method->name);
         return false;
     }
 
-    /* TODO: verify that java.lang.Object() is actually empty! */
     if (calledMethod->clazz == gDvm.classJavaLangObject &&
         dvmCompareNameDescriptorAndMethod("<init>", "()V", calledMethod) == 0)
     {
         /*
-         * Replace with "empty" instruction.  DO NOT disturb anything
-         * else about it, as we want it to function the same as
-         * OP_INVOKE_DIRECT when debugging is enabled.
+         * Replace the instruction.  We want to modify as little as possible
+         * because, if the debugger is attached, the interpreter will
+         * forward execution to the invoke-direct handler.
          */
         assert((insns[0] & 0xff) == OP_INVOKE_DIRECT);
         updateOpcode(method, insns, OP_INVOKE_OBJECT_INIT);
 
-        //LOGI("DexOpt: marked-empty call to %s.%s --> %s.%s\n",
-        //    method->clazz->descriptor, method->name,
-        //    calledMethod->clazz->descriptor, calledMethod->name);
+        LOGVV("DexOpt: replaced Object.<init> in %s.%s\n",
+            method->clazz->descriptor, method->name);
     }
 
     return true;
diff --git a/vm/compiler/codegen/arm/CodegenDriver.c b/vm/compiler/codegen/arm/CodegenDriver.c
index ade4197..2ed17d6 100644
--- a/vm/compiler/codegen/arm/CodegenDriver.c
+++ b/vm/compiler/codegen/arm/CodegenDriver.c
@@ -3271,10 +3271,8 @@
             genTrap(cUnit, mir->offset, pcrLabel);
             break;
         }
-        /* NOP */
         case OP_INVOKE_OBJECT_INIT: {
-            if (gDvmJit.methodTraceSupport)
-                genInterpSingleStep(cUnit, mir);
+            genInterpSingleStep(cUnit, mir);
             break;
         }
         case OP_FILLED_NEW_ARRAY:
diff --git a/vm/mterp/armv5te/OP_INVOKE_OBJECT_INIT.S b/vm/mterp/armv5te/OP_INVOKE_OBJECT_INIT.S
index b75e124..2ae5f5b 100644
--- a/vm/mterp/armv5te/OP_INVOKE_OBJECT_INIT.S
+++ b/vm/mterp/armv5te/OP_INVOKE_OBJECT_INIT.S
@@ -1,7 +1,20 @@
 %verify "executed"
+%verify "finalizable class"
     /*
-     * invoke-object-init is a no-op in a "standard" interpreter.
+     * Invoke Object.<init> on an object.  In practice we know that
+     * Object's nullary constructor doesn't do anything, so we just
+     * skip it (we know a debugger isn't active).
      */
-    FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
+    FETCH(r0, 2)                        @ r0<- GFED
+    and     r1, r0, #15                 @ r1<- D
+    GET_VREG(r0, r1)                    @ r0<- "this" ptr
+    cmp     r0, #0                      @ check for NULL
+    beq     common_errNullObject        @ export PC and throw NPE
+    ldr     r1, [r0, #offObject_clazz]  @ r1<- obj->clazz
+    ldr     r2, [r1, #offClassObject_accessFlags] @ r2<- clazz->accessFlags
+    tst     r2, #CLASS_ISFINALIZABLE    @ is this class finalizable?
+    beq     1f                          @ nope, done
+    bl      dvmSetFinalizable           @ call dvmSetFinalizable(obj)
+1:  FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
     GET_INST_OPCODE(ip)                 @ ip<- opcode from rINST
     GOTO_OPCODE(ip)                     @ execute it
diff --git a/vm/mterp/c/OP_INVOKE_OBJECT_INIT.c b/vm/mterp/c/OP_INVOKE_OBJECT_INIT.c
index cc65e3f..1da8b06 100644
--- a/vm/mterp/c/OP_INVOKE_OBJECT_INIT.c
+++ b/vm/mterp/c/OP_INVOKE_OBJECT_INIT.c
@@ -1,15 +1,33 @@
 HANDLE_OPCODE(OP_INVOKE_OBJECT_INIT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
-#if INTERP_TYPE != INTERP_DBG
-    //LOGI("Ignoring empty\n");
-    FINISH(3);
+    {
+        Object* obj;
+
+        vsrc1 = FETCH(2) & 0x0f;        /* reg number of "this" pointer */
+        obj = GET_REGISTER_AS_OBJECT(vsrc1);
+
+        if (!checkForNullExportPC(obj, fp, pc))
+            GOTO_exceptionThrown();
+
+        /*
+         * The object should be marked "finalizable" when Object.<init>
+         * completes normally.  We're going to assume it does complete
+         * (by virtue of being nothing but a return-void) and set it now.
+         */
+        if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE)) {
+            dvmSetFinalizable(obj);
+        }
+
+#if INTERP_TYPE == INTERP_DBG
+        if (!DEBUGGER_ACTIVE) {
+            /* skip method invocation */
+            FINISH(3);
+        } else {
+            /* behave like OP_INVOKE_DIRECT */
+            GOTO_invoke(invokeDirect, false, false);
+        }
 #else
-    if (!DEBUGGER_ACTIVE) {
-        //LOGI("Skipping empty\n");
-        FINISH(3);      // don't want it to show up in profiler output
-    } else {
-        //LOGI("Running empty\n");
-        /* fall through to OP_INVOKE_DIRECT */
-        GOTO_invoke(invokeDirect, false, false);
-    }
+        /* debugger can't be attached, skip method invocation */
+        FINISH(3);
 #endif
+    }
 OP_END
diff --git a/vm/mterp/common/asm-constants.h b/vm/mterp/common/asm-constants.h
index e4070ec..df72f08 100644
--- a/vm/mterp/common/asm-constants.h
+++ b/vm/mterp/common/asm-constants.h
@@ -318,6 +318,7 @@
 MTERP_CONSTANT(ACC_NATIVE,          0x0100)
 MTERP_CONSTANT(ACC_INTERFACE,       0x0200)
 MTERP_CONSTANT(ACC_ABSTRACT,        0x0400)
+MTERP_CONSTANT(CLASS_ISFINALIZABLE, 1<<31)
 
 /* flags for dvmMalloc */
 MTERP_CONSTANT(ALLOC_DONT_TRACK,    0x01)
diff --git a/vm/mterp/config-armv7-a b/vm/mterp/config-armv7-a
index e66640c..e0ccdd3 100644
--- a/vm/mterp/config-armv7-a
+++ b/vm/mterp/config-armv7-a
@@ -156,6 +156,8 @@
 
 # "helper" code for C; include if you use any of the C stubs (this generates
 # object code, so it's normally excluded)
+#
+# Add this if you see linker failures for stuff like "dvmMterp_exceptionThrown".
 ##import c/gotoTargets.c
 
 # end of defs; include this when cstubs/stubdefs.c is included
diff --git a/vm/mterp/config-x86 b/vm/mterp/config-x86
index f137198..4d67c51 100644
--- a/vm/mterp/config-x86
+++ b/vm/mterp/config-x86
@@ -40,6 +40,7 @@
     op OP_SGET_WIDE_VOLATILE c
     op OP_SPUT_WIDE_VOLATILE c
     op OP_RETURN_VOID_BARRIER c
+    op OP_INVOKE_OBJECT_INIT c
 op-end
 
 # arch-specific entry point to interpreter
diff --git a/vm/mterp/config-x86-atom b/vm/mterp/config-x86-atom
index e1e8866..11e667c 100644
--- a/vm/mterp/config-x86-atom
+++ b/vm/mterp/config-x86-atom
@@ -298,6 +298,7 @@
 op OP_SGET_WIDE_VOLATILE c
 op OP_SPUT_WIDE_VOLATILE c
 op OP_RETURN_VOID_BARRIER c
+op OP_INVOKE_OBJECT_INIT c
 op-end
 
 # arch-specific entry point to interpreter
diff --git a/vm/mterp/cstubs/stubdefs.c b/vm/mterp/cstubs/stubdefs.c
index d9d6f3e..ea061ea 100644
--- a/vm/mterp/cstubs/stubdefs.c
+++ b/vm/mterp/cstubs/stubdefs.c
@@ -86,9 +86,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
diff --git a/vm/mterp/out/InterpAsm-armv5te-vfp.S b/vm/mterp/out/InterpAsm-armv5te-vfp.S
index 9537885..1d2abb3 100644
--- a/vm/mterp/out/InterpAsm-armv5te-vfp.S
+++ b/vm/mterp/out/InterpAsm-armv5te-vfp.S
@@ -7412,9 +7412,21 @@
 .L_OP_INVOKE_OBJECT_INIT: /* 0xf0 */
 /* File: armv5te/OP_INVOKE_OBJECT_INIT.S */
     /*
-     * invoke-object-init is a no-op in a "standard" interpreter.
+     * Invoke Object.<init> on an object.  In practice we know that
+     * Object's nullary constructor doesn't do anything, so we just
+     * skip it (we know a debugger isn't active).
      */
-    FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
+    FETCH(r0, 2)                        @ r0<- GFED
+    and     r1, r0, #15                 @ r1<- D
+    GET_VREG(r0, r1)                    @ r0<- "this" ptr
+    cmp     r0, #0                      @ check for NULL
+    beq     common_errNullObject        @ export PC and throw NPE
+    ldr     r1, [r0, #offObject_clazz]  @ r1<- obj->clazz
+    ldr     r2, [r1, #offClassObject_accessFlags] @ r2<- clazz->accessFlags
+    tst     r2, #CLASS_ISFINALIZABLE    @ is this class finalizable?
+    beq     1f                          @ nope, done
+    bl      dvmSetFinalizable           @ call dvmSetFinalizable(obj)
+1:  FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
     GET_INST_OPCODE(ip)                 @ ip<- opcode from rINST
     GOTO_OPCODE(ip)                     @ execute it
 
diff --git a/vm/mterp/out/InterpAsm-armv5te.S b/vm/mterp/out/InterpAsm-armv5te.S
index fb0d1e9..62cdd2e 100644
--- a/vm/mterp/out/InterpAsm-armv5te.S
+++ b/vm/mterp/out/InterpAsm-armv5te.S
@@ -7734,9 +7734,21 @@
 .L_OP_INVOKE_OBJECT_INIT: /* 0xf0 */
 /* File: armv5te/OP_INVOKE_OBJECT_INIT.S */
     /*
-     * invoke-object-init is a no-op in a "standard" interpreter.
+     * Invoke Object.<init> on an object.  In practice we know that
+     * Object's nullary constructor doesn't do anything, so we just
+     * skip it (we know a debugger isn't active).
      */
-    FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
+    FETCH(r0, 2)                        @ r0<- GFED
+    and     r1, r0, #15                 @ r1<- D
+    GET_VREG(r0, r1)                    @ r0<- "this" ptr
+    cmp     r0, #0                      @ check for NULL
+    beq     common_errNullObject        @ export PC and throw NPE
+    ldr     r1, [r0, #offObject_clazz]  @ r1<- obj->clazz
+    ldr     r2, [r1, #offClassObject_accessFlags] @ r2<- clazz->accessFlags
+    tst     r2, #CLASS_ISFINALIZABLE    @ is this class finalizable?
+    beq     1f                          @ nope, done
+    bl      dvmSetFinalizable           @ call dvmSetFinalizable(obj)
+1:  FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
     GET_INST_OPCODE(ip)                 @ ip<- opcode from rINST
     GOTO_OPCODE(ip)                     @ execute it
 
diff --git a/vm/mterp/out/InterpAsm-armv7-a-neon.S b/vm/mterp/out/InterpAsm-armv7-a-neon.S
index dc317d3..3ae8c21 100644
--- a/vm/mterp/out/InterpAsm-armv7-a-neon.S
+++ b/vm/mterp/out/InterpAsm-armv7-a-neon.S
@@ -7370,9 +7370,21 @@
 .L_OP_INVOKE_OBJECT_INIT: /* 0xf0 */
 /* File: armv5te/OP_INVOKE_OBJECT_INIT.S */
     /*
-     * invoke-object-init is a no-op in a "standard" interpreter.
+     * Invoke Object.<init> on an object.  In practice we know that
+     * Object's nullary constructor doesn't do anything, so we just
+     * skip it (we know a debugger isn't active).
      */
-    FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
+    FETCH(r0, 2)                        @ r0<- GFED
+    and     r1, r0, #15                 @ r1<- D
+    GET_VREG(r0, r1)                    @ r0<- "this" ptr
+    cmp     r0, #0                      @ check for NULL
+    beq     common_errNullObject        @ export PC and throw NPE
+    ldr     r1, [r0, #offObject_clazz]  @ r1<- obj->clazz
+    ldr     r2, [r1, #offClassObject_accessFlags] @ r2<- clazz->accessFlags
+    tst     r2, #CLASS_ISFINALIZABLE    @ is this class finalizable?
+    beq     1f                          @ nope, done
+    bl      dvmSetFinalizable           @ call dvmSetFinalizable(obj)
+1:  FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
     GET_INST_OPCODE(ip)                 @ ip<- opcode from rINST
     GOTO_OPCODE(ip)                     @ execute it
 
diff --git a/vm/mterp/out/InterpAsm-armv7-a.S b/vm/mterp/out/InterpAsm-armv7-a.S
index babba04..f276cd5 100644
--- a/vm/mterp/out/InterpAsm-armv7-a.S
+++ b/vm/mterp/out/InterpAsm-armv7-a.S
@@ -7370,9 +7370,21 @@
 .L_OP_INVOKE_OBJECT_INIT: /* 0xf0 */
 /* File: armv5te/OP_INVOKE_OBJECT_INIT.S */
     /*
-     * invoke-object-init is a no-op in a "standard" interpreter.
+     * Invoke Object.<init> on an object.  In practice we know that
+     * Object's nullary constructor doesn't do anything, so we just
+     * skip it (we know a debugger isn't active).
      */
-    FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
+    FETCH(r0, 2)                        @ r0<- GFED
+    and     r1, r0, #15                 @ r1<- D
+    GET_VREG(r0, r1)                    @ r0<- "this" ptr
+    cmp     r0, #0                      @ check for NULL
+    beq     common_errNullObject        @ export PC and throw NPE
+    ldr     r1, [r0, #offObject_clazz]  @ r1<- obj->clazz
+    ldr     r2, [r1, #offClassObject_accessFlags] @ r2<- clazz->accessFlags
+    tst     r2, #CLASS_ISFINALIZABLE    @ is this class finalizable?
+    beq     1f                          @ nope, done
+    bl      dvmSetFinalizable           @ call dvmSetFinalizable(obj)
+1:  FETCH_ADVANCE_INST(3)               @ advance to next instr, load rINST
     GET_INST_OPCODE(ip)                 @ ip<- opcode from rINST
     GOTO_OPCODE(ip)                     @ execute it
 
diff --git a/vm/mterp/out/InterpAsm-x86-atom.S b/vm/mterp/out/InterpAsm-x86-atom.S
index 50cc7b3..c53fe6c 100644
--- a/vm/mterp/out/InterpAsm-x86-atom.S
+++ b/vm/mterp/out/InterpAsm-x86-atom.S
@@ -14664,7 +14664,6 @@
 /* ------------------------------ */
     .balign 64
 .L_OP_INVOKE_OBJECT_INIT: /* 0xf0 */
-/* File: x86-atom/OP_INVOKE_OBJECT_INIT.S */
    /* Copyright (C) 2008 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
@@ -14681,17 +14680,15 @@
     */
 
    /*
-    * File: OP_INVOKE_OBJECT_INIT.S
-    *
-    * Code: Used as a no-op. Uses no substitutions.
-    *
-    * For: invoke-object-init
-    *
-    * Format: B|A|op CCCC G|F|E|D (35c)
+    * File: stub.S
     */
 
-    FINISH 3
-
+    SAVE_PC_FP_TO_GLUE %edx             # save program counter and frame pointer
+    pushl       rGLUE                   # push parameter glue
+    call        dvmMterp_OP_INVOKE_OBJECT_INIT      # call c-based implementation
+    lea         4(%esp), %esp
+    LOAD_PC_FP_FROM_GLUE                # restore program counter and frame pointer
+    FINISH_A                            # jump to next instruction
 /* ------------------------------ */
     .balign 64
 .L_OP_RETURN_VOID_BARRIER: /* 0xf1 */
diff --git a/vm/mterp/out/InterpAsm-x86.S b/vm/mterp/out/InterpAsm-x86.S
index 74ec78d..6e5290f 100644
--- a/vm/mterp/out/InterpAsm-x86.S
+++ b/vm/mterp/out/InterpAsm-x86.S
@@ -6125,14 +6125,14 @@
 /* ------------------------------ */
     .balign 64
 .L_OP_INVOKE_OBJECT_INIT: /* 0xf0 */
-/* File: x86/OP_INVOKE_OBJECT_INIT.S */
-    /*
-     * invoke-object-init is a no-op in a "standard" interpreter.
-     */
-    FETCH_INST_WORD 3
-    ADVANCE_PC 3
+    /* (stub) */
+    SAVE_PC_FP_TO_GLUE %ecx          # leaves rGLUE in %ecx
+    movl %ecx,OUT_ARG0(%esp)         # glue is first arg to function
+    call      dvmMterp_OP_INVOKE_OBJECT_INIT     # do the real work
+    mov       rGLUE,%ecx
+    LOAD_PC_FP_FROM_GLUE             # retrieve updated values
+    FETCH_INST
     GOTO_NEXT
-
 /* ------------------------------ */
     .balign 64
 .L_OP_RETURN_VOID_BARRIER: /* 0xf1 */
diff --git a/vm/mterp/out/InterpC-allstubs.c b/vm/mterp/out/InterpC-allstubs.c
index 03bb746..eda4f69 100644
--- a/vm/mterp/out/InterpC-allstubs.c
+++ b/vm/mterp/out/InterpC-allstubs.c
@@ -499,9 +499,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
@@ -3054,19 +3054,37 @@
 
 /* File: c/OP_INVOKE_OBJECT_INIT.c */
 HANDLE_OPCODE(OP_INVOKE_OBJECT_INIT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
-#if INTERP_TYPE != INTERP_DBG
-    //LOGI("Ignoring empty\n");
-    FINISH(3);
+    {
+        Object* obj;
+
+        vsrc1 = FETCH(2) & 0x0f;        /* reg number of "this" pointer */
+        obj = GET_REGISTER_AS_OBJECT(vsrc1);
+
+        if (!checkForNullExportPC(obj, fp, pc))
+            GOTO_exceptionThrown();
+
+        /*
+         * The object should be marked "finalizable" when Object.<init>
+         * completes normally.  We're going to assume it does complete
+         * (by virtue of being nothing but a return-void) and set it now.
+         */
+        if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE)) {
+            dvmSetFinalizable(obj);
+        }
+
+#if INTERP_TYPE == INTERP_DBG
+        if (!DEBUGGER_ACTIVE) {
+            /* skip method invocation */
+            FINISH(3);
+        } else {
+            /* behave like OP_INVOKE_DIRECT */
+            GOTO_invoke(invokeDirect, false, false);
+        }
 #else
-    if (!DEBUGGER_ACTIVE) {
-        //LOGI("Skipping empty\n");
-        FINISH(3);      // don't want it to show up in profiler output
-    } else {
-        //LOGI("Running empty\n");
-        /* fall through to OP_INVOKE_DIRECT */
-        GOTO_invoke(invokeDirect, false, false);
-    }
+        /* debugger can't be attached, skip method invocation */
+        FINISH(3);
 #endif
+    }
 OP_END
 
 /* File: c/OP_RETURN_VOID_BARRIER.c */
diff --git a/vm/mterp/out/InterpC-armv5te-vfp.c b/vm/mterp/out/InterpC-armv5te-vfp.c
index 0643dec..8fc5605 100644
--- a/vm/mterp/out/InterpC-armv5te-vfp.c
+++ b/vm/mterp/out/InterpC-armv5te-vfp.c
@@ -499,9 +499,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
diff --git a/vm/mterp/out/InterpC-armv5te.c b/vm/mterp/out/InterpC-armv5te.c
index a647643..261891e 100644
--- a/vm/mterp/out/InterpC-armv5te.c
+++ b/vm/mterp/out/InterpC-armv5te.c
@@ -499,9 +499,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
diff --git a/vm/mterp/out/InterpC-armv7-a-neon.c b/vm/mterp/out/InterpC-armv7-a-neon.c
index 0bc7f4e..28603ef 100644
--- a/vm/mterp/out/InterpC-armv7-a-neon.c
+++ b/vm/mterp/out/InterpC-armv7-a-neon.c
@@ -499,9 +499,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
diff --git a/vm/mterp/out/InterpC-armv7-a.c b/vm/mterp/out/InterpC-armv7-a.c
index d771fa6..0d0c8c4 100644
--- a/vm/mterp/out/InterpC-armv7-a.c
+++ b/vm/mterp/out/InterpC-armv7-a.c
@@ -499,9 +499,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
diff --git a/vm/mterp/out/InterpC-portdbg.c b/vm/mterp/out/InterpC-portdbg.c
index 8497502..9cd97eb 100644
--- a/vm/mterp/out/InterpC-portdbg.c
+++ b/vm/mterp/out/InterpC-portdbg.c
@@ -3416,19 +3416,37 @@
 
 /* File: c/OP_INVOKE_OBJECT_INIT.c */
 HANDLE_OPCODE(OP_INVOKE_OBJECT_INIT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
-#if INTERP_TYPE != INTERP_DBG
-    //LOGI("Ignoring empty\n");
-    FINISH(3);
+    {
+        Object* obj;
+
+        vsrc1 = FETCH(2) & 0x0f;        /* reg number of "this" pointer */
+        obj = GET_REGISTER_AS_OBJECT(vsrc1);
+
+        if (!checkForNullExportPC(obj, fp, pc))
+            GOTO_exceptionThrown();
+
+        /*
+         * The object should be marked "finalizable" when Object.<init>
+         * completes normally.  We're going to assume it does complete
+         * (by virtue of being nothing but a return-void) and set it now.
+         */
+        if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE)) {
+            dvmSetFinalizable(obj);
+        }
+
+#if INTERP_TYPE == INTERP_DBG
+        if (!DEBUGGER_ACTIVE) {
+            /* skip method invocation */
+            FINISH(3);
+        } else {
+            /* behave like OP_INVOKE_DIRECT */
+            GOTO_invoke(invokeDirect, false, false);
+        }
 #else
-    if (!DEBUGGER_ACTIVE) {
-        //LOGI("Skipping empty\n");
-        FINISH(3);      // don't want it to show up in profiler output
-    } else {
-        //LOGI("Running empty\n");
-        /* fall through to OP_INVOKE_DIRECT */
-        GOTO_invoke(invokeDirect, false, false);
-    }
+        /* debugger can't be attached, skip method invocation */
+        FINISH(3);
 #endif
+    }
 OP_END
 
 /* File: c/OP_RETURN_VOID_BARRIER.c */
diff --git a/vm/mterp/out/InterpC-portstd.c b/vm/mterp/out/InterpC-portstd.c
index 08d06b8..43bcb44 100644
--- a/vm/mterp/out/InterpC-portstd.c
+++ b/vm/mterp/out/InterpC-portstd.c
@@ -3166,19 +3166,37 @@
 
 /* File: c/OP_INVOKE_OBJECT_INIT.c */
 HANDLE_OPCODE(OP_INVOKE_OBJECT_INIT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
-#if INTERP_TYPE != INTERP_DBG
-    //LOGI("Ignoring empty\n");
-    FINISH(3);
+    {
+        Object* obj;
+
+        vsrc1 = FETCH(2) & 0x0f;        /* reg number of "this" pointer */
+        obj = GET_REGISTER_AS_OBJECT(vsrc1);
+
+        if (!checkForNullExportPC(obj, fp, pc))
+            GOTO_exceptionThrown();
+
+        /*
+         * The object should be marked "finalizable" when Object.<init>
+         * completes normally.  We're going to assume it does complete
+         * (by virtue of being nothing but a return-void) and set it now.
+         */
+        if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE)) {
+            dvmSetFinalizable(obj);
+        }
+
+#if INTERP_TYPE == INTERP_DBG
+        if (!DEBUGGER_ACTIVE) {
+            /* skip method invocation */
+            FINISH(3);
+        } else {
+            /* behave like OP_INVOKE_DIRECT */
+            GOTO_invoke(invokeDirect, false, false);
+        }
 #else
-    if (!DEBUGGER_ACTIVE) {
-        //LOGI("Skipping empty\n");
-        FINISH(3);      // don't want it to show up in profiler output
-    } else {
-        //LOGI("Running empty\n");
-        /* fall through to OP_INVOKE_DIRECT */
-        GOTO_invoke(invokeDirect, false, false);
-    }
+        /* debugger can't be attached, skip method invocation */
+        FINISH(3);
 #endif
+    }
 OP_END
 
 /* File: c/OP_RETURN_VOID_BARRIER.c */
diff --git a/vm/mterp/out/InterpC-x86-atom.c b/vm/mterp/out/InterpC-x86-atom.c
index 98d5c27..f98969a 100644
--- a/vm/mterp/out/InterpC-x86-atom.c
+++ b/vm/mterp/out/InterpC-x86-atom.c
@@ -499,9 +499,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
@@ -1335,6 +1335,41 @@
 HANDLE_SPUT_X(OP_SPUT_WIDE_VOLATILE,    "-wide-volatile", LongVolatile, _WIDE)
 OP_END
 
+/* File: c/OP_INVOKE_OBJECT_INIT.c */
+HANDLE_OPCODE(OP_INVOKE_OBJECT_INIT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
+    {
+        Object* obj;
+
+        vsrc1 = FETCH(2) & 0x0f;        /* reg number of "this" pointer */
+        obj = GET_REGISTER_AS_OBJECT(vsrc1);
+
+        if (!checkForNullExportPC(obj, fp, pc))
+            GOTO_exceptionThrown();
+
+        /*
+         * The object should be marked "finalizable" when Object.<init>
+         * completes normally.  We're going to assume it does complete
+         * (by virtue of being nothing but a return-void) and set it now.
+         */
+        if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE)) {
+            dvmSetFinalizable(obj);
+        }
+
+#if INTERP_TYPE == INTERP_DBG
+        if (!DEBUGGER_ACTIVE) {
+            /* skip method invocation */
+            FINISH(3);
+        } else {
+            /* behave like OP_INVOKE_DIRECT */
+            GOTO_invoke(invokeDirect, false, false);
+        }
+#else
+        /* debugger can't be attached, skip method invocation */
+        FINISH(3);
+#endif
+    }
+OP_END
+
 /* File: c/OP_RETURN_VOID_BARRIER.c */
 HANDLE_OPCODE(OP_RETURN_VOID_BARRIER /**/)
     ILOGV("|return-void");
diff --git a/vm/mterp/out/InterpC-x86.c b/vm/mterp/out/InterpC-x86.c
index 8d47209..7445ad4 100644
--- a/vm/mterp/out/InterpC-x86.c
+++ b/vm/mterp/out/InterpC-x86.c
@@ -499,9 +499,9 @@
         return;                                                             \
     } while(false)
 
-#define GOTO_invoke(_target, _methodCallRange)                              \
+#define GOTO_invoke(_target, _methodCallRange, _jumboFormat)                \
     do {                                                                    \
-        dvmMterp_##_target(glue, _methodCallRange);                         \
+        dvmMterp_##_target(glue, _methodCallRange, _jumboFormat);           \
         return;                                                             \
     } while(false)
 
@@ -1360,6 +1360,41 @@
     FINISH(3);
 OP_END
 
+/* File: c/OP_INVOKE_OBJECT_INIT.c */
+HANDLE_OPCODE(OP_INVOKE_OBJECT_INIT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
+    {
+        Object* obj;
+
+        vsrc1 = FETCH(2) & 0x0f;        /* reg number of "this" pointer */
+        obj = GET_REGISTER_AS_OBJECT(vsrc1);
+
+        if (!checkForNullExportPC(obj, fp, pc))
+            GOTO_exceptionThrown();
+
+        /*
+         * The object should be marked "finalizable" when Object.<init>
+         * completes normally.  We're going to assume it does complete
+         * (by virtue of being nothing but a return-void) and set it now.
+         */
+        if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISFINALIZABLE)) {
+            dvmSetFinalizable(obj);
+        }
+
+#if INTERP_TYPE == INTERP_DBG
+        if (!DEBUGGER_ACTIVE) {
+            /* skip method invocation */
+            FINISH(3);
+        } else {
+            /* behave like OP_INVOKE_DIRECT */
+            GOTO_invoke(invokeDirect, false, false);
+        }
+#else
+        /* debugger can't be attached, skip method invocation */
+        FINISH(3);
+#endif
+    }
+OP_END
+
 /* File: c/OP_RETURN_VOID_BARRIER.c */
 HANDLE_OPCODE(OP_RETURN_VOID_BARRIER /**/)
     ILOGV("|return-void");
diff --git a/vm/mterp/x86-atom/OP_INVOKE_OBJECT_INIT.S b/vm/mterp/x86-atom/OP_INVOKE_OBJECT_INIT.S
index 16ae56e..faf1565 100644
--- a/vm/mterp/x86-atom/OP_INVOKE_OBJECT_INIT.S
+++ b/vm/mterp/x86-atom/OP_INVOKE_OBJECT_INIT.S
@@ -16,11 +16,10 @@
    /*
     * File: OP_INVOKE_OBJECT_INIT.S
     *
-    * Code: Used as a no-op. Uses no substitutions.
+    * Code: TODO
     *
     * For: invoke-object-init
     *
     * Format: B|A|op CCCC G|F|E|D (35c)
     */
 
-    FINISH 3
diff --git a/vm/mterp/x86-atom/TODO.txt b/vm/mterp/x86-atom/TODO.txt
index 7dc624e..df094a6 100644
--- a/vm/mterp/x86-atom/TODO.txt
+++ b/vm/mterp/x86-atom/TODO.txt
@@ -23,3 +23,4 @@
 (lo) Implement OP_EXECUTE_INLINE_RANGE
 (lo) Implement OP_*_VOLATILE (12 instructions)
 (lo) Implement OP_RETURN_VOID_BARRIER
+(lo) Implement OP_INVOKE_OBJECT_INIT
diff --git a/vm/mterp/x86/OP_INVOKE_OBJECT_INIT.S b/vm/mterp/x86/OP_INVOKE_OBJECT_INIT.S
index 6402392..fb84b32 100644
--- a/vm/mterp/x86/OP_INVOKE_OBJECT_INIT.S
+++ b/vm/mterp/x86/OP_INVOKE_OBJECT_INIT.S
@@ -1,7 +1,4 @@
 %verify "executed"
     /*
-     * invoke-object-init is a no-op in a "standard" interpreter.
+     * TODO (currently punting to stub)
      */
-    FETCH_INST_WORD 3
-    ADVANCE_PC 3
-    GOTO_NEXT