changeset 14055:5d236453e343

Interpreter: misc cleanups, extend special handling for final field writes to PUTFIELD as well as PUTSTATIC
author briangoetz
date Fri, 10 Jun 2016 12:40:30 -0400
parents 2e69293d5e12
children 6821244a6e2c
files interpreter/src/valhalla/interpreter/Frame.java interpreter/src/valhalla/interpreter/InternalHelpers.java interpreter/src/valhalla/interpreter/Interpreter.java interpreter/test-helpers/test/valhalla/interpreter/ConstructorTestHelper.java interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper5.java interpreter/test-helpers/test/valhalla/interpreter/StaticInitTestHelper.java interpreter/test/valhalla/interpreter/FrameTest.java interpreter/test/valhalla/interpreter/InterpretBootclassTest.java interpreter/test/valhalla/interpreter/InterpreterTest.java interpreter/test/valhalla/interpreter/InterpreterTestCase.java
diffstat 10 files changed, 313 insertions(+), 270 deletions(-) [+]
line wrap: on
line diff
--- a/interpreter/src/valhalla/interpreter/Frame.java	Fri Jun 10 00:32:55 2016 -0700
+++ b/interpreter/src/valhalla/interpreter/Frame.java	Fri Jun 10 12:40:30 2016 -0400
@@ -42,12 +42,12 @@
     private final SlotVal[] locals;
     private final SlotVal[] stack;
     private final byte[] bytecode;
+    private final Lookup lookup;
     private Frame caller;
     private int stackTop;
     private int pc;
     private Insn insn;
     private String expectedResult;
-    private Lookup lookup;
 
     enum SlotKind {
         REF(Object.class, 1, true),
@@ -91,6 +91,8 @@
     }
 
     private Frame(Interpreter interpreter,
+                  Lookup lookup,
+                  Frame caller,
                   ClassModel clazz,
                   MethodModel method,
                   int maxStack,
@@ -98,6 +100,8 @@
         this.interpreter = interpreter;
         this.clazz = clazz;
         this.method = method;
+        this.lookup = lookup;
+        this.caller = caller;
         this.bytecode = method == null ? null : method.getBytecode();
         this.locals = new SlotVal[maxLocals];
         this.stack = new SlotVal[maxStack];
@@ -106,21 +110,22 @@
     }
 
     public Frame(Interpreter interpreter,
+                 Lookup lookup,
+                 Frame caller,
                  ClassModel clazz,
                  MethodModel method) {
-        this(interpreter, clazz, method, method.getMaxStack(), method.getMaxLocals());
+        this(interpreter, lookup, caller, clazz, method, method.getMaxStack(), method.getMaxLocals());
         insn = method.getInsn(pc);
     }
 
     // For testing
-    public Frame(int maxStack, int maxLocals) {
-        this(null, null, null, maxStack, maxLocals);
+    public Frame(Lookup lookup, int maxStack, int maxLocals) {
+        this(null, lookup, null, null, null, maxStack, maxLocals);
     }
 
     // root frame
     public Frame(Lookup lookup) {
-        this(0, 0);
-        setLookup(lookup);
+        this(lookup, 0, 0);
     }
 
     private void assertValKind(SlotVal slotVal, SlotKind kind) {
@@ -492,6 +497,14 @@
         return slot;
     }
 
+    public Frame getCaller() {
+        return caller;
+    }
+
+    public Lookup getLookup() {
+        return lookup;
+    }
+
     public void istore(int slot, int val) { locals[checkSlot(slot)] = new SlotVal(INT, val); }
     public void lstore(int slot, long val) { locals[checkSlot(slot)] = new SlotVal(LONG, val); locals[checkSlot(slot + 1)] = PAD; }
     public void fstore(int slot, float val) { istore(slot, f2iBitwise(val)); }
@@ -506,22 +519,6 @@
     public Object aload(int i) { return asRef(locals[checkSlot(i)]); }
     public ValueBox vload(int i) { return asVal(locals[checkSlot(i)]); }
 
-    public Frame getCaller() {
-        return caller;
-    }
-
-    public void setCaller(Frame caller) {
-        this.caller = caller;
-    }
-
-    public Lookup getLookup() {
-        return lookup;
-    }
-
-    public void setLookup(Lookup lookup) {
-        this.lookup = lookup;
-    }
-
     private static long d2lBitwise(double d) { return Double.doubleToRawLongBits(d); }
     private static double l2dBitwise(long l) { return Double.longBitsToDouble(l); }
     private static int f2iBitwise(float f) { return Float.floatToRawIntBits(f); }
--- a/interpreter/src/valhalla/interpreter/InternalHelpers.java	Fri Jun 10 00:32:55 2016 -0700
+++ b/interpreter/src/valhalla/interpreter/InternalHelpers.java	Fri Jun 10 12:40:30 2016 -0400
@@ -74,18 +74,6 @@
         return (MethodHandle) MH_MethodHandleNatives_linkMethodHandleConstant.invoke(lookupClass, refKind, owner, name, type);
     }
 
-    private static final MethodHandle MH_MethodHandleNatives_linkMethodHandleConstant;
-
-    static {
-        try {
-            Class<?> methodHandleNativesClass = Class.forName("java.lang.invoke.MethodHandleNatives");
-            MH_MethodHandleNatives_linkMethodHandleConstant = LOOKUP.findStatic(methodHandleNativesClass, "linkMethodHandleConstant", MethodType.methodType(MethodHandle.class, Class.class, int.class, Class.class, String.class, Object.class));
-        }
-        catch (Throwable e) {
-            throw new AssertionError(e);
-        }
-    }
-
     // FIXME: put this as a privileged Lookup method
     static MethodHandle convertVirtualToSpecial(MethodHandleInfo mhi, Class dynamicReceiver) {
         try {
@@ -96,20 +84,6 @@
         }
     }
 
-    private static final MethodHandle MH_Lookup_resolveOrFail;
-    private static final MethodHandle MH_MemberName_getDeclaringClass;
-
-    static {
-        try {
-            Class<?> memberNameClass = Class.forName("java.lang.invoke.MemberName");
-            MH_Lookup_resolveOrFail = LOOKUP.findVirtual(MethodHandles.Lookup.class, "resolveOrFail", MethodType.methodType(memberNameClass, Byte.TYPE, Class.class, String.class, MethodType.class));
-            MH_MemberName_getDeclaringClass = LOOKUP.findVirtual(memberNameClass, "getDeclaringClass", MethodType.methodType(Class.class));
-        }
-        catch (Throwable e) {
-            throw new AssertionError(e);
-        }
-    }
-
     // FIXME: put this as a privileged Lookup method
     static MethodHandle findSpecialConstructor(Class owner, MethodType type) {
         try {
@@ -133,6 +107,7 @@
     private static final MethodHandle MH_MemberName_getMethodType;
     private static final MethodHandle MH_DirectMethodHandle_prepareLambdaForm;
     private static final MethodHandle MH_DirectMethodHandle_Special_new;
+    private static final MethodHandle MH_MethodHandleNatives_linkMethodHandleConstant;
 
     static {
         try {
@@ -141,6 +116,7 @@
             Class<?> directMethodHandleClass = Class.forName("java.lang.invoke.DirectMethodHandle");
             Class<?> directMethodHandleSpecialClass = Class.forName("java.lang.invoke.DirectMethodHandle$Special");
             Class<?> lambdaFormClass = Class.forName("java.lang.invoke.LambdaForm");
+            Class<?> methodHandleNativesClass = Class.forName("java.lang.invoke.MethodHandleNatives");
             MH_MemberName_init = LOOKUP.findConstructor(memberNameClass, MethodType.methodType(void.class, Class.class, String.class, MethodType.class, byte.class));
             MH_MemberName_getFactory = LOOKUP.findStatic(memberNameClass, "getFactory", MethodType.methodType(memberNameFactoryClass));
             MH_MemberNameFactory_resolveOrFail = LOOKUP.findVirtual(memberNameFactoryClass, "resolveOrFail", MethodType.methodType(memberNameClass, byte.class, memberNameClass, Class.class, Class.class));
@@ -148,6 +124,7 @@
             MH_MemberName_getMethodType = LOOKUP.findVirtual(memberNameClass, "getMethodType", MethodType.methodType(MethodType.class));
             MH_DirectMethodHandle_prepareLambdaForm = LOOKUP.findStatic(directMethodHandleClass, "preparedLambdaForm", MethodType.methodType(lambdaFormClass, memberNameClass));
             MH_DirectMethodHandle_Special_new = LOOKUP.findConstructor(directMethodHandleSpecialClass, MethodType.methodType(void.class, MethodType.class, lambdaFormClass, memberNameClass));
+            MH_MethodHandleNatives_linkMethodHandleConstant = LOOKUP.findStatic(methodHandleNativesClass, "linkMethodHandleConstant", MethodType.methodType(MethodHandle.class, Class.class, int.class, Class.class, String.class, Object.class));
         }
         catch (Throwable e) {
             throw new AssertionError(e);
--- a/interpreter/src/valhalla/interpreter/Interpreter.java	Fri Jun 10 00:32:55 2016 -0700
+++ b/interpreter/src/valhalla/interpreter/Interpreter.java	Fri Jun 10 12:40:30 2016 -0400
@@ -24,7 +24,6 @@
  */
 package valhalla.interpreter;
 
-import com.sun.tools.classfile.Opcode;
 import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.internal.org.objectweb.asm.Type;
 import sun.misc.Resource;
@@ -32,7 +31,6 @@
 import valhalla.interpreter.OpcodeHandler.HandlerAction;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.lang.invoke.*;
 import java.lang.invoke.MethodHandles.Lookup;
@@ -109,7 +107,6 @@
 
         @Override
         protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-            System.err.println("ClassLoader: " + name);
             ClassModel cm = interpreter.tryLoad(name.replace('.', '/'));
             if (cm != null)
                 return cm.getRepresentationClass();
@@ -118,6 +115,13 @@
         }
     }
 
+    protected void trace(Frame f, String message) {
+        if (TRACING)
+            System.out.printf("%s[bci=%d]: opcode=%d (%s) %s %n",
+                              f.curMethod().getName(), f.curBCI(), f.curOpcode(), opcodeToString(f.curOpcode()),
+                              message.isEmpty() ? "" : "-- " + message);
+    }
+
     protected Object loadConstant(Frame f, int index) {
         int tag = f.curClass().cpKind(index);
         switch (tag) {
@@ -234,7 +238,7 @@
                 case H_INVOKEVIRTUAL:
                 case H_INVOKEINTERFACE:
                     // do a dispatch to find out where the bytecodes really are
-                    selected = doMethodSelection(mhi, args[0].getClass());
+                    selected = tryMethodSelection(mhi, args[0].getClass());
                     if (selected != null) {
                         mhi = InternalHelpers.crackMethodHandle(selected);
                         // method selection should not select an abstract method
@@ -285,20 +289,15 @@
     }
 
     private Frame makeNewFrame(Frame caller, ClassModel cm, Lookup lookup, boolean isStatic, String methodName, String methodDesc) {
-        Frame f = cm.methods()
-                    .filter(m -> m.getName().equals(methodName)
-                                 && m.getDesc().equals(methodDesc)
-                                 && ((m.getAccess() & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0)
-                                 && ((isStatic && (m.getAccess() & Opcodes.ACC_STATIC) != 0)
-                                     || (!isStatic && (m.getAccess() & Opcodes.ACC_STATIC) == 0)))
-                    .findFirst()
-                    .map(mm -> new Frame(this, cm, mm))
-                    .orElse(null);
-        if (f == null)
-            return null;
-        f.setCaller(caller);
-        f.setLookup(lookup);
-        return f;
+        return cm.methods()
+                 .filter(m -> m.getName().equals(methodName)
+                              && m.getDesc().equals(methodDesc)
+                              && ((m.getAccess() & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0)
+                              && ((isStatic && (m.getAccess() & Opcodes.ACC_STATIC) != 0)
+                                  || (!isStatic && (m.getAccess() & Opcodes.ACC_STATIC) == 0)))
+                 .findFirst()
+                 .map(mm -> new Frame(this, lookup, caller, cm, mm))
+                 .orElse(null);
     }
 
     // Helper methods for opcode handlers
@@ -307,31 +306,40 @@
         ClassModel.MemberDesc ref = f.readMemberRef(1);
         Class type = MethodType.fromMethodDescriptorString("()" + ref.desc, f.curClassLoader()).returnType();
 
-        // Special handling for PUTSTATIC to own fields from <clinit>: use reflection instead of MHI
-        if (f.curOpcode() == PUTSTATIC
-            && f.curMethod().getName().equals("<clinit>")
+        // Special handling for writes to own fields from ctor/static initializer: use reflection instead of MHI
+        if (((f.curOpcode() == PUTSTATIC && f.curMethod().getName().equals("<clinit>"))
+            || (f.curOpcode() == PUTFIELD && f.curMethod().getName().equals("<init>")))
             && ref.owner.equals(f.curClass().getName())) {
             try {
-                if (TRACING)
-                    System.out.printf("%s[bci=%d]: opcode=%d (%s) -- special PUTSTATIC handling %n",
-                                      f.curMethod().getName(), f.curBCI(), f.curOpcode(), opcodeToString(f.curOpcode()));
-                Field field = f.curClass().getRepresentationClass().getDeclaredField(ref.name);
-                field.setAccessible(true);
-                Field modifiersField = Field.class.getDeclaredField("modifiers");
-                modifiersField.setAccessible(true);
-                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
-                field.set(null, f.popBoxed(ref.desc));
+                trace(f, "special field write handling for " + ref.name);
+                Field field = finalFieldSetter(f.curClass().getRepresentationClass(), ref.name);
+                if (f.curOpcode() == PUTSTATIC)
+                    field.set(null, f.popBoxed(ref.desc));
+                else {
+                    Object val = f.popBoxed(ref.desc);
+                    Object receiver = f.apop();
+                    field.set(receiver, val);
+                }
                 return next();
             }
             catch (ReflectiveOperationException e) {
                 if (TRACING)
-                    System.out.printf("Exception looking up field: " + e);
+                    System.out.printf("Exception in field lookup: " + e);
                 // fall through to standard processing
             }
         }
         return memberOp(f, toClass(f, ref.owner), ref.name, type);
     }
 
+    private Field finalFieldSetter(Class<?> clazz, String name) throws ReflectiveOperationException {
+        Field field = clazz.getDeclaredField(name);
+        field.setAccessible(true);
+        Field modifiersField = Field.class.getDeclaredField("modifiers");
+        modifiersField.setAccessible(true);
+        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+        return field;
+    }
+
     protected HandlerAction invokeOp(Frame f) throws InterpreterError {
         ClassModel.MemberDesc ref = f.readMemberRef(1);
         MethodType methodType = MethodType.fromMethodDescriptorString(ref.desc, f.curClassLoader());
@@ -524,21 +532,13 @@
                                   Class owner,
                                   String name,
                                   Object type)
-            throws LinkageError
-    {
-        switch (opcode) {
-            case INVOKEVIRTUAL:
-                if (owner.isInterface())  // corner case for List.hashCode (ugly!)
-                    opcode = INVOKEINTERFACE;
-                break;
-
-            case INVOKESPECIAL:
-                if (name.equals("<init>"))
-                    return InternalHelpers.findSpecialConstructor(owner, (MethodType) type);
-                break;
-        }
+            throws LinkageError {
+        if (opcode == INVOKEVIRTUAL && owner.isInterface())  // corner case for List.hashCode (ugly!)
+            opcode = INVOKEINTERFACE;
         try {
-            return InternalHelpers.linkMethodHandleConstant(lookup.lookupClass(), opcodeToRefKind(opcode), owner, name, type);
+            return opcode == INVOKESPECIAL && name.equals("<init>")
+                   ? InternalHelpers.findSpecialConstructor(owner, (MethodType) type)
+                   : InternalHelpers.linkMethodHandleConstant(lookup.lookupClass(), opcodeToRefKind(opcode), owner, name, type);
         } catch (Error ex) {
             throw ex;
         } catch (Throwable ex) {
@@ -547,11 +547,11 @@
     }
 
     MethodHandle doMethodSelection(MethodHandle mh, Class dynamicReceiver) throws InterpreterError {
-        MethodHandle res = doMethodSelection(InternalHelpers.crackMethodHandle(mh), dynamicReceiver);
+        MethodHandle res = tryMethodSelection(InternalHelpers.crackMethodHandle(mh), dynamicReceiver);
         return (res != null) ? res : mh;
     }
 
-    private MethodHandle doMethodSelection(MethodHandleInfo mhi, Class dynamicReceiver) throws InterpreterError {
+    private MethodHandle tryMethodSelection(MethodHandleInfo mhi, Class dynamicReceiver) throws InterpreterError {
         switch (mhi.getReferenceKind()) {
             case H_INVOKEVIRTUAL:
             case H_INVOKEINTERFACE:
@@ -572,8 +572,7 @@
                 OpcodeHandler handler = insn.getHandler();
                 if (handler == null)
                     throw new InterpreterError(f, "No handler for opcode " + insn);
-                if (TRACING)
-                    System.out.printf("%s[bci=%d]: opcode=%d (%s) %n", f.curMethod().getName(), f.curBCI(), f.curOpcode(), opcodeToString(f.curOpcode()));
+                trace(f, "");
 
                 HandlerAction a = handler.handle(f);
                 switch (a.actionKind) {
@@ -597,8 +596,7 @@
                             continue;
                         }
                         // else throw out to caller frame
-                        if (TRACING)
-                            System.out.printf("%s[bci=%d]: opcode=%d (%s), throwing %s %n", f.curMethod().getName(), f.curBCI(), f.curOpcode(), opcodeToString(f.curOpcode()), a.returnValue);
+                        trace(f, String.format("throwing %s", a.returnValue));
 
                         return a;
                 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/ConstructorTestHelper.java	Fri Jun 10 12:40:30 2016 -0400
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package valhalla.interpreter;
+
+/**
+ * ConstructorTestHelper
+ *
+ * @author Brian Goetz
+ */
+public class ConstructorTestHelper {
+    static class CTH {
+        public int x;
+        public final int y;
+
+        public CTH(int x, int y) {
+            this.x = x;
+            this.y = y;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public int getY() {
+            return y;
+        }
+    }
+
+    public static Object testConstructor(int x, int y) {
+        CTH cth = new CTH(x, y);
+        if (cth.x != x || cth.y != y)
+            throw new AssertionError("constructor error");
+        return cth;
+    }
+}
--- a/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper5.java	Fri Jun 10 00:32:55 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-package valhalla.interpreter;/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/**
- * InterpreterTestHelper5
- *
- * @author Brian Goetz
- */
-class InterpreterTestHelper5 {
-    public static int x = 3;
-    public static final String y = InterpreterTestHelper5.class.getName();
-
-    public static int x() { return x; }
-    public static String y() { return y; }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/StaticInitTestHelper.java	Fri Jun 10 12:40:30 2016 -0400
@@ -0,0 +1,37 @@
+package valhalla.interpreter;/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * InterpreterTestHelper5
+ *
+ * @author Brian Goetz
+ */
+class StaticInitTestHelper {
+    public static int x = 3;
+    public static final String y = StaticInitTestHelper.class.getName();
+
+    public static int x() { return x; }
+    public static String y() { return y; }
+}
--- a/interpreter/test/valhalla/interpreter/FrameTest.java	Fri Jun 10 00:32:55 2016 -0700
+++ b/interpreter/test/valhalla/interpreter/FrameTest.java	Fri Jun 10 12:40:30 2016 -0400
@@ -54,23 +54,23 @@
     }
 
     public void testPopEmpty() {
-        Frame f = new Frame(1, 0);
+        Frame f = new Frame(null, 1, 0);
         assertThrows(() -> f.pop());
     }
 
     public void testPushPopInt() {
-        Frame f1 = new Frame(1, 0);
+        Frame f1 = new Frame(null, 1, 0);
         f1.ipush(Integer.MAX_VALUE);
         assertTrue(f1.ipop() == Integer.MAX_VALUE);
         assertThrows(() -> f1.ipop());
 
-        Frame f2 = new Frame(1, 0);
+        Frame f2 = new Frame(null, 1, 0);
         f1.ipush(Integer.MAX_VALUE);
         assertThrows(() -> f1.apop());
     }
 
     public void testPushPopRef() {
-        Frame f1 = new Frame(2, 0);
+        Frame f1 = new Frame(null, 2, 0);
         f1.apush("a");
         f1.apush("b");
         assertTrue(f1.apop().equals("b"));
@@ -79,67 +79,67 @@
     }
 
     public void testPushPopLong() {
-        Frame f = new Frame(2, 0);
+        Frame f = new Frame(null, 2, 0);
         f.lpush(Long.MAX_VALUE);
         assertTrue(f.lpop() == Long.MAX_VALUE);
         assertThrows(() -> f.lpop());
 
-        Frame f2 = new Frame(1, 0);
+        Frame f2 = new Frame(null, 1, 0);
         assertThrows(() -> f2.lpush(0));
     }
 
     public void testOverflow() {
-        assertThrows(() -> new Frame(0, 0).ipush(0));
-        assertThrows(() -> new Frame(0, 0).lpush(0L));
-        assertThrows(() -> new Frame(0, 0).apush(0));
+        assertThrows(() -> new Frame(null, 0, 0).ipush(0));
+        assertThrows(() -> new Frame(null, 0, 0).lpush(0L));
+        assertThrows(() -> new Frame(null, 0, 0).apush(0));
 
-        Frame f1 = new Frame(1, 0);
+        Frame f1 = new Frame(null, 1, 0);
         f1.ipush(0);
         assertThrows(() -> f1.ipush(2));
 
-        Frame f2 = new Frame(1, 0);
+        Frame f2 = new Frame(null, 1, 0);
         f2.apush("");
         assertThrows(() -> f2.apush(""));
 
-        Frame f3 = new Frame(2, 0);
+        Frame f3 = new Frame(null, 2, 0);
         f3.lpush(3L);
         assertThrows(() -> f2.apush(""));
     }
 
     public void testIntLong() {
-        Frame f1 = new Frame(2, 0);
+        Frame f1 = new Frame(null, 2, 0);
         f1.ipush(0);
         f1.ipush(0);
         assertThrows(() -> f1.lpop());
 
-        Frame f2 = new Frame(2, 0);
+        Frame f2 = new Frame(null, 2, 0);
         f2.lpush(0);
         assertThrows(() -> f2.ipop());
     }
 
     public void testMixedPushPop() {
-        Frame f1 = new Frame(2, 0);
+        Frame f1 = new Frame(null, 2, 0);
         f1.ipush(0);
         assertThrows(() -> f1.apop());
 
-        Frame f2 = new Frame(2, 0);
+        Frame f2 = new Frame(null, 2, 0);
         f2.lpush(0);
         assertThrows(() -> f2.apop());
 
-        Frame f3 = new Frame(2, 0);
+        Frame f3 = new Frame(null, 2, 0);
         f3.apush(0);
         assertThrows(() -> f3.ipop());
     }
 
     public void testLoadStore() {
-        assertThrows(() -> new Frame(0, 1).istore(1, 1));
-        assertThrows(() -> new Frame(0, 1).istore(-1, 1));
-        assertThrows(() -> new Frame(0, 1).astore(1, 1));
-        assertThrows(() -> new Frame(0, 1).astore(-1, 1));
-        assertThrows(() -> new Frame(0, 1).lstore(1, 1));
-        assertThrows(() -> new Frame(0, 1).lstore(-1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).istore(1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).istore(-1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).astore(1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).astore(-1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).lstore(1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).lstore(-1, 1));
 
-        Frame f1 = new Frame(0, 1);
+        Frame f1 = new Frame(null, 0, 1);
         f1.istore(0, 3);
         assertEquals(f1.iload(0), 3);
 
@@ -148,7 +148,7 @@
 
         assertThrows(() -> f1.lstore(0, 0L));
 
-        Frame f2 = new Frame(0, 2);
+        Frame f2 = new Frame(null, 0, 2);
         f2.lstore(0, Long.MAX_VALUE);
         assertEquals(f2.lload(0), Long.MAX_VALUE);
     }
--- a/interpreter/test/valhalla/interpreter/InterpretBootclassTest.java	Fri Jun 10 00:32:55 2016 -0700
+++ b/interpreter/test/valhalla/interpreter/InterpretBootclassTest.java	Fri Jun 10 12:40:30 2016 -0400
@@ -24,20 +24,13 @@
  */
 package valhalla.interpreter;
 
-import java.io.IOException;
 import java.util.AbstractList;
 import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
 
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import static java.util.stream.Collectors.toList;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 /**
  * InterpretBootclassTest
@@ -45,26 +38,7 @@
  * @author Brian Goetz
  */
 @Test
-public class InterpretBootclassTest {
-    Interpreter interpreter;
-
-    @BeforeMethod
-    public void setUp() throws IOException {
-        interpreter = new StandardInterpreter("out/test/test-helpers");
-        interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
-        // interpreter.setEventListener(System.err::println);
-    }
-
-    @AfterMethod
-    public void assertLog() {
-        // It's OK if the interpreter executes into field MHs, or anonymous classes
-        for (InterpreterEvent event : interpreter.log) {
-            if (event.kind == InterpreterEvent.Kind.EXECUTE && event.clazz.startsWith("valhalla.interpreter.")) {
-                if (!event.name.matches("^(putField|getField|putStatic|getStatic):.*") && !event.clazz.contains("/"))
-                    fail("Questionable execution event: " + event);
-            }
-        }
-    }
+public class InterpretBootclassTest extends InterpreterTestCase {
 
     public void testArrayList() throws Throwable {
         interpreter.addBootclass(ArrayList.class);
@@ -73,24 +47,10 @@
         assertTrue(o instanceof ArrayList);
         assertEquals("[a, b, c]", o.toString());
 
-        // Assertions against log stream
-
-        List<String> interpretedMethods
-                = interpreter.log.stream()
-                                 .filter(e -> e.kind == InterpreterEvent.Kind.INTERPRET)
-                                 .filter(e -> e.clazz.equals("java.util.ArrayList"))
-                                 .map(e -> e.name)
-                                 .collect(toList());
-        assertTrue(interpretedMethods.contains("<init>"));
-        assertTrue(interpretedMethods.contains("add"));
-        assertTrue(interpretedMethods.contains("contains"));
-
-        interpreter.log.stream()
-                       .filter(e -> e.kind == InterpreterEvent.Kind.EXECUTE)
-                       .filter(e -> e.clazz.equals("java.util.AbstractList"))
-                       .map(e -> e.name)
-                       .findAny()
-                       .orElseThrow(() -> new AssertionError("Didn't find EXECUTE AbstractList.<init>"));
+        assertMethodInterpreted("java.util.ArrayList", "<init>");
+        assertMethodInterpreted("java.util.ArrayList", "add");
+        assertMethodInterpreted("java.util.ArrayList", "contains");
+        assertMethodExecuted("java.util.AbstractList", "<init>");
     }
 
     public void testAbstractList() throws Throwable {
@@ -101,21 +61,7 @@
         assertTrue(o instanceof ArrayList);
         assertEquals("[a, b, c]", o.toString());
 
-        // Assertions against log stream
-
-        List<String> interpretedMethods
-                = interpreter.log.stream()
-                                 .filter(e -> e.kind == InterpreterEvent.Kind.INTERPRET)
-                                 .filter(e -> e.clazz.equals("java.util.AbstractList"))
-                                 .map(e -> e.name)
-                                 .collect(toList());
-        assertTrue(interpretedMethods.contains("<init>"));
-
-        interpreter.log.stream()
-                       .filter(e -> e.kind == InterpreterEvent.Kind.EXECUTE)
-                       .filter(e -> e.clazz.equals("java.util.AbstractCollection"))
-                       .map(e -> e.name)
-                       .findAny()
-                       .orElseThrow(() -> new AssertionError("Didn't find EXECUTE AbstractCollection.<init>"));
+        assertMethodInterpreted("java.util.AbstractList", "<init>");
+        assertMethodExecuted("java.util.AbstractCollection", "<init>");
     }
 }
--- a/interpreter/test/valhalla/interpreter/InterpreterTest.java	Fri Jun 10 00:32:55 2016 -0700
+++ b/interpreter/test/valhalla/interpreter/InterpreterTest.java	Fri Jun 10 12:40:30 2016 -0400
@@ -24,18 +24,13 @@
  */
 package valhalla.interpreter;
 
-import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
-import java.util.EnumSet;
 
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 /**
  * InterpreterTest
@@ -43,41 +38,22 @@
  * @author Brian Goetz
  */
 @Test
-public class InterpreterTest {
+public class InterpreterTest extends InterpreterTestCase {
     public static final String HELPER_1 = "valhalla/interpreter/InterpreterTestHelper1";
     public static final String HELPER_3 = "valhalla/interpreter/InterpreterTestHelper3";
     public static final String HELPER_4 = "valhalla/interpreter/InterpreterTestHelper4";
-    public static final String HELPER_5 = "valhalla/interpreter/InterpreterTestHelper5";
+    public static final String STATIC_HELPER = "valhalla/interpreter/StaticInitTestHelper";
+    public static final String CONSTRUCTOR_HELPER = "valhalla/interpreter/ConstructorTestHelper";
 
-    Interpreter interpreter;
-
-    @BeforeMethod
-    public void setUp() throws IOException {
-        interpreter = new StandardInterpreter("out/test/test-helpers");
-        // interpreter.setEventListener(e -> System.err.println(e));
-        interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
+    /** Test that a static initializer actually gets run, with final and nonfinal fields */
+    public void testStaticInit() throws Throwable {
+        Object o = interpreter.invokestatic(STATIC_HELPER, "x", "()I");
+        assertEquals((int) (Integer) o, 3);
+        o = interpreter.invokestatic(STATIC_HELPER, "y", "()Ljava/lang/String;");
+        assertEquals(o, "valhalla.interpreter.StaticInitTestHelper");
     }
 
-    @AfterMethod
-    public void assertLog() {
-        // It's OK if the interpreter executes into field MHs, or anonymous classes
-        for (InterpreterEvent event : interpreter.log) {
-            if (event.kind == InterpreterEvent.Kind.EXECUTE && event.clazz.startsWith("valhalla.interpreter.")) {
-                if (!event.name.matches("^(putField|getField|putStatic|getStatic):.*") && !event.clazz.contains("/"))
-                    fail("Questionable execution event: " + event);
-            }
-        }
-    }
-
-    /** Test that a static initializer actually gets run */
-    public void testStaticInit() throws Throwable {
-        Object o = interpreter.invokestatic(HELPER_5, "x", "()I");
-        assertEquals((int) (Integer) o, 3);
-        o = interpreter.invokestatic(HELPER_5, "y", "()Ljava/lang/String;");
-        assertEquals(o, "valhalla.interpreter.InterpreterTestHelper5");
-    }
-
-    public void testLoadSimple() throws Throwable {
+    public void testsStaticCalls() throws Throwable {
         Object o = interpreter.invokestatic(HELPER_1, "one", "()I");
         assertEquals((int) (Integer) o, 1);
 
@@ -104,10 +80,15 @@
 
         o = interpreter.invokestatic(HELPER_1, "thisClass", "()Ljava/lang/Class;");
         assertEquals(o, interpreter.resolveClass(HELPER_1).getRepresentationClass());
+    }
 
+    public void testFactoryMethod() throws Throwable {
         Object instance =
                 interpreter.invokestatic(HELPER_1, "make", "(IJCFDSBZLjava/lang/Object;Ljava/lang/Class;)L" + HELPER_1 + ";",
                                          (int) 3, (long) Long.MAX_VALUE, (char) 'a', 3.14f, 9.9999999999999D, (short) 9999, (byte) 99, true, "blarg", String.class);
+        interpreter.invokestatic(HELPER_1, "assertInstance", "(L" + HELPER_1 + ";)V", instance);
+
+        // Test the fields reflectively too
         Class<?> ic = instance.getClass();
         Field f;
         f = ic.getDeclaredField("i"); f.setAccessible(true);
@@ -130,16 +111,14 @@
         assertEquals(f.get(instance), "blarg");
         f = ic.getDeclaredField("clazz"); f.setAccessible(true);
         assertEquals(f.get(instance), String.class);
-
-        interpreter.invokestatic(HELPER_1, "assertInstance", "(L" + HELPER_1 + ";)V", instance);
     }
 
-    public void testOutboard() throws Throwable {
+    public void testOutboardCall() throws Throwable {
         Integer i = (Integer) interpreter.invokestatic(HELPER_1, "testOutboard", "()I");
         assertEquals((int) i, 3);
     }
 
-    public void testInterface() throws Throwable {
+    public void testInterfaceCall() throws Throwable {
         Integer i = (Integer) interpreter.invokestatic(HELPER_1, "testInterfaceCall", "()I");
         assertEquals((int) i, 3);
         Integer i2 = (Integer) interpreter.invokestatic(HELPER_1, "testInterfaceCall2", "()I");
@@ -234,16 +213,6 @@
             interpreter.invokestatic(HELPER_4, "testidiv", "(II)I", 4, 0));
     }
 
-    private void assertThrows(Class<? extends Throwable> exc, XRunnable test) throws Throwable {
-        try {
-            test.runOrThrow();
-            throw new AssertionError("should not return normally!");
-        } catch (Throwable ex) {
-            if (exc.isInstance(ex))  return;
-            throw ex;
-        }
-    }
-
     public void testCatch() throws Throwable {
         XRunnable donothing = () -> { };
         final String testCatchSig = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;";
@@ -272,6 +241,15 @@
         }
     }
 
+    public void testConstructor() throws Throwable {
+        Object o = interpreter.invokestatic(CONSTRUCTOR_HELPER, "testConstructor", "(II)Ljava/lang/Object;", 7, 9);
+        Class<?> ic = o.getClass();
+        Field f;
+        f = ic.getDeclaredField("x"); f.setAccessible(true);
+        assertEquals(f.get(o), 7);
+        f = ic.getDeclaredField("y"); f.setAccessible(true);
+        assertEquals(f.get(o), 9);
+    }
 }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test/valhalla/interpreter/InterpreterTestCase.java	Fri Jun 10 12:40:30 2016 -0400
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package valhalla.interpreter;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import static java.util.stream.Collectors.toList;
+import static org.testng.Assert.fail;
+
+/**
+ * InterpreterTestCase
+ *
+ * @author Brian Goetz
+ */
+public class InterpreterTestCase {
+    Interpreter interpreter;
+
+    @BeforeMethod
+    public void setUp() throws IOException {
+        interpreter = new StandardInterpreter("out/test/test-helpers");
+        interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
+    }
+
+    @AfterMethod
+    public void assertLog() {
+        // It's OK if the interpreter executes into field MHs, or anonymous classes
+        for (InterpreterEvent event : interpreter.log) {
+            if (event.kind == InterpreterEvent.Kind.EXECUTE && event.clazz.startsWith("valhalla.interpreter.")) {
+                if (!event.name.matches("^(putField|getField|putStatic|getStatic):.*") && !event.clazz.contains("/"))
+                    fail("Questionable execution event: " + event);
+            }
+        }
+    }
+
+    protected void assertMethodExecuted(String clazz, String name) {
+        interpreter.log.stream()
+                       .filter(e -> e.kind == InterpreterEvent.Kind.EXECUTE)
+                       .filter(e -> e.clazz.equals(clazz))
+                       .filter(e -> e.name.equals(name))
+                       .map(e -> e.name)
+                       .findAny()
+                       .orElseThrow(() -> new AssertionError(String.format("Didn't find EXECUTE %s.%s", clazz, name)));
+    }
+
+    protected void assertMethodInterpreted(String clazz, String name) {
+        interpreter.log.stream()
+                       .filter(e -> e.kind == InterpreterEvent.Kind.INTERPRET)
+                       .filter(e -> e.clazz.equals(clazz))
+                       .filter(e -> e.name.equals(name))
+                       .findAny()
+                       .orElseThrow(() -> new AssertionError(String.format("Didn't find INTERPRET %s.%s", clazz, name)));
+    }
+
+    protected void assertThrows(Class<? extends Throwable> exc, XRunnable test) throws Throwable {
+        try {
+            test.runOrThrow();
+            throw new AssertionError("should not return normally!");
+        } catch (Throwable ex) {
+            if (exc.isInstance(ex))  return;
+            throw ex;
+        }
+    }
+}