changeset 14048:cf12b592bf8e

Migrate interpreter from ASM to JDK ClassFile parser; increase transparency between interpreter and bytecode so as to better support experimental bytecode forms.
author briangoetz
date Thu, 09 Jun 2016 11:04:48 -0400
parents b0f73e4de9d6
children 786c857536a9
files interpreter/src/valhalla/interpreter/ClassModel.java interpreter/src/valhalla/interpreter/Frame.java interpreter/src/valhalla/interpreter/Insn.java interpreter/src/valhalla/interpreter/InternalHelpers.java interpreter/src/valhalla/interpreter/Interpreter.java interpreter/src/valhalla/interpreter/InterpreterError.java interpreter/src/valhalla/interpreter/MethodModel.java interpreter/src/valhalla/interpreter/OpcodeHandler.java interpreter/src/valhalla/interpreter/ProxyClassBuilder.java interpreter/src/valhalla/interpreter/StandardClassModel.java interpreter/src/valhalla/interpreter/StandardInterpreter.java interpreter/src/valhalla/interpreter/StandardMethodModel.java interpreter/src/valhalla/interpreter/Util.java interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper1.java interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper3.java interpreter/test/valhalla/interpreter/InterpretBootclassTest.java interpreter/test/valhalla/interpreter/InterpreterTest.java interpreter/test/valhalla/interpreter/ResolveTest.java
diffstat 18 files changed, 1493 insertions(+), 640 deletions(-) [+]
line wrap: on
line diff
--- a/interpreter/src/valhalla/interpreter/ClassModel.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/src/valhalla/interpreter/ClassModel.java	Thu Jun 09 11:04:48 2016 -0400
@@ -24,49 +24,66 @@
  */
 package valhalla.interpreter;
 
-import jdk.internal.org.objectweb.asm.tree.ClassNode;
-import jdk.internal.org.objectweb.asm.tree.FieldNode;
-import jdk.internal.org.objectweb.asm.tree.MethodNode;
+import java.util.stream.Stream;
 
 /**
  * ClassModel
  *
  * @author Brian Goetz
  */
-public class ClassModel {
-    public final String binaryName;
-    public final ClassNode cn;
-    private ProxyClassBuilder proxyClassBuilder;
-    private Class<?> representationClass;
+public interface ClassModel {
+    Interpreter interpreter();
+    String getName();
+    Stream<MethodModel> methods();
+    Class<?> getRepresentationClass();
 
-    public ClassModel(Class<?> nativeClass) {
-        this.binaryName = nativeClass.getName();
-        this.cn = null;
-        this.representationClass = nativeClass;
+    int cpKind(int index);
+    String cpUTF8(int index);
+    String cpClass(int index);
+    MemberDesc cpMemberRef(int index);
+    String cpMethodType(int index);
+    MethodHandleDesc cpMethodHandle(int index);
+    IndyDesc cpIndy(int index);
+    Object cpSimpleConstant(int index);
+
+
+    class MemberDesc {
+        public final String owner;
+        public final String name;
+        public final String desc;
+
+        public MemberDesc(String owner, String name, String desc) {
+            this.owner = owner;
+            this.name = name;
+            this.desc = desc;
+        }
     }
 
-    public ClassModel(Interpreter interpreter, String binaryName, ClassNode cn) {
-        this.binaryName = binaryName;
-        this.cn = cn;
-        proxyClassBuilder = ProxyClassBuilder.make(interpreter, cn.access, cn.name, cn.superName, cn.interfaces.stream().toArray(String[]::new));
-        for (FieldNode f : cn.fields)
-            proxyClassBuilder.addField(f.access, f.name, f.desc);
-        for (MethodNode m : cn.methods)
-            proxyClassBuilder.addMethod(m.access, m.name, m.desc);
+    class IndyDesc {
+        public final String invokeName;
+        public final String invokeDesc;
+        public final int bootstrapCPIndex;
+        public final int[] bootstrapArgCPIndexes;
+
+        public IndyDesc(String invokeName, String invokeDesc, int bootstrapCPIndex, int[] indexes) {
+            this.invokeName = invokeName;
+            this.invokeDesc = invokeDesc;
+            this.bootstrapCPIndex = bootstrapCPIndex;
+            bootstrapArgCPIndexes = indexes;
+        }
     }
 
-    public ClassModel(String binaryName, ClassNode cn, Class<?> nativeClass) {
-        this.binaryName = binaryName;
-        this.representationClass = nativeClass;
-        this.cn = cn;
-    }
+    class MethodHandleDesc {
+        public final int refKind;
+        public final String owner;
+        public final String name;
+        public final String desc;
 
-    public Class<?> getRepresentationClass() {
-        if (representationClass == null && proxyClassBuilder != null) {
-            representationClass = proxyClassBuilder.build();
-            proxyClassBuilder = null;
+        public MethodHandleDesc(int refKind, String owner, String name, String desc) {
+            this.refKind = refKind;
+            this.owner = owner;
+            this.name = name;
+            this.desc = desc;
         }
-
-        return representationClass;
     }
 }
--- a/interpreter/src/valhalla/interpreter/Frame.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/src/valhalla/interpreter/Frame.java	Thu Jun 09 11:04:48 2016 -0400
@@ -25,10 +25,8 @@
 package valhalla.interpreter;
 
 import jdk.internal.org.objectweb.asm.Type;
-import jdk.internal.org.objectweb.asm.tree.*;
 
 import java.lang.invoke.MethodHandles.Lookup;
-import java.util.stream.Stream;
 
 import static valhalla.interpreter.Frame.SlotKind.*;
 
@@ -38,6 +36,19 @@
  * @author Brian Goetz
  */
 public class Frame {
+    private final Interpreter interpreter;
+    private final ClassModel clazz;
+    private final MethodModel method;
+    private final SlotVal[] locals;
+    private final SlotVal[] stack;
+    private final byte[] bytecode;
+    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),
         INT(Integer.class, 1, false),
@@ -49,17 +60,17 @@
         final int slots;
         final boolean nullOK;
 
-        SlotKind(Class<?> repClass, int slots, boolean ok) {
+        SlotKind(Class<?> repClass, int slots, boolean nullOK) {
             this.repClass = repClass;
             this.slots = slots;
-            nullOK = ok;
+            this.nullOK = nullOK;
         }
     }
 
     private final SlotVal PAD = new SlotVal(PADDING, null);
 
     private class ValueBox {
-        ClassNode clazz;
+        ClassModel clazz;
         Object[] fields;
     }
 
@@ -79,43 +90,31 @@
         public String toString() { return kind + ":" + rep; }  // for debugging
     }
 
-    private final Interpreter interpreter;
-    private final ClassNode clazz;
-    private final MethodNode method;
-    private final InsnList instructions;
-    private final SlotVal[] locals;
-    private final SlotVal[] stack;
-    private int stackTop;
-    private int pc;
-    private int nextPc;
-    private Frame caller;
-    private String expectedResult;
-    private Lookup lookup;
-
     private Frame(Interpreter interpreter,
-                  ClassNode clazz,
-                  MethodNode method,
-                  InsnList instructions,
+                  ClassModel clazz,
+                  MethodModel method,
                   int maxStack,
                   int maxLocals) {
         this.interpreter = interpreter;
         this.clazz = clazz;
         this.method = method;
-        this.instructions = instructions;
-        locals = new SlotVal[maxLocals];
-        stack = new SlotVal[maxStack];
+        this.bytecode = method == null ? null : method.getBytecode();
+        this.locals = new SlotVal[maxLocals];
+        this.stack = new SlotVal[maxStack];
         stackTop = -1;
-        pc = skipNonInstructions(0);
-        nextPc = skipNonInstructions(pc + 1);
+        pc = 0;
+    }
+
+    public Frame(Interpreter interpreter,
+                 ClassModel clazz,
+                 MethodModel method) {
+        this(interpreter, clazz, method, method.getMaxStack(), method.getMaxLocals());
+        insn = method.getInsn(pc);
     }
 
     // For testing
     public Frame(int maxStack, int maxLocals) {
-        this(null, null, null, null, maxStack, maxLocals);
-    }
-
-    public Frame(Interpreter interpreter, ClassNode clazz, MethodNode method) {
-        this(interpreter, clazz, method, method.instructions, method.maxStack, method.maxLocals);
+        this(null, null, null, maxStack, maxLocals);
     }
 
     // root frame
@@ -400,6 +399,7 @@
         assert(expectedResult == null);
         expectedResult = descr;
     }
+
     public void pushExpectedResult(Object result) {
         String descr = expectedResult;
         expectedResult = null;
@@ -407,51 +407,81 @@
         if (!"V".equals(descr))
             pushBoxed(descr, result);
     }
+
     public void pushException(Throwable ex) {
         stackTop = -1;
         expectedResult = null;  // cancel normal return expectation
         apush(ex);
     }
 
-    private int skipNonInstructions(int offset) {
-        if (instructions != null)
-            while (offset < instructions.size() && instructions.get(offset).getOpcode() == -1)
-                ++offset;
-        return offset;
-
-    }
     public void advancePC() {
-        pc = nextPc;
-        nextPc = skipNonInstructions(pc + 1);
+        pc += insn.getLength();
+        insn = method.getInsn(pc);
     }
 
-    public void setNextPC(LabelNode label) {
-        nextPc = skipNonInstructions(instructions.indexOf(label));
+    public void branchRelative(int offset) {
+        pc += offset;
+        insn = method.getInsn(pc);
     }
 
-    public Stream<TryCatchBlockNode> currentExceptionHandlers() {
-        if (method.tryCatchBlocks == null || method.tryCatchBlocks.isEmpty())
-            return Stream.empty();
-        return method.tryCatchBlocks.stream().filter(this::handlerIsCurrent);
-    }
-    private boolean handlerIsCurrent(TryCatchBlockNode tcb) {
-        return instructions.indexOf(tcb.start) <= pc && pc < instructions.indexOf(tcb.end);
+    private void boundsCheck(int offset) {
+        if (offset > insn.getLength())
+            throw new IndexOutOfBoundsException(String.format("Attempt to read byte %d of instruction at [%d, %d]", pc+offset, pc, pc+insn.getLength()));
     }
 
-    @SuppressWarnings("unchecked")
-    public<T extends AbstractInsnNode> T curInsn() {
-        return (T) instructions.get(pc);
+    public int read8u(int offset) {
+        boundsCheck(offset + 1);
+        return Util.read8u(bytecode, pc + offset);
     }
-    public int currentPC() { return pc; }
 
-    public ClassNode currentClass() {
+    public int read8s(int offset) {
+        boundsCheck(offset + 1);
+        return Util.read8s(bytecode, pc + offset);
+    }
+
+    public int read16u(int offset) {
+        boundsCheck(offset + 2);
+        return Util.read16u(bytecode, pc + offset);
+    }
+
+    public int read16s(int offset) {
+        boundsCheck(offset + 2);
+        return Util.read16s(bytecode, pc + offset);
+    }
+
+    public int read32s(int offset) {
+        boundsCheck(offset + 4);
+        return Util.read32s(bytecode, pc + offset);
+    }
+
+    public String readClassName(int offset) {
+        return clazz.cpClass(read16u(offset));
+    }
+
+    public ClassModel.MemberDesc readMemberRef(int offset) {
+        return clazz.cpMemberRef(read16u(offset));
+    }
+
+    public Insn curInsn() {
+        return insn;
+    }
+
+    public ClassModel curClass() {
         return clazz;
     }
 
-    public MethodNode currentMethod() {
+    public MethodModel curMethod() {
         return method;
     }
 
+    public int curOpcode() {
+        return insn.getOpcode();
+    }
+
+    public int curBCI() {
+        return pc;
+    }
+
     private int checkSlot(int slot) {
         if (slot < 0 || slot > locals.length - 1)
             throw new InterpreterError("Invalid local variable slot " + slot);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/Insn.java	Thu Jun 09 11:04:48 2016 -0400
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * InsnModel
+ *
+ * @author Brian Goetz
+ */
+public class Insn {
+    private final int opcode;
+    private final int start;
+    private final int length;
+    private final OpcodeHandler handler;
+
+    public Insn(int opcode, int start, int length, OpcodeHandler handler) {
+        this.opcode = opcode;
+        this.start = start;
+        this.length = length;
+        this.handler = handler;
+    }
+
+    public int getOpcode() {
+        return opcode;
+    }
+
+    public int getLength() {
+        return length;
+    }
+
+    public OpcodeHandler getHandler() {
+        return handler;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Instruction{opcode=%d, start=%d, length=%d}", getOpcode(), start, length);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/InternalHelpers.java	Thu Jun 09 11:04:48 2016 -0400
@@ -0,0 +1,158 @@
+/*
+ * 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.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleInfo;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+
+import sun.misc.Unsafe;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESPECIAL;
+
+/**
+ * InternalHelpers -- the part the reaches into the internal parts of the JVM.
+ *
+ * @author John Rose
+ */
+class InternalHelpers {
+    private InternalHelpers() {
+    } // all static
+
+    static final MethodHandles.Lookup LOOKUP;
+    static final Unsafe UNSAFE;
+
+    static {
+        try {
+            Field theLookup = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
+            theLookup.setAccessible(true);
+            LOOKUP = (MethodHandles.Lookup) theLookup.get(null);
+            UNSAFE = (Unsafe) (Object) LOOKUP.findStaticGetter(Unsafe.class, "theUnsafe", Unsafe.class).invoke();
+        }
+        catch (Throwable e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    public static Class<?> loadClass(Interpreter interpreter, String name, byte[] bytes) {
+        return UNSAFE.defineClass(name, bytes, 0, bytes.length, interpreter.classLoader, null);
+    }
+
+    public static MethodHandleInfo crackMethodHandle(MethodHandle mh) {
+        return LOOKUP.revealDirect(mh);
+    }
+
+    static MethodHandles.Lookup makeFullPowerLookup(Class<?> clazz) {
+        return LOOKUP.in(clazz);
+    }
+
+    static MethodHandle linkMethodHandleConstant(Class<?> lookupClass, int refKind, Class owner, String name, Object type) throws Throwable {
+        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 {
+            Object o = MH_Lookup_resolveOrFail.invoke(LOOKUP, (byte) mhi.getReferenceKind(), dynamicReceiver, mhi.getName(), mhi.getMethodType());
+            Class<?> clazz = (Class) MH_MemberName_getDeclaringClass.invoke(o);
+            return LOOKUP.findSpecial(clazz, mhi.getName(), mhi.getMethodType(), clazz);
+        }
+        catch (Throwable ex) {
+            throw new InterpreterError(ex);
+        }
+    }
+
+    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 {
+            Object memberName = MH_MemberName_init.invoke(owner, "<init>", type, (byte) H_INVOKESPECIAL);
+            Object memberNameFactory = MH_MemberName_getFactory.invoke();
+            Object resolved = MH_MemberNameFactory_resolveOrFail.invoke(memberNameFactory, (byte) H_INVOKESPECIAL, memberName, null, NoSuchMethodException.class);
+            Object init = MH_MemberName_asSpecial.invoke(resolved);
+            MethodType mtype = ((MethodType) MH_MemberName_getMethodType.invoke(init)).insertParameterTypes(0, owner);
+            Object lform = MH_DirectMethodHandle_prepareLambdaForm.invoke(init);
+            return (MethodHandle) MH_DirectMethodHandle_Special_new.invoke(mtype, lform, init);
+        }
+        catch (Throwable ex) {
+            throw new LinkageError("findSpecialConstructor", ex);
+        }
+    }
+
+    private static final MethodHandle MH_MemberName_getFactory;
+    private static final MethodHandle MH_MemberNameFactory_resolveOrFail;
+    private static final MethodHandle MH_MemberName_init;
+    private static final MethodHandle MH_MemberName_asSpecial;
+    private static final MethodHandle MH_MemberName_getMethodType;
+    private static final MethodHandle MH_DirectMethodHandle_prepareLambdaForm;
+    private static final MethodHandle MH_DirectMethodHandle_Special_new;
+
+    static {
+        try {
+            Class<?> memberNameClass = Class.forName("java.lang.invoke.MemberName");
+            Class<?> memberNameFactoryClass = Class.forName("java.lang.invoke.MemberName$Factory");
+            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");
+            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));
+            MH_MemberName_asSpecial = LOOKUP.findVirtual(memberNameClass, "asSpecial", MethodType.methodType(memberNameClass));
+            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));
+        }
+        catch (Throwable e) {
+            throw new AssertionError(e);
+        }
+    }
+}
--- a/interpreter/src/valhalla/interpreter/Interpreter.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/src/valhalla/interpreter/Interpreter.java	Thu Jun 09 11:04:48 2016 -0400
@@ -24,27 +24,20 @@
  */
 package valhalla.interpreter;
 
-import jdk.internal.org.objectweb.asm.ClassReader;
-import jdk.internal.org.objectweb.asm.Handle;
 import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.internal.org.objectweb.asm.Type;
-import jdk.internal.org.objectweb.asm.tree.*;
-import sun.misc.Unsafe;
 import valhalla.interpreter.OpcodeHandler.HandlerAction;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.lang.invoke.*;
 import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.Array;
-import java.lang.reflect.Field;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -52,6 +45,8 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.*;
 
+import static com.sun.tools.classfile.ConstantPool.*;
+import static java.lang.invoke.MethodHandleInfo.*;
 import static jdk.internal.org.objectweb.asm.Opcodes.*;
 import static valhalla.interpreter.OpcodeHandler.HandlerAction.*;
 
@@ -61,19 +56,16 @@
  * @author Brian Goetz
  * @author John Rose
  */
-public class Interpreter {
+public abstract class Interpreter {
     static boolean TRACING = false;
 
-    private final OpcodeHandler[] handlers = new OpcodeHandler[255];
     private final Class<?>[] primClasses = new Class<?>[16];
-    private final Map<String, Class> primToClass = new HashMap<>();
-    private final Map<Class, String> descrOf = new ConcurrentHashMap<>();
     private final List<File> classPath = new ArrayList<>();
-    private final ClassLoader classLoader;
+    final ClassLoader classLoader;
 
     // Mutable state
-    private final Map<String, ClassModel> classes = new ConcurrentHashMap<>();
-    private final Map<InvokeDynamicInsnNode, CallSite> indyCallSites = new ConcurrentHashMap<>();
+    private final Map<String, ClassModel> systemDictionary = new ConcurrentHashMap<>();
+    private final Map<Insn, CallSite> indyLinkageState = new ConcurrentHashMap<>();
     private Set<InterpreterEvent.Kind> eventFilter = EnumSet.noneOf(InterpreterEvent.Kind.class);
     private Consumer<InterpreterEvent> eventListener;
 
@@ -86,6 +78,10 @@
         classLoader = new InterpreterClassLoader(this, new URL[0], Interpreter.class.getClassLoader());
     }
 
+    public abstract Insn recognizeInstruction(byte[] bytes, int offset);
+
+    public abstract ClassModel newClassModel(String binaryName, byte[] bytes);
+
     public void setEventFilter(Set<InterpreterEvent.Kind> eventFilter) {
         this.eventFilter = eventFilter;
     }
@@ -103,285 +99,62 @@
         }
     }
 
-    // Opcode handlers
+    protected Object loadConstant(Frame f, int index) {
+        int tag = f.curClass().cpKind(index);
+        switch (tag) {
+            case CONSTANT_Integer:
+            case CONSTANT_Long:
+            case CONSTANT_Double:
+            case CONSTANT_Float:
+            case CONSTANT_String:
+                return f.curClass().cpSimpleConstant(index);
 
-    {
-        handlers[RETURN] = f -> HandlerAction.retVoid();
-        handlers[ARETURN] = f -> HandlerAction.ret(f.apop());
-        handlers[IRETURN] = f -> HandlerAction.ret(f.ipop());
-        handlers[LRETURN] = f -> HandlerAction.ret(f.lpop());
-        handlers[DRETURN] = f -> HandlerAction.ret(f.dpop());
-        handlers[FRETURN] = f -> HandlerAction.ret(f.fpop());
-        handlers[ATHROW] = f-> HandlerAction.exception((Throwable) f.apop());
-        handlers[NOP] = exec(f -> { });
-        handlers[ACONST_NULL] = exec(f -> f.apush(null));
-        handlers[ICONST_M1] = exec(f -> f.ipush(-1));
-        handlers[ICONST_0] = exec(f -> f.ipush(0));
-        handlers[ICONST_1] = exec(f -> f.ipush(1));
-        handlers[ICONST_2] = exec(f -> f.ipush(2));
-        handlers[ICONST_3] = exec(f -> f.ipush(3));
-        handlers[ICONST_4] = exec(f -> f.ipush(4));
-        handlers[ICONST_5] = exec(f -> f.ipush(5));
-        handlers[LCONST_1] = exec(f -> f.lpush(1L));
-        handlers[FCONST_0] = exec(f -> f.fpush(0.0f));
-        handlers[FCONST_1] = exec(f -> f.fpush(1.0f));
-        handlers[FCONST_2] = exec(f -> f.fpush(2.0f));
-        handlers[DCONST_0] = exec(f -> f.dpush(1.0d));
-        handlers[DCONST_1] = exec(f -> f.dpush(2.0d));
-        handlers[L2I] = f -> lpop(f, a -> exec(() -> f.ipush((int) a)));
-        handlers[L2F] = f -> lpop(f, a -> exec(() -> f.fpush((float) a)));
-        handlers[L2D] = f -> lpop(f, a -> exec(() -> f.dpush((double) a)));
-        handlers[F2I] = f -> fpop(f, a -> exec(() -> f.ipush((int) a)));
-        handlers[F2L] = f -> fpop(f, a -> exec(() -> f.lpush((long) a)));
-        handlers[F2D] = f -> fpop(f, a -> exec(() -> f.dpush((double) a)));
-        handlers[D2I] = f -> dpop(f, a -> exec(() -> f.ipush((int) a)));
-        handlers[D2L] = f -> dpop(f, a -> exec(() -> f.lpush((long) a)));
-        handlers[D2F] = f -> dpop(f, a -> exec(() -> f.fpush((float) a)));
-        handlers[I2F] = f -> ipop(f, a -> exec(() -> f.fpush((float) a)));
-        handlers[I2L] = f -> ipop(f, a -> exec(() -> f.lpush((long) a)));
-        handlers[I2D] = f -> ipop(f, a -> exec(() -> f.dpush((double) a)));
-        handlers[I2B] = f -> ipop(f, a -> exec(() -> f.ipush((byte) a)));
-        handlers[I2C] = f -> ipop(f, a -> exec(() -> f.ipush((char) a)));
-        handlers[I2S] = f -> ipop(f, a -> exec(() -> f.ipush((short) a)));
-        handlers[IALOAD] = f -> ipop(f, ix -> apop(f, (int[] arr) -> arc(arr, ix, () -> exec(() -> f.ipush(arr[ix])))));
-        handlers[LALOAD] = f -> ipop(f, ix -> apop(f, (long[] arr) -> arc(arr, ix, () -> exec(() -> f.lpush(arr[ix])))));
-        handlers[FALOAD] = f -> ipop(f, ix -> apop(f, (float[] arr) -> arc(arr, ix, () -> exec(() -> f.fpush(arr[ix])))));
-        handlers[DALOAD] = f -> ipop(f, ix -> apop(f, (double[] arr) -> arc(arr, ix, () -> exec(() -> f.dpush(arr[ix])))));
-        handlers[AALOAD] = f -> ipop(f, ix -> apop(f, (Object[] arr) -> arc(arr, ix, () -> exec(() -> f.apush(arr[ix])))));
-        handlers[BALOAD] = f -> ipop(f, ix -> apop(f, (byte[] arr) -> arc(arr, ix, () -> exec(() -> f.ipush(arr[ix])))));
-        handlers[CALOAD] = f -> ipop(f, ix -> apop(f, (char[] arr) -> arc(arr, ix, () -> exec(() -> f.ipush(arr[ix])))));
-        handlers[SALOAD] = f -> ipop(f, ix -> apop(f, (short[] arr) -> arc(arr, ix, () -> exec(() -> f.ipush(arr[ix])))));
-        handlers[IASTORE] = f -> ipop(f, v -> ipop(f, ix -> apop(f, (int[] arr) -> arc(arr, ix, () -> exec(() -> arr[ix] = v)))));
-        handlers[LASTORE] = f -> lpop(f, v -> ipop(f, ix -> apop(f, (long[] arr) -> arc(arr, ix, () -> exec(() -> arr[ix] = v)))));
-        handlers[FASTORE] = f -> fpop(f, v -> ipop(f, ix -> apop(f, (float[] arr) -> arc(arr, ix, () -> exec(() -> arr[ix] = v)))));
-        handlers[DASTORE] = f -> dpop(f, v -> ipop(f, ix -> apop(f, (double[] arr) -> arc(arr, ix, () -> exec(() -> arr[ix] = v)))));
-        handlers[AASTORE] = f -> apop(f, v -> ipop(f, ix -> apop(f, (Object[] arr) -> asc(arr, ix, v, () -> exec(() -> arr[ix] = v)))));
-        handlers[BASTORE] = f -> ipop(f, v -> ipop(f, ix -> apop(f, (byte[] arr) -> arc(arr, ix, () -> exec(() -> arr[ix] = (byte) v)))));
-        handlers[CASTORE] = f -> ipop(f, v -> ipop(f, ix -> apop(f, (char[] arr) -> arc(arr, ix, () -> exec(() -> arr[ix] = (char) v)))));
-        handlers[SASTORE] = f -> ipop(f, v -> ipop(f, ix -> apop(f, (short[] arr) -> arc(arr, ix, () -> exec(() -> arr[ix] = (short) v)))));
-        handlers[POP] = f -> exec(f::pop);
-        handlers[POP2] = f -> exec(f::pop2);
-        handlers[DUP] = f -> exec(f::dup);
-        handlers[DUP_X1] = f -> exec(f::dup_x1);
-        handlers[DUP_X2] = f -> exec(f::dup_x2);
-        handlers[DUP2] = f -> exec(f::dup2);
-        handlers[DUP2_X1] = f -> exec(f::dup2_x1);
-        handlers[DUP2_X2] = f -> exec(f::dup2_x2);
-        handlers[SWAP] = f -> exec(f::swap);
-        handlers[IADD] = f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a + b))));
-        handlers[LADD] = f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a + b))));
-        handlers[FADD] = f -> fpop(f, b -> fpop(f, a -> exec(() -> f.fpush(a + b))));
-        handlers[DADD] = f -> dpop(f, b -> dpop(f, a -> exec(() -> f.dpush(a + b))));
-        handlers[ISUB] = f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a - b))));
-        handlers[LSUB] = f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a - b))));
-        handlers[FSUB] = f -> fpop(f, b -> fpop(f, a -> exec(() -> f.fpush(a - b))));
-        handlers[DSUB] = f -> dpop(f, b -> dpop(f, a -> exec(() -> f.dpush(a - b))));
-        handlers[IMUL] = f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a * b))));
-        handlers[LMUL] = f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a * b))));
-        handlers[FMUL] = f -> fpop(f, b -> fpop(f, a -> exec(() -> f.fpush(a * b))));
-        handlers[DMUL] = f -> dpop(f, b -> dpop(f, a -> exec(() -> f.dpush(a * b))));
-        handlers[IREM] = f -> ipop(f, b -> ipop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.ipush(a % b))));
-        handlers[LREM] = f -> lpop(f, b -> lpop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.lpush(a % b))));
-        handlers[FREM] = f -> fpop(f, b -> fpop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.fpush(a % b))));
-        handlers[DREM] = f -> dpop(f, b -> dpop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.dpush(a % b))));
-        handlers[IDIV] = f -> ipop(f, b -> ipop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.ipush(a / b))));
-        handlers[LDIV] = f -> ipop(f, b -> lpop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.lpush(a / b))));
-        handlers[FDIV] = f -> fpop(f, b -> fpop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.fpush(a / b))));
-        handlers[DDIV] = f -> dpop(f, b -> dpop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.dpush(a / b))));
-        handlers[IAND] = f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a & b))));
-        handlers[LAND] = f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a & b))));
-        handlers[IOR] = f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a | b))));
-        handlers[LOR] = f -> lpop(f, b -> lpop(f, a -> exec(() -> f.dpush(a | b))));
-        handlers[IXOR] = f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a ^ b))));
-        handlers[LXOR] = f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a ^ b))));
-        handlers[INEG] = f -> ipop(f, a -> exec(() -> f.ipush(-a)));
-        handlers[LNEG] = f -> lpop(f, a -> exec(() -> f.lpush(-a)));
-        handlers[FNEG] = f -> fpop(f, a -> exec(() -> f.fpush(-a)));
-        handlers[DNEG] = f -> dpop(f, a -> exec(() -> f.dpush(-a)));
-        handlers[ISHL] = f -> ipop(f, n -> ipop(f, a -> exec(() -> f.ipush(a << n))));
-        handlers[LSHL] = f -> ipop(f, n -> lpop(f, a -> exec(() -> f.lpush(a << n))));
-        handlers[ISHR] = f -> ipop(f, n -> ipop(f, a -> exec(() -> f.ipush(a >> n))));
-        handlers[LSHR] = f -> ipop(f, n -> lpop(f, a -> exec(() -> f.lpush(a >> n))));
-        handlers[IUSHR] = f -> ipop(f, n -> ipop(f, a -> exec(() -> f.ipush(a >>> n))));
-        handlers[LUSHR] = f -> ipop(f, n -> lpop(f, a -> exec(() -> f.lpush(a >>> n))));
-        handlers[ARRAYLENGTH] = f -> apop(f, arr -> (arr == null) ? npe() : exec(() -> f.ipush(arraylength(arr))));
-        handlers[BIPUSH] = f -> exec(() -> f.ipush(((IntInsnNode) f.curInsn()).operand));
-        handlers[SIPUSH] = f -> exec(() -> f.ipush(((IntInsnNode) f.curInsn()).operand));
-        handlers[ILOAD] = f -> exec(() -> f.ipush(f.iload(f.<VarInsnNode>curInsn().var)));
-        handlers[LLOAD] = f -> exec(() -> f.lpush(f.lload(f.<VarInsnNode>curInsn().var)));
-        handlers[FLOAD] = f -> exec(() -> f.fpush(f.fload(f.<VarInsnNode>curInsn().var)));
-        handlers[DLOAD] = f -> exec(() -> f.dpush(f.dload(f.<VarInsnNode>curInsn().var)));
-        handlers[ALOAD] = f -> exec(() -> f.apush(f.aload(f.<VarInsnNode>curInsn().var)));
-        handlers[ISTORE] = f -> ipop(f, a -> exec(() -> f.istore(f.<VarInsnNode>curInsn().var, a)));
-        handlers[LSTORE] = f -> lpop(f, a -> exec(() -> f.lstore(f.<VarInsnNode>curInsn().var, a)));
-        handlers[FSTORE] = f -> fpop(f, a -> exec(() -> f.fstore(f.<VarInsnNode>curInsn().var, a)));
-        handlers[DSTORE] = f -> dpop(f, a -> exec(() -> f.dstore(f.<VarInsnNode>curInsn().var, a)));
-        handlers[ASTORE] = f -> apop(f, a -> exec(() -> f.astore(f.<VarInsnNode>curInsn().var, a)));
-        handlers[IINC] = f -> {
-            IincInsnNode node = f.curInsn();
-            return exec(() -> f.istore(node.var, f.iload(node.var) + node.incr));
-        };
+            case CONSTANT_Class:
+                return toClass(f, f.curClass().cpClass(index));
 
-        handlers[INVOKESTATIC] = this::invokeOp;
-        handlers[INVOKEINTERFACE] = this::invokeOp;
-        handlers[INVOKEVIRTUAL] = this::invokeOp;
-        handlers[INVOKESPECIAL] = this::invokeOp;
-        handlers[INVOKEDYNAMIC] = this::invokeDynamicOp;
-        handlers[GETFIELD] = this::fieldOp;
-        handlers[PUTFIELD] = this::fieldOp;
-        handlers[GETSTATIC] = this::fieldOp;
-        handlers[PUTSTATIC] = this::fieldOp;
+            case CONSTANT_MethodType:
+                return MethodType.fromMethodDescriptorString(f.curClass().cpMethodType(index), null);
 
-        handlers[LDC] = f -> {
-            Object cst = f.<LdcInsnNode>curInsn().cst;
-            if (cst instanceof Integer)
-                f.ipush((Integer) cst);
-            else if (cst instanceof Float)
-                f.fpush((Float) cst);
-            else if (cst instanceof Long)
-                f.lpush((Long) cst);
-            else if (cst instanceof Double)
-                f.dpush((Double) cst);
-            else
-                f.apush(asmToRuntime(f, cst));
-            return next();
-        };
+            case CONSTANT_MethodHandle:
+                try {
+                    ClassModel.MethodHandleDesc mh = f.curClass().cpMethodHandle(index);
+                    switch (mh.refKind) {
+                        case REF_invokeStatic:
+                            return f.getLookup()
+                                    .findStatic(toClass(f, mh.owner), mh.name,
+                                                MethodType.fromMethodDescriptorString(mh.desc, null));
+                        case REF_invokeVirtual:
+                        case REF_invokeInterface:
+                            return f.getLookup()
+                                    .findVirtual(toClass(f, mh.owner), mh.name,
+                                                 MethodType.fromMethodDescriptorString(mh.desc, null));
 
-        handlers[GOTO] = f -> branch(f.<JumpInsnNode>curInsn().label);
-        handlers[IFNULL] = f -> apop(f, a -> (a == null) ? branch(f.<JumpInsnNode>curInsn().label) : next());
-        handlers[IFNONNULL] = f -> apop(f, a -> (a != null) ? branch(f.<JumpInsnNode>curInsn().label) : next());
-        handlers[IF_ACMPEQ] = f -> apop(f, a -> apop(f, b -> (a == b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[IF_ACMPNE] = f -> apop(f, a -> apop(f, b -> (a != b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[IF_ICMPEQ] = f -> ipop(f, b -> ipop(f, a -> (a == b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[IF_ICMPNE] = f -> ipop(f, b -> ipop(f, a -> (a != b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[IF_ICMPLT] = f -> ipop(f, b -> ipop(f, a -> (a < b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[IF_ICMPGE] = f -> ipop(f, b -> ipop(f, a -> (a >= b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[IF_ICMPGT] = f -> ipop(f, b -> ipop(f, a -> (a > b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[IF_ICMPLE] = f -> ipop(f, b -> ipop(f, a -> (a <= b) ? branch(f.<JumpInsnNode>curInsn().label) : next()));
-        handlers[LCMP] = f -> lpop(f, b -> lpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1))));
-        handlers[FCMPL] = f -> fpop(f, b -> fpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1))));  // NaN
-        handlers[FCMPG] = f -> fpop(f, b -> fpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1))));  // NaN
-        handlers[DCMPL] = f -> dpop(f, b -> dpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1))));  // NaN
-        handlers[DCMPG] = f -> dpop(f, b -> dpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1))));  // NaN
-        handlers[IFEQ] = f -> ipop(f, a -> (a == 0) ? branch(f.<JumpInsnNode>curInsn().label) : next());
-        handlers[IFNE] = f -> ipop(f, a -> (a != 0) ? branch(f.<JumpInsnNode>curInsn().label) : next());
-        handlers[IFLT] = f -> ipop(f, a -> (a < 0) ? branch(f.<JumpInsnNode>curInsn().label) : next());
-        handlers[IFGE] = f -> ipop(f, a -> (a >= 0) ? branch(f.<JumpInsnNode>curInsn().label) : next());
-        handlers[IFGT] = f -> ipop(f, a -> (a > 0) ? branch(f.<JumpInsnNode>curInsn().label) : next());
-        handlers[IFLE] = f -> ipop(f, a -> (a <= 0) ? branch(f.<JumpInsnNode>curInsn().label) : next());
+                        case REF_invokeSpecial:
+                            return f.getLookup()
+                                    .findSpecial(toClass(f, mh.owner), mh.name,
+                                                 MethodType.fromMethodDescriptorString(mh.desc, null),
+                                                 f.curClass().getRepresentationClass());
+                        default:
+                            throw new InterpreterError("Unknown refkind: " + mh.refKind);
+                    }
+                }
+                catch (NoSuchMethodException | IllegalAccessException e) {
+                    throw new InterpreterError(f, e);
+                }
 
-        handlers[MONITORENTER] = f -> apop(f, a -> (a == null) ? npe() : exec(() -> Internal.UNSAFE.monitorEnter(a)));
-        handlers[MONITOREXIT]  = f -> apop(f, a -> (a == null) ? npe() : exec(() -> Internal.UNSAFE.monitorExit(a)));
-
-        handlers[NEW] = f -> {
-            String desc = f.<TypeInsnNode>curInsn().desc;
-            try {
-                f.apush(Internal.UNSAFE.allocateInstance(toClass(f, desc)));
-            }
-            catch (InstantiationException e) {
-                throw new InterpreterError(f, "NEW " + desc, e);
-            }
-            return next();
-        };
-        handlers[INSTANCEOF] = f -> apop(f, a -> exec(() -> f.ipush((a != null && toClass(f, f.<TypeInsnNode>curInsn().desc).isAssignableFrom(a.getClass())) ? 1 : 0)));
-        handlers[CHECKCAST] = f -> apop(f, a -> {
-            Class<?> clazz = toClass(f, f.<TypeInsnNode>curInsn().desc);
-            return (a == null || clazz.isAssignableFrom(a.getClass()))
-                   ? exec(() -> f.apush(a))
-                   : dothrow(() -> clazz.cast(a), ClassCastException.class);
-        });
-
-        handlers[NEWARRAY] = f -> newarray(f, toPrimClass(f.<IntInsnNode>curInsn().operand), f.ipop());
-        handlers[ANEWARRAY] = f -> newarray(f, toClass(f, f.<TypeInsnNode>curInsn().desc), f.ipop());
-        handlers[MULTIANEWARRAY] = f -> {
-            MultiANewArrayInsnNode insn = f.<MultiANewArrayInsnNode>curInsn();
-            int[] lens = new int[insn.dims];
-            Class<?> clazz = toClass(f, insn.desc);
-            for (int i = lens.length; --i >= 0; ) {
-                lens[i] = f.ipop();
-                clazz = Objects.requireNonNull(clazz.getComponentType());
-            }
-            return newarray(f, clazz, lens);
-        };
-        handlers[TABLESWITCH] = f -> branchIndexed(f.<TableSwitchInsnNode>curInsn(), f.ipop());
-        handlers[LOOKUPSWITCH] = f -> branchIndexed(f.<LookupSwitchInsnNode>curInsn(), f.ipop());
-
-        primMapping(T_BOOLEAN, boolean.class, "Z");
-        primMapping(T_CHAR, char.class, "C");
-        primMapping(T_FLOAT, float.class, "F");
-        primMapping(T_DOUBLE, double.class, "D");
-        primMapping(T_BYTE, byte.class, "B");
-        primMapping(T_SHORT, short.class, "S");
-        primMapping(T_INT, int.class, "I");
-        primMapping(T_LONG, long.class, "J");
-    }
-
-    private void primMapping(int index, Class clazz, String descr) {
-        primClasses[index] = clazz;
-        primToClass.put(descr, clazz);
-        descrOf.put(clazz, descr);
-    }
-
-    private void logEvent(InterpreterEvent e) {
-        if (eventListener != null)
-            eventListener.accept(e);
-        if (eventFilter.contains(e.kind))
-            log.add(e);
-    }
-
-    private Class<?> toClass(Frame f, String name) throws InterpreterError {
-        // FIXME: class name must be access-checked relative to current frame class
-        // ...doing it neatly requires a new API point Lookup.findClass
-        try {
-            return Class.forName(name.replace('/', '.'), false, f.getLookup().lookupClass().getClassLoader());
-        }
-        catch (ClassNotFoundException e) {
-            // FIXME: emulate linkage error instead of throwing interpreter exception
-            throw new InterpreterError(f, e);
+            default:
+                throw new IllegalArgumentException("Unknown constant tag: " + tag);
         }
     }
 
-    private Class<?> toPrimClass(int basicType) throws InterpreterError {
+    protected Class<?> toPrimClass(int basicType) throws InterpreterError {
         return Objects.requireNonNull(primClasses[basicType]);
     }
 
-    private Object asmToRuntime(Frame f, Object o) {
-        try {
-            if (o instanceof Type) {
-                Type t = (Type) o;
-                return (t.getSort() == Type.METHOD) ? MethodType.fromMethodDescriptorString(t.getDescriptor(), null)
-                    : toClass(f, t.getClassName());
-            }
-            else if (o instanceof Handle) {
-                Handle handle = (Handle) o;
-                return f.getLookup()
-                        .findStatic(toClass(f, handle.getOwner()), handle.getName(),
-                                MethodType.fromMethodDescriptorString(handle.getDesc(), null));
-            }
-            else
-                return o;
-        }
-        catch (NoSuchMethodException | IllegalAccessException e) {
-            throw new InterpreterError(f, e);
-        }
-    }
-
-    private HandlerAction fieldOp(Frame f) throws InterpreterError {
-        FieldInsnNode n = f.curInsn();
-        Class type = MethodType.fromMethodDescriptorString("()" + n.desc, null).returnType();
-        return memberOp(f, toClass(f, n.owner), n.name, type);
-    }
-
-    private HandlerAction invokeOp(Frame f) throws InterpreterError {
-        MethodInsnNode n = f.curInsn();
-        MethodType methodType = MethodType.fromMethodDescriptorString(n.desc, null);
-        return memberOp(f, toClass(f, n.owner), n.name, methodType);
-    }
-
     private HandlerAction memberOp(Frame f, Class<?> owner, String name, Object type) throws InterpreterError {
         MethodHandle resolved;
         try {
-            resolved = linkMemberAccess(f.getLookup(), f.curInsn().getOpcode(), owner, name, type);
+            resolved = linkMemberAccess(f.getLookup(), f.curOpcode(), owner, name, type);
         } catch (LinkageError ex) {
             // FIXME: record this linkage error in the constant pool, to rethrow if needed
             return exception(ex);
@@ -389,20 +162,10 @@
         return finishInvokeOp(f, resolved);
     }
 
-    private HandlerAction invokeDynamicOp(Frame f) throws InterpreterError {
-        InvokeDynamicInsnNode n = f.curInsn();
-        CallSite cs = linkCallSite(f, n);
-        MethodType type = cs.getTarget().type();
-        assert(type.equals(MethodType.fromMethodDescriptorString(n.desc, null)));
-        if (cs instanceof FailedCallSite)
-            return exception(((FailedCallSite) cs).exception);
-        return finishInvokeOp(f, cs.getTarget().asFixedArity());
-    }
-
     private HandlerAction finishInvokeOp(Frame f, MethodHandle resolved) {
         MethodType type = resolved.type();
         Object[] args = f.popArgs(type.toMethodDescriptorString());
-        if (opcodeDoesNullCheck(f.curInsn().getOpcode()) && args[0] == null)
+        if (opcodeDoesNullCheck(f.curOpcode()) && args[0] == null)
             return npe();
         f.expectResult(Type.getType(type.toMethodDescriptorString()).getReturnType().getDescriptor());
         HandlerAction action = interpretOrExecute(f, resolved, args);
@@ -420,33 +183,6 @@
         }
     }
 
-    private CallSite linkCallSite(Frame f, InvokeDynamicInsnNode n) throws InterpreterError {
-        CallSite cs = indyCallSites.get(n);
-        if (cs == null) {
-            MethodType methodType = MethodType.fromMethodDescriptorString(n.desc, null);
-            MethodHandle bsm = (MethodHandle) asmToRuntime(f, n.bsm);
-            Object[] args = new Object[n.bsmArgs.length + 3];
-            for (int i=0; i<n.bsmArgs.length; i++)
-                args[i+3] = asmToRuntime(f, n.bsmArgs[i]);
-            args[0] = f.getLookup();
-            args[1] = n.name;
-            args[2] = methodType;
-            try {
-                cs = (CallSite) interpretOrExecute(f, bsm, args).getReturnValueOrThrow();
-                if (!cs.getTarget().type().equals(methodType))
-                    throw new BootstrapMethodError("wrong type of call site: "+cs.getTarget().type());
-            } catch (InterpreterError ex) {
-                throw ex;
-            } catch (Throwable ex) {
-                cs = new FailedCallSite(methodType, ex);
-                assert(cs.getTarget().type().equals(methodType));
-            }
-            CallSite oldcs = indyCallSites.putIfAbsent(n, cs);
-            if (oldcs != null)  cs = oldcs;
-        }
-        return cs;
-    }
-
     private static class FailedCallSite extends ConstantCallSite {
         private final Throwable exception;
         FailedCallSite(MethodType type, Throwable ex) {
@@ -469,7 +205,7 @@
 
         String debugInfo = "";
         try {
-            mhi = Internal.crackMethodHandle(resolved);  // privileged cracking to get bytecodes
+            mhi = InternalHelpers.crackMethodHandle(resolved);  // privileged cracking to get bytecodes
             switch (mhi.getReferenceKind()) {
                 case H_INVOKESTATIC:
                 case H_INVOKESPECIAL:
@@ -497,12 +233,16 @@
         MethodHandle target = (selected != null) ? selected : resolved;
 
         if (interpretable) {
-            ClassModel cm = classes.get(mhi.getDeclaringClass().getName());
+            ClassModel cm = systemDictionary.get(mhi.getDeclaringClass().getName());
             if (cm != null) {
                 logEvent(InterpreterEvent.interpret(mhi.getDeclaringClass().getName(),
                                                     mhi.getName(),
                                                     mhi.getMethodType().toMethodDescriptorString()));
-                Frame frame = makeNewFrame(caller, cm.cn, mhi);
+                Frame frame = makeNewFrame(caller, cm,
+                                           InternalHelpers.makeFullPowerLookup(mhi.getDeclaringClass()),
+                                           mhi.getReferenceKind() == REF_invokeStatic,
+                                           mhi.getName(),
+                                           mhi.getMethodType().toMethodDescriptorString());
                 frame.storeArgs(target.type().toMethodDescriptorString(), args);
                 return interpret(frame);
             }
@@ -520,15 +260,15 @@
         return retOrException(() -> target.asSpreader(Object[].class, args.length).invoke(args));
     }
 
-    private Frame makeNewFrame(Frame caller, ClassNode cn, Lookup lookup, boolean isStatic, String methodName, String methodDesc) {
-        Frame f = cn.methods.stream()
-                            .filter(m -> m.name.equals(methodName)
-                                         && m.desc.equals(methodDesc)
-                                         && ((isStatic && (m.access & Opcodes.ACC_STATIC) != 0)
-                                             || (!isStatic && (m.access & Opcodes.ACC_STATIC) == 0)))
-                            .findFirst()
-                            .map(mn -> new Frame(this, cn, mn))
-                            .orElse(null);
+    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)
+                                 && ((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);
@@ -536,20 +276,100 @@
         return f;
     }
 
-    private Frame makeNewFrame(Frame caller, ClassNode cn, MethodHandleInfo mhi) {
-        return makeNewFrame(caller, cn,
-                            Internal.makeFullPowerLookup(mhi.getDeclaringClass()),
-                            mhi.getReferenceKind() == MethodHandleInfo.REF_invokeStatic,
-                            mhi.getName(),
-                            mhi.getMethodType().toMethodDescriptorString());
+    // Helper methods for opcode handlers
+
+    protected HandlerAction fieldOp(Frame f) throws InterpreterError {
+        ClassModel.MemberDesc ref = f.readMemberRef(1);
+        Class type = MethodType.fromMethodDescriptorString("()" + ref.desc, null).returnType();
+        return memberOp(f, toClass(f, ref.owner), ref.name, type);
     }
 
-    // Helper methods for opcode handlers
+    protected HandlerAction invokeOp(Frame f) throws InterpreterError {
+        ClassModel.MemberDesc ref = f.readMemberRef(1);
+        MethodType methodType = MethodType.fromMethodDescriptorString(ref.desc, null);
+        return memberOp(f, toClass(f, ref.owner), ref.name, methodType);
+    }
 
-    private HandlerAction newarray(Frame f, Class<?> elementClass, int... lens) {
+    protected HandlerAction invokeDynamicOp(Frame f) throws InterpreterError {
+        Insn insn = f.curInsn();
+        CallSite cs = linkIndySite(f, insn);
+        assert(cs.getTarget().type().equals(MethodType.fromMethodDescriptorString(f.curClass().cpIndy(f.read16u(1)).invokeDesc, null)));
+        if (cs instanceof FailedCallSite)
+            return exception(((FailedCallSite) cs).exception);
+        return finishInvokeOp(f, cs.getTarget().asFixedArity());
+    }
+
+    private CallSite linkIndySite(Frame f, Insn insn) throws InterpreterError {
+        CallSite cs = indyLinkageState.get(insn);
+        int index = f.read16u(1);
+
+        if (cs == null) {
+            ClassModel.IndyDesc indy = f.curClass().cpIndy(index);
+            MethodType methodType = MethodType.fromMethodDescriptorString(indy.invokeDesc, null);
+            MethodHandle bsm = (MethodHandle) loadConstant(f, indy.bootstrapCPIndex);
+            int[] bsmArgs = indy.bootstrapArgCPIndexes;
+            Object[] args = new Object[bsmArgs.length + 3];
+            for (int i=0; i<bsmArgs.length; i++)
+                args[i+3] = loadConstant(f, bsmArgs[i]);
+            args[0] = f.getLookup();
+            args[1] = indy.invokeName;
+            args[2] = methodType;
+            try {
+                cs = (CallSite) interpretOrExecute(f, bsm, args).getReturnValueOrThrow();
+                if (!cs.getTarget().type().equals(methodType))
+                    throw new BootstrapMethodError("wrong type of call site: "+cs.getTarget().type());
+            } catch (InterpreterError ex) {
+                throw ex;
+            } catch (Throwable ex) {
+                cs = new FailedCallSite(methodType, ex);
+                assert(cs.getTarget().type().equals(methodType));
+            }
+            CallSite oldcs = indyLinkageState.putIfAbsent(insn, cs);
+            if (oldcs != null)
+                cs = oldcs;
+        }
+        return cs;
+    }
+
+    protected HandlerAction loadConstantOp(Frame f, int index) {
+        Object constant = loadConstant(f, index);
+        if (constant instanceof Integer)
+            f.ipush((Integer) constant);
+        else if (constant instanceof Long)
+            f.lpush((Long) constant);
+        else if (constant instanceof Double)
+            f.dpush((Double) constant);
+        else if (constant instanceof Float)
+            f.fpush((Float) constant);
+        else
+            f.apush(constant);
+        return next();
+    }
+
+    protected Class<?> toClass(Frame f, String name) throws InterpreterError {
+        // FIXME: class name must be access-checked relative to current frame class
+        // ...doing it neatly requires a new API point Lookup.findClass
+        try {
+            return Class.forName(name.replace('/', '.'), false, f.getLookup().lookupClass().getClassLoader());
+        }
+        catch (ClassNotFoundException e) {
+            // FIXME: emulate linkage error instead of throwing interpreter exception
+            throw new InterpreterError(f, e);
+        }
+    }
+
+    protected void logEvent(InterpreterEvent e) {
+        if (eventListener != null)
+            eventListener.accept(e);
+        if (eventFilter.contains(e.kind))
+            log.add(e);
+    }
+
+    protected HandlerAction newarray(Frame f, Class<?> elementClass, int... lens) {
         return exec(() -> f.apush(java.lang.reflect.Array.newInstance(elementClass, lens)));
     }
-    private int arraylength(Object arr) throws InterpreterError {
+
+    protected int arraylength(Object arr) throws InterpreterError {
         try {
             return Array.getLength(arr);
         } catch (IllegalArgumentException ex) {
@@ -557,27 +377,16 @@
         }
     }
 
-    private HandlerAction branchIndexed(TableSwitchInsnNode n, int i) {
-        assert(n.min + n.labels.size() == n.max+1);
-        return branchIndexed(n.labels, n.dflt, i - n.min);
-    }
-    private HandlerAction branchIndexed(LookupSwitchInsnNode n, int i) {
-        return branchIndexed(n.labels, n.dflt, n.keys.indexOf(i));
-    }
-    private HandlerAction branchIndexed(List<LabelNode> labels, LabelNode dflt, int i) {
-        return branch((i >= 0 && i < labels.size()) ? labels.get(i) : dflt);
-    }
+    protected interface FloatFunction<T> { T apply(float f); }
 
-    private interface FloatFunction<T> { T apply(float f); }
+    protected HandlerAction ipop(Frame f, IntFunction<HandlerAction> fn) { return fn.apply(f.ipop()); }
+    protected HandlerAction fpop(Frame f, FloatFunction<HandlerAction> fn) { return fn.apply(f.fpop()); }
+    protected HandlerAction lpop(Frame f, LongFunction<HandlerAction> fn) { return fn.apply(f.lpop()); }
+    protected HandlerAction dpop(Frame f, DoubleFunction<HandlerAction> fn) { return fn.apply(f.dpop()); }
+    @SuppressWarnings("unchecked")
+    protected<T> HandlerAction apop(Frame f, Function<T, HandlerAction> fn) { return fn.apply((T) f.apop()); }
 
-    private HandlerAction ipop(Frame f, IntFunction<HandlerAction> fn) { return fn.apply(f.ipop()); }
-    private HandlerAction fpop(Frame f, FloatFunction<HandlerAction> fn) { return fn.apply(f.fpop()); }
-    private HandlerAction lpop(Frame f, LongFunction<HandlerAction> fn) { return fn.apply(f.lpop()); }
-    private HandlerAction dpop(Frame f, DoubleFunction<HandlerAction> fn) { return fn.apply(f.dpop()); }
-    @SuppressWarnings("unchecked")
-    private <T> HandlerAction apop(Frame f, Function<T, HandlerAction> fn) { return fn.apply((T) f.apop()); }
-
-    public HandlerAction exec(Runnable r) {
+    protected HandlerAction exec(Runnable r) {
         try {
             r.run();
         } catch (RuntimeException ex) {
@@ -585,27 +394,34 @@
         }
         return HandlerAction.NEXT;
     }
-    private HandlerAction npe() {
+
+    protected HandlerAction npe() {
         Object[][] nulla = {null};
         return dothrow(() -> nulla[0][0], NullPointerException.class);
     }
-    private HandlerAction aex(RunnableWithResults rawThrow) {
+
+    protected HandlerAction aex(RunnableWithResults rawThrow) {
         return dothrow(rawThrow, ArithmeticException.class);
     }
-    private HandlerAction arc(Object arr, int ix, Supplier<HandlerAction> fn) {
+
+    protected HandlerAction rangeCheck(Object arr, int ix, Supplier<HandlerAction> fn) {
         if (arr == null)
             return npe();
-        if (ix < 0 || ix >= arraylength(arr))
+        else if (ix < 0 || ix >= arraylength(arr))
             return dothrow(() -> Array.get(arr, ix), ArrayIndexOutOfBoundsException.class);
-        return fn.get();
+        else
+            return fn.get();
     }
-    private HandlerAction asc(Object[] arr, int ix, Object v, Supplier<HandlerAction> fn) {
+
+    protected HandlerAction storeCheck(Object[] arr, int ix, Object v, Supplier<HandlerAction> fn) {
         if (v != null && arr != null && arr.length > 0 &&
             !arr.getClass().getComponentType().isInstance(v))
             return dothrow(() -> arr[0] = v, ArrayStoreException.class);
-        return arc(arr, ix, fn);
+        else
+            return rangeCheck(arr, ix, fn);
     }
-    private HandlerAction dothrow(RunnableWithResults rawThrow, Class<? extends RuntimeException> exc) {
+
+    protected HandlerAction dothrow(RunnableWithResults rawThrow, Class<? extends RuntimeException> exc) {
         HandlerAction action = retOrException(rawThrow);
         if (action.actionKind != OpcodeHandler.ActionKind.THROW)
             throw new InterpreterError("expected a throw");
@@ -614,7 +430,7 @@
         return action;
     }
 
-    public OpcodeHandler exec(Consumer<Frame> c) {
+    protected OpcodeHandler exec(Consumer<Frame> c) {
         return f -> { c.accept(f); return HandlerAction.NEXT; };
     }
 
@@ -665,11 +481,11 @@
 
             case INVOKESPECIAL:
                 if (name.equals("<init>"))
-                    return Internal.findSpecialConstructor(owner, (MethodType) type);
+                    return InternalHelpers.findSpecialConstructor(owner, (MethodType) type);
                 break;
         }
         try {
-            return Internal.linkMethodHandleConstant(lookup.lookupClass(), opcodeToRefKind(opcode), owner, name, type);
+            return InternalHelpers.linkMethodHandleConstant(lookup.lookupClass(), opcodeToRefKind(opcode), owner, name, type);
         } catch (Error ex) {
             throw ex;
         } catch (Throwable ex) {
@@ -678,10 +494,10 @@
     }
 
     MethodHandle doMethodSelection(MethodHandle mh, Class dynamicReceiver) throws InterpreterError {
-        MethodHandleInfo mhi = Internal.crackMethodHandle(mh);
-        MethodHandle res = doMethodSelection(mhi, dynamicReceiver);
+        MethodHandle res = doMethodSelection(InternalHelpers.crackMethodHandle(mh), dynamicReceiver);
         return (res != null) ? res : mh;
     }
+
     private MethodHandle doMethodSelection(MethodHandleInfo mhi, Class dynamicReceiver) throws InterpreterError {
         switch (mhi.getReferenceKind()) {
             case H_INVOKEVIRTUAL:
@@ -692,27 +508,28 @@
         }
         if (dynamicReceiver.getName().contains("/"))
             return null;
-        return Internal.convertVirtualToSpecial(mhi, dynamicReceiver);
+        return InternalHelpers.convertVirtualToSpecial(mhi, dynamicReceiver);
     }
 
     private HandlerAction interpret(Frame f) throws InterpreterError {
         try {
             while (true) {
-                int opcode = f.curInsn().getOpcode();
+                Insn insn = f.curInsn();
+                int opcode = insn.getOpcode();
+                OpcodeHandler handler = insn.getHandler();
+                if (handler == null)
+                    throw new InterpreterError(f, "No handler for opcode " + insn);
                 if (TRACING)
-                    System.out.println(f.currentPC()+" : "+f.curInsn());
-                OpcodeHandler h = handlers[opcode];
-                if (h == null)
-                    throw new InterpreterError(f, "Unknown opcode " + opcode);
-                HandlerAction a = h.handle(f);
+                    System.out.printf("bci=%d: %s%n", f.curBCI(), f.curInsn());
+
+                HandlerAction a = handler.handle(f);
                 switch (a.actionKind) {
                     case NEXT:
                         f.advancePC();
                         continue;
 
                     case BRANCH:
-                        f.setNextPC(a.branchTarget);
-                        f.advancePC();
+                        f.branchRelative(a.branchOffset);
                         continue;
 
                     case RETURN:
@@ -720,14 +537,10 @@
 
                     case THROW:
                         // first see if there is a local exception handler
-                        TryCatchBlockNode tcb = f.currentExceptionHandlers()
-                                                 .filter(tcb1 -> toClass(f, tcb1.type).isInstance(a.returnValue))
-                                                 .findFirst()
-                                                 .orElse(null);
-                        if (tcb != null) {
+                        int offset = f.curMethod().findExceptionHandler(f.curBCI(), clazzName -> toClass(f, clazzName).isInstance(a.returnValue));
+                        if (offset != 0) {
                             f.pushException((Throwable) a.returnValue);
-                            f.setNextPC(tcb.handler);
-                            f.advancePC();
+                            f.branchRelative(offset);
                             continue;
                         }
                         // else throw out to caller frame
@@ -740,159 +553,45 @@
         }
     }
 
-    // This is the part the reaches into the internal parts of the JVM.
-    static class Internal {
-        private Internal() {} // all static
-
-        private static final Lookup LOOKUP;
-        private static final Unsafe UNSAFE;
-        static {
-            try {
-                Field theLookup = Lookup.class.getDeclaredField("IMPL_LOOKUP");
-                theLookup.setAccessible(true);
-                LOOKUP = (Lookup) theLookup.get(null);
-                UNSAFE = (Unsafe) (Object) LOOKUP.findStaticGetter(Unsafe.class, "theUnsafe", Unsafe.class).invoke();
-            }
-            catch (Throwable e) {
-                throw new AssertionError(e);
-            }
-        }
-
-        public static Class<?> loadClass(Interpreter interpreter, String name, byte[] bytes) {
-            return UNSAFE.defineClass(name, bytes, 0, bytes.length, interpreter.classLoader, null);
-        }
-
-        public static MethodHandleInfo crackMethodHandle(MethodHandle mh) {
-            return LOOKUP.revealDirect(mh);
-        }
-
-        private static Lookup makeFullPowerLookup(Class<?> clazz) {
-            return LOOKUP.in(clazz);
-        }
-
-        private static MethodHandle linkMethodHandleConstant(Class<?> lookupClass, int refKind, Class owner, String name, Object type) throws Throwable {
-            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
-        private static MethodHandle convertVirtualToSpecial(MethodHandleInfo mhi, Class dynamicReceiver) {
-            try {
-                Object o = MH_Lookup_resolveOrFail.invoke(LOOKUP, (byte) mhi.getReferenceKind(), dynamicReceiver, mhi.getName(), mhi.getMethodType());
-                Class<?> clazz = (Class) MH_MemberName_getDeclaringClass.invoke(o);
-                return LOOKUP.findSpecial(clazz, mhi.getName(), mhi.getMethodType(), clazz);
-            }
-            catch (Throwable ex) {
-                throw new InterpreterError(ex);
-            }
-        }
-        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(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
-        private static MethodHandle findSpecialConstructor(Class owner, MethodType type) {
-            try {
-                Object memberName = MH_MemberName_init.invoke(owner, "<init>", type, (byte) H_INVOKESPECIAL);
-                Object memberNameFactory = MH_MemberName_getFactory.invoke();
-                Object resolved = MH_MemberNameFactory_resolveOrFail.invoke(memberNameFactory, (byte) H_INVOKESPECIAL, memberName, null, NoSuchMethodException.class);
-                Object init = MH_MemberName_asSpecial.invoke(resolved);
-                MethodType mtype = ((MethodType) MH_MemberName_getMethodType.invoke(init)).insertParameterTypes(0, owner);
-                Object lform = MH_DirectMethodHandle_prepareLambdaForm.invoke(init);
-                return (MethodHandle) MH_DirectMethodHandle_Special_new.invoke(mtype, lform, init);
-            } catch (Throwable ex) {
-                throw new LinkageError("findSpecialConstructor", ex);
-            }
-        }
-        private static final MethodHandle MH_MemberName_getFactory;
-        private static final MethodHandle MH_MemberNameFactory_resolveOrFail;
-        private static final MethodHandle MH_MemberName_init;
-        private static final MethodHandle MH_MemberName_asSpecial;
-        private static final MethodHandle MH_MemberName_getMethodType;
-        private static final MethodHandle MH_DirectMethodHandle_prepareLambdaForm;
-        private static final MethodHandle MH_DirectMethodHandle_Special_new;
-        static {
-            try {
-                Class<?> memberNameClass = Class.forName("java.lang.invoke.MemberName");
-                Class<?> memberNameFactoryClass = Class.forName("java.lang.invoke.MemberName$Factory");
-                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");
-                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));
-                MH_MemberName_asSpecial = LOOKUP.findVirtual(memberNameClass, "asSpecial", MethodType.methodType(memberNameClass));
-                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));
-            } catch (Throwable e) {
-                throw new AssertionError(e);
-            }
-        }
-    }
-
     // Public interpreter API
 
-    public ClassModel resolveClass(String className) {
-        ClassModel model = classes.get(className.replace('/', '.'));
+    public ClassModel resolveClass(String binaryName) {
+        String humanName = binaryName.replace('/', '.');
+        ClassModel model = systemDictionary.get(humanName);
         if (model != null)
             return model;
 
         try {
             for (File dir : classPath) {
-                File f = new File(dir, className + ".class");
+                File f = new File(dir, binaryName + ".class");
                 if (f.exists()) {
                     FileInputStream fis = new FileInputStream(f);
                     byte[] bytes = new byte[(int) f.length()];
                     if (fis.read(bytes) != bytes.length)
                         throw new IOException("Cannot read file " + f + " fully");
 
-                    ClassNode cn = new ClassNode();
-                    new ClassReader(bytes).accept(cn, 0);
-                    String name = cn.name.replace('/', '.');
-                    ClassModel classModel = new ClassModel(this, cn.name, cn);
-                    classes.put(name, classModel);
-                    logEvent(InterpreterEvent.load(name));
+                    ClassModel classModel = newClassModel(binaryName, bytes);
+                    systemDictionary.put(humanName, classModel);
+                    logEvent(InterpreterEvent.load(humanName));
                     // Run <clinit> by forced interpretation
-                    cn.methods.stream()
-                              .filter(m -> m.name.equals("<clinit>") && m.desc.equals("()V"))
+                    classModel.methods()
+                              .filter(m -> m.getName().equals("<clinit>") && m.getDesc().equals("()V"))
                               .findFirst()
-                              .ifPresent(m -> forceInterpret(INVOKESTATIC, className, "<clinit>", "()V") );
+                              .ifPresent(m -> forceInterpret(INVOKESTATIC, binaryName, "<clinit>", "()V") );
 
                     return classModel;
                 }
             }
-            throw new InterpreterError("Cannot resolve class " + className);
+            throw new InterpreterError("Cannot resolve class " + binaryName);
         }
         catch (IOException e) {
-            throw new InterpreterError("Cannot resolve class " + className, e);
+            throw new InterpreterError("Cannot resolve class " + binaryName, e);
         }
     }
 
-    public String addBootclass(Class c) throws IOException {
-        ClassNode cn = new ClassNode();
-        InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(c.getName().replace('.', '/') + ".class");
-        new ClassReader(stream).accept(cn, 0);
-        String name = cn.name.replace('/', '.');
-        classes.put(name, new ClassModel(cn.name, cn, c));
+    public String addBootclass(Class c) {
+        String name = c.getName();
+        systemDictionary.put(name, StandardClassModel.interpreted(this, c));
         logEvent(InterpreterEvent.load(name));
         return name;
     }
@@ -902,18 +601,18 @@
         if (cm == null)
             throw new InterpreterError(new ClassNotFoundException(ownerClass));
 
-        Lookup lookup = Internal.makeFullPowerLookup(cm.getRepresentationClass());
+        Lookup lookup = InternalHelpers.makeFullPowerLookup(cm.getRepresentationClass());
         MethodHandle method = linkMemberAccess(lookup, opcode, cm.getRepresentationClass(), name,
-                MethodType.fromMethodDescriptorString(desc, null));
+                                               MethodType.fromMethodDescriptorString(desc, null));
         return interpretOrExecute(new Frame(lookup), method, args).getReturnValueOrThrow();
     }
 
     public Object forceInterpret(int opcode, String ownerClass, String name, String desc, Object... args) {
         ClassModel cm = resolveClass(ownerClass);
         logEvent(InterpreterEvent.interpret(ownerClass, name, desc));
-        Lookup lookup = Internal.makeFullPowerLookup(cm.getRepresentationClass());
+        Lookup lookup = InternalHelpers.makeFullPowerLookup(cm.getRepresentationClass());
         boolean isStatic = (opcode == INVOKESTATIC);
-        Frame frame = makeNewFrame(new Frame(lookup), cm.cn, lookup, isStatic, name, desc);
+        Frame frame = makeNewFrame(new Frame(lookup), cm, lookup, isStatic, name, desc);
         if (frame == null)
             throw new InterpreterError(String.format("Cannot find %s.%s%s/%s", ownerClass, name, desc, isStatic ? "static" : "virtual"));
         frame.storeArgs(desc, args);
@@ -925,10 +624,18 @@
     }
 
     public static Object callback(Class fromClass, int opcode, String clazz, String method, String desc, Object[] args) throws Throwable {
-        return ((InterpreterClassLoader) fromClass.getClassLoader()).interpreter.callback(opcode, clazz, method, desc, args);
+        Interpreter interpreter = ((InterpreterClassLoader) fromClass.getClassLoader()).interpreter;
+        return interpreter.invoke(opcode, clazz, method, desc, args);
     }
 
-    private Object callback(int opcode, String clazz, String method, String desc, Object[] args) throws Throwable {
-        return invoke(opcode, clazz, method, desc, args);
+    {
+        primClasses[T_BOOLEAN] = boolean.class;
+        primClasses[T_CHAR] = char.class;
+        primClasses[T_FLOAT] = float.class;
+        primClasses[T_DOUBLE] = double.class;
+        primClasses[T_BYTE] = byte.class;
+        primClasses[T_SHORT] = short.class;
+        primClasses[T_INT] = int.class;
+        primClasses[T_LONG] = long.class;
     }
 }
--- a/interpreter/src/valhalla/interpreter/InterpreterError.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/src/valhalla/interpreter/InterpreterError.java	Thu Jun 09 11:04:48 2016 -0400
@@ -65,11 +65,15 @@
     }
 
     private void unpackFrame(Frame f) {
-        if (f == null || f.currentClass() == null)  return;
-        className = f.currentClass().name;
-        methodName = f.currentMethod().name;
-        opcode = f.curInsn().getOpcode();
-        index = f.currentMethod().instructions.indexOf(f.curInsn());
+        if (f == null)
+            return;
+
+        ClassModel cm = f.curClass();
+        MethodModel mm = f.curMethod();
+        className = cm == null ? "" : cm.getName();
+        methodName = mm == null ? "" : mm.getName();
+        opcode = f.curOpcode();
+        index = f.curBCI();
     }
 
     private static String makeDetailMessage(Frame f, String moreMessage) {
@@ -84,8 +88,13 @@
         if (className != null)
             return this;  // already decorated
         // make a new one with a good message:
-        InterpreterError ex = new InterpreterError(f, getMessage(), getCause(), false, true);
-        ex.setStackTrace(this.getStackTrace());
-        return ex;
+        try {
+            InterpreterError ex = new InterpreterError(f, getMessage(), getCause(), false, true);
+            ex.setStackTrace(this.getStackTrace());
+            return ex;
+        }
+        catch (Throwable t) {
+            return this;
+        }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/MethodModel.java	Thu Jun 09 11:04:48 2016 -0400
@@ -0,0 +1,47 @@
+/*
+ * 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.util.function.Predicate;
+
+/**
+ * MethodModel
+ *
+ * @author Brian Goetz
+ */
+public interface MethodModel {
+    ClassModel getClassModel();
+    String getName();
+    String getDesc();
+    int getAccess();
+    int getMaxStack();
+    int getMaxLocals();
+    byte[] getBytecode();
+
+    Insn getInsn(int bci);
+
+    /** Returns 0 if no handler found, otherwise returns offset */
+    int findExceptionHandler(int bci, Predicate<String> typeMatcher);
+}
--- a/interpreter/src/valhalla/interpreter/OpcodeHandler.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/src/valhalla/interpreter/OpcodeHandler.java	Thu Jun 09 11:04:48 2016 -0400
@@ -24,7 +24,6 @@
  */
 package valhalla.interpreter;
 
-import jdk.internal.org.objectweb.asm.tree.LabelNode;
 
 /**
  * Functional interface for a handler that interprets a single bytecode.  The
@@ -44,27 +43,27 @@
     class HandlerAction {
 
         final ActionKind actionKind;
-        final LabelNode branchTarget;
+        final int branchOffset;
         final Object returnValue;
 
-        public static final HandlerAction NEXT = new HandlerAction(ActionKind.NEXT, null, null);
+        public static final HandlerAction NEXT = new HandlerAction(ActionKind.NEXT, -1, null);
 
-        private HandlerAction(ActionKind actionKind, LabelNode branchTarget, Object returnValue) {
+        private HandlerAction(ActionKind actionKind, int branchOffset, Object returnValue) {
             this.actionKind = actionKind;
-            this.branchTarget = branchTarget;
+            this.branchOffset = branchOffset;
             this.returnValue = returnValue;
         }
 
         static HandlerAction retVoid() {
-            return new HandlerAction(ActionKind.RETURN, null, null);
+            return new HandlerAction(ActionKind.RETURN, -1, null);
         }
 
         static HandlerAction ret(Object o) {
-            return new HandlerAction(ActionKind.RETURN, null, o);
+            return new HandlerAction(ActionKind.RETURN, -1, o);
         }
 
-        static HandlerAction branch(LabelNode label) {
-            return new HandlerAction(ActionKind.BRANCH, label, null);
+        static HandlerAction branch(int branchOffset) {
+            return new HandlerAction(ActionKind.BRANCH, branchOffset, null);
         }
 
         static HandlerAction next() {
@@ -72,12 +71,13 @@
         }
 
         static HandlerAction exception(Throwable t) {
-            return new HandlerAction(ActionKind.THROW, null, t);
+            return new HandlerAction(ActionKind.THROW, -1, t);
         }
 
         interface RunnableWithResults {
             Object runOrThrow() throws Throwable;
         }
+
         static HandlerAction retOrException(RunnableWithResults fn) throws InterpreterError {
             Object res;
             try {
@@ -85,17 +85,20 @@
             } catch (InterpreterError ex) {
                 throw ex;   // do not wrap this particular error; pass it to interpreter framework
             } catch (Throwable ex) {
-                return HandlerAction.exception(ex);
+                return exception(ex);
             }
-            return HandlerAction.ret(res);
+            return ret(res);
         }
+
         boolean isRetOrException() {
             return actionKind == ActionKind.RETURN ||actionKind == ActionKind.THROW;
         }
+
         HandlerAction asRetOrException() {
             if (isRetOrException())  return this;
             throw new InterpreterError("cannot decode handler for return: "+actionKind);
         }
+
         Object getReturnValueOrThrow() throws InterpreterError, Throwable {
             switch (actionKind) {
                 case RETURN:           return returnValue;
--- a/interpreter/src/valhalla/interpreter/ProxyClassBuilder.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/src/valhalla/interpreter/ProxyClassBuilder.java	Thu Jun 09 11:04:48 2016 -0400
@@ -127,6 +127,6 @@
         cw.visitEnd();
         byte[] bytes = cw.toByteArray();
 //        new ClassReader(bytes).accept(new TraceClassVisitor(new PrintWriter(System.out)), 0);
-        return Interpreter.Internal.loadClass(interpreter, className, bytes);
+        return InternalHelpers.loadClass(interpreter, className, bytes);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/StandardClassModel.java	Thu Jun 09 11:04:48 2016 -0400
@@ -0,0 +1,219 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import com.sun.tools.classfile.BootstrapMethods_attribute;
+import com.sun.tools.classfile.BootstrapMethods_attribute.BootstrapMethodSpecifier;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.ConstantPool;
+import com.sun.tools.classfile.ConstantPool.CONSTANT_InvokeDynamic_info;
+import com.sun.tools.classfile.ConstantPool.CONSTANT_MethodHandle_info;
+import com.sun.tools.classfile.ConstantPool.CONSTANT_MethodType_info;
+import com.sun.tools.classfile.ConstantPool.CONSTANT_NameAndType_info;
+import com.sun.tools.classfile.ConstantPool.CPRefInfo;
+import com.sun.tools.classfile.ConstantPoolException;
+
+import static com.sun.tools.classfile.ConstantPool.CONSTANT_Double;
+import static com.sun.tools.classfile.ConstantPool.CONSTANT_Float;
+import static com.sun.tools.classfile.ConstantPool.CONSTANT_Integer;
+import static com.sun.tools.classfile.ConstantPool.CONSTANT_Long;
+import static com.sun.tools.classfile.ConstantPool.CONSTANT_String;
+import static valhalla.interpreter.Util.launder;
+import static valhalla.interpreter.Util.readFully;
+
+/**
+ * StandardClassModel
+ *
+ * @author Brian Goetz
+ */
+public class StandardClassModel implements ClassModel {
+    private final Interpreter interpreter;
+    private final String binaryName;
+    private final ClassFile classFile;
+    private final ConstantPool constantPool;
+    private final BootstrapMethods_attribute bootstrapMethods;
+    private final MethodModel[] methods;
+    private ProxyClassBuilder proxyClassBuilder;
+    private Class<?> representationClass;
+
+    private StandardClassModel(Interpreter interpreter, String binaryName, byte[] bytes) {
+        this.interpreter = interpreter;
+        this.binaryName = binaryName;
+        try {
+            classFile = ClassFile.read(new ByteArrayInputStream(bytes));
+            constantPool = classFile.constant_pool;
+            bootstrapMethods = (BootstrapMethods_attribute) classFile.attributes.get("BootstrapMethods");
+            methods = Stream.of(classFile.methods).map(m -> new StandardMethodModel(this, classFile, m)).toArray(MethodModel[]::new);
+        }
+        catch (IOException | ConstantPoolException e) {
+            throw new ClassFormatError(e.getMessage());
+        }
+    }
+
+    public static ClassModel interpreted(Interpreter interpreter, String binaryName, byte[] bytes) {
+        StandardClassModel model = new StandardClassModel(interpreter, binaryName, bytes);
+        ClassFile classFile = model.classFile;
+        model.proxyClassBuilder = ProxyClassBuilder.make(interpreter,
+                                                         classFile.access_flags.flags,
+                                                         launder(classFile::getName),
+                                                         launder(classFile::getSuperclassName),
+                                                         IntStream.range(0, classFile.interfaces.length)
+                                                                  .mapToObj(i -> launder(() -> classFile.getInterfaceName(i)))
+                                                                  .toArray(String[]::new));
+        Stream.of(classFile.fields)
+              .forEach(f -> model.proxyClassBuilder.addField(f.access_flags.flags,
+                                                             launder(() -> f.getName(classFile.constant_pool)),
+                                                             launder(() -> f.descriptor.getValue(classFile.constant_pool))));
+        Stream.of(classFile.methods)
+              .forEach(m -> model.proxyClassBuilder.addMethod(m.access_flags.flags,
+                                                              launder(() -> m.getName(classFile.constant_pool)),
+                                                              launder(() -> m.descriptor.getValue(classFile.constant_pool))));
+        return model;
+    }
+
+    public static ClassModel interpreted(Interpreter interpreter, Class<?> clazz) {
+        try (InputStream stream = ClassLoader.getSystemClassLoader()
+                                             .getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
+
+            String name = clazz.getName();
+            StandardClassModel model = new StandardClassModel(interpreter, clazz.getName().replace('.', '/'), readFully(stream));
+            model.representationClass = clazz;
+            return model;
+        }
+        catch (IOException e) {
+            throw new InterpreterError(e);
+        }
+    }
+
+    @Override
+    public Interpreter interpreter() {
+        return interpreter;
+    }
+
+    @Override
+    public String getName() {
+        return binaryName;
+    }
+
+    @Override
+    public Stream<MethodModel> methods() {
+        return Stream.of(methods);
+    }
+
+    @Override
+    public Class<?> getRepresentationClass() {
+        if (representationClass == null && proxyClassBuilder != null) {
+            representationClass = proxyClassBuilder.build();
+            proxyClassBuilder = null;
+        }
+
+        return representationClass;
+    }
+
+    @Override
+    public String cpUTF8(int index) {
+        return launder(() -> constantPool.getUTF8Value(index));
+    }
+
+    @Override
+    public String cpClass(int index) {
+        return launder(() -> constantPool.getClassInfo(index).getName());
+    }
+
+    @Override
+    public MemberDesc cpMemberRef(int index) {
+        try {
+            CPRefInfo refInfo = (CPRefInfo) constantPool.get(index);
+            CONSTANT_NameAndType_info nt = refInfo.getNameAndTypeInfo();
+            return new MemberDesc(refInfo.getClassName(), nt.getName(), nt.getType());
+        }
+        catch (ConstantPoolException e) {
+            throw new InterpreterError(e);
+        }
+    }
+
+    @Override
+    public String cpMethodType(int index) {
+        return launder(() -> cpUTF8(((CONSTANT_MethodType_info) constantPool.get(index)).descriptor_index));
+    }
+
+    @Override
+    public MethodHandleDesc cpMethodHandle(int index) {
+        try {
+            CONSTANT_MethodHandle_info mh = (CONSTANT_MethodHandle_info) constantPool.get(index);
+            CPRefInfo ref = mh.getCPRefInfo();
+            CONSTANT_NameAndType_info nt = ref.getNameAndTypeInfo();
+            return new MethodHandleDesc(mh.reference_kind.tag, ref.getClassName(), nt.getName(), nt.getType());
+        }
+        catch (ConstantPoolException e) {
+            throw new InterpreterError(e);
+        }
+    }
+
+    @Override
+    public IndyDesc cpIndy(int index) {
+        try {
+            CONSTANT_InvokeDynamic_info indy = (CONSTANT_InvokeDynamic_info) constantPool.get(index);
+            BootstrapMethodSpecifier bootstrap = bootstrapMethods.bootstrap_method_specifiers[indy.bootstrap_method_attr_index];
+            return new IndyDesc(indy.getNameAndTypeInfo().getName(),
+                                indy.getNameAndTypeInfo().getType(),
+                                bootstrap.bootstrap_method_ref,
+                                bootstrap.bootstrap_arguments);
+        }
+        catch (ConstantPoolException e) {
+            throw new InterpreterError(e);
+        }
+    }
+
+    @Override
+    public int cpKind(int index) {
+        return launder(() -> constantPool.get(index).getTag());
+    }
+
+    @Override
+    public Object cpSimpleConstant(int index) {
+        try {
+            ConstantPool.CPInfo info = constantPool.get(index);
+            switch (info.getTag()) {
+                case CONSTANT_Integer: return ((ConstantPool.CONSTANT_Integer_info) info).value;
+                case CONSTANT_Float:   return ((ConstantPool.CONSTANT_Float_info) info).value;
+                case CONSTANT_Long:    return ((ConstantPool.CONSTANT_Long_info) info).value;
+                case CONSTANT_Double:  return ((ConstantPool.CONSTANT_Double_info) info).value;
+                case CONSTANT_String:  return ((ConstantPool.CONSTANT_String_info) info).getString();
+                default:
+                    throw new IllegalArgumentException("Invalid constant tag: " + info.getTag());
+            }
+        }
+        catch (ConstantPoolException e) {
+            throw new InterpreterError(e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/StandardInterpreter.java	Thu Jun 09 11:04:48 2016 -0400
@@ -0,0 +1,338 @@
+/*
+ * 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 com.sun.tools.classfile.Opcode;
+
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+import static valhalla.interpreter.InternalHelpers.UNSAFE;
+import static valhalla.interpreter.OpcodeHandler.HandlerAction.branch;
+import static valhalla.interpreter.OpcodeHandler.HandlerAction.exception;
+import static valhalla.interpreter.OpcodeHandler.HandlerAction.next;
+import static valhalla.interpreter.OpcodeHandler.HandlerAction.ret;
+import static valhalla.interpreter.OpcodeHandler.HandlerAction.retVoid;
+import static valhalla.interpreter.Util.read32s;
+import static valhalla.interpreter.Util.read8u;
+
+/**
+ * StandardInterpreter -- intepreter for Java 8 bytecode
+ *
+ * @author Brian Goetz
+ */
+public class StandardInterpreter extends Interpreter {
+    private final OpcodeHandler[] handlers = new OpcodeHandler[255];
+    private final int[] opcodeLen = new int[255];
+
+    public StandardInterpreter(String... paths) {
+        super(paths);
+    }
+
+    @Override
+    public Insn recognizeInstruction(byte[] bytes, int offset) {
+        int opcode = read8u(bytes, offset);
+        int length = opcodeLen[opcode];
+        if (length > 0) {
+            return new Insn(opcode, offset, length, handlers[opcode]);
+        }
+        else if (opcode == TABLESWITCH) {
+            int x = offset + (4 - (offset & 3)) + 4;
+            int low = read32s(bytes, x);
+            int high = read32s(bytes, x + 4);
+            x += 8 + (4 * (high-low+1));
+            return new Insn(opcode, offset, x - offset, handlers[opcode]);
+        }
+        else if (opcode == LOOKUPSWITCH) {
+            int x = offset + (4 - (offset & 3)) + 4;
+            int npairs = read32s(bytes, x);
+            x += 4 + (8 * npairs);
+            return new Insn(opcode, offset, x - offset, handlers[opcode]);
+        }
+        else
+            throw new InterpreterError(String.format("Unknown bytecode %d at bci=%d", opcode, offset));
+    }
+
+    @Override
+    public ClassModel newClassModel(String binaryName, byte[] bytes) {
+        return StandardClassModel.interpreted(this, binaryName, bytes);
+    }
+
+    private void opcode(int opcode, OpcodeHandler handler) {
+        opcode(opcode, 0, handler);
+    }
+
+    // No support yet for WIDE
+    // addlBytes=-1 indicates custom computation
+    private void opcode(int opcode, int addlBytes, OpcodeHandler handler) {
+        handlers[opcode] = handler;
+        opcodeLen[opcode] = 1 + addlBytes;
+    }
+
+    {
+        opcode(RETURN, f -> retVoid());
+        opcode(ARETURN, f -> ret(f.apop()));
+        opcode(IRETURN, f -> ret(f.ipop()));
+        opcode(LRETURN, f -> ret(f.lpop()));
+        opcode(DRETURN, f -> ret(f.dpop()));
+        opcode(FRETURN, f -> ret(f.fpop()));
+        opcode(ATHROW, f -> exception((Throwable) f.apop()));
+        opcode(NOP, exec(f -> { }));
+        opcode(ACONST_NULL, exec(f -> f.apush(null)));
+        opcode(ICONST_M1, exec(f -> f.ipush(-1)));
+        opcode(ICONST_0, exec(f -> f.ipush(0)));
+        opcode(ICONST_1, exec(f -> f.ipush(1)));
+        opcode(ICONST_2, exec(f -> f.ipush(2)));
+        opcode(ICONST_3, exec(f -> f.ipush(3)));
+        opcode(ICONST_4, exec(f -> f.ipush(4)));
+        opcode(ICONST_5, exec(f -> f.ipush(5)));
+        opcode(LCONST_0, exec(f -> f.lpush(0L)));
+        opcode(LCONST_1, exec(f -> f.lpush(1L)));
+        opcode(FCONST_0, exec(f -> f.fpush(0.0f)));
+        opcode(FCONST_1, exec(f -> f.fpush(1.0f)));
+        opcode(FCONST_2, exec(f -> f.fpush(2.0f)));
+        opcode(DCONST_0, exec(f -> f.dpush(1.0d)));
+        opcode(DCONST_1, exec(f -> f.dpush(2.0d)));
+        opcode(L2I, f -> lpop(f, a -> exec(() -> f.ipush((int) a))));
+        opcode(L2F, f -> lpop(f, a -> exec(() -> f.fpush((float) a))));
+        opcode(L2D, f -> lpop(f, a -> exec(() -> f.dpush((double) a))));
+        opcode(F2I, f -> fpop(f, a -> exec(() -> f.ipush((int) a))));
+        opcode(F2L, f -> fpop(f, a -> exec(() -> f.lpush((long) a))));
+        opcode(F2D, f -> fpop(f, a -> exec(() -> f.dpush((double) a))));
+        opcode(D2I, f -> dpop(f, a -> exec(() -> f.ipush((int) a))));
+        opcode(D2L, f -> dpop(f, a -> exec(() -> f.lpush((long) a))));
+        opcode(D2F, f -> dpop(f, a -> exec(() -> f.fpush((float) a))));
+        opcode(I2F, f -> ipop(f, a -> exec(() -> f.fpush((float) a))));
+        opcode(I2L, f -> ipop(f, a -> exec(() -> f.lpush((long) a))));
+        opcode(I2D, f -> ipop(f, a -> exec(() -> f.dpush((double) a))));
+        opcode(I2B, f -> ipop(f, a -> exec(() -> f.ipush((byte) a))));
+        opcode(I2C, f -> ipop(f, a -> exec(() -> f.ipush((char) a))));
+        opcode(I2S, f -> ipop(f, a -> exec(() -> f.ipush((short) a))));
+        opcode(IALOAD, f -> ipop(f, ix -> apop(f, (int[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.ipush(arr[ix]))))));
+        opcode(LALOAD, f -> ipop(f, ix -> apop(f, (long[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.lpush(arr[ix]))))));
+        opcode(FALOAD, f -> ipop(f, ix -> apop(f, (float[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.fpush(arr[ix]))))));
+        opcode(DALOAD, f -> ipop(f, ix -> apop(f, (double[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.dpush(arr[ix]))))));
+        opcode(AALOAD, f -> ipop(f, ix -> apop(f, (Object[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.apush(arr[ix]))))));
+        opcode(BALOAD, f -> ipop(f, ix -> apop(f, (byte[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.ipush(arr[ix]))))));
+        opcode(CALOAD, f -> ipop(f, ix -> apop(f, (char[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.ipush(arr[ix]))))));
+        opcode(SALOAD, f -> ipop(f, ix -> apop(f, (short[] arr) -> rangeCheck(arr, ix, () -> exec(() -> f.ipush(arr[ix]))))));
+        opcode(IASTORE, f -> ipop(f, v -> ipop(f, ix -> apop(f, (int[] arr) -> rangeCheck(arr, ix, () -> exec(() -> arr[ix] = v))))));
+        opcode(LASTORE, f -> lpop(f, v -> ipop(f, ix -> apop(f, (long[] arr) -> rangeCheck(arr, ix, () -> exec(() -> arr[ix] = v))))));
+        opcode(FASTORE, f -> fpop(f, v -> ipop(f, ix -> apop(f, (float[] arr) -> rangeCheck(arr, ix, () -> exec(() -> arr[ix] = v))))));
+        opcode(DASTORE, f -> dpop(f, v -> ipop(f, ix -> apop(f, (double[] arr) -> rangeCheck(arr, ix, () -> exec(() -> arr[ix] = v))))));
+        opcode(AASTORE, f -> apop(f, v -> ipop(f, ix -> apop(f, (Object[] arr) -> storeCheck(arr, ix, v, () -> exec(() -> arr[ix] = v))))));
+        opcode(BASTORE, f -> ipop(f, v -> ipop(f, ix -> apop(f, (byte[] arr) -> rangeCheck(arr, ix, () -> exec(() -> arr[ix] = (byte) v))))));
+        opcode(CASTORE, f -> ipop(f, v -> ipop(f, ix -> apop(f, (char[] arr) -> rangeCheck(arr, ix, () -> exec(() -> arr[ix] = (char) v))))));
+        opcode(SASTORE, f -> ipop(f, v -> ipop(f, ix -> apop(f, (short[] arr) -> rangeCheck(arr, ix, () -> exec(() -> arr[ix] = (short) v))))));
+        opcode(POP, f -> exec(f::pop));
+        opcode(POP2, f -> exec(f::pop2));
+        opcode(DUP, f -> exec(f::dup));
+        opcode(DUP_X1, f -> exec(f::dup_x1));
+        opcode(DUP_X2, f -> exec(f::dup_x2));
+        opcode(DUP2, f -> exec(f::dup2));
+        opcode(DUP2_X1, f -> exec(f::dup2_x1));
+        opcode(DUP2_X2, f -> exec(f::dup2_x2));
+        opcode(SWAP, f -> exec(f::swap));
+        opcode(IADD, f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a + b)))));
+        opcode(LADD, f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a + b)))));
+        opcode(FADD, f -> fpop(f, b -> fpop(f, a -> exec(() -> f.fpush(a + b)))));
+        opcode(DADD, f -> dpop(f, b -> dpop(f, a -> exec(() -> f.dpush(a + b)))));
+        opcode(ISUB, f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a - b)))));
+        opcode(LSUB, f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a - b)))));
+        opcode(FSUB, f -> fpop(f, b -> fpop(f, a -> exec(() -> f.fpush(a - b)))));
+        opcode(DSUB, f -> dpop(f, b -> dpop(f, a -> exec(() -> f.dpush(a - b)))));
+        opcode(IMUL, f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a * b)))));
+        opcode(LMUL, f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a * b)))));
+        opcode(FMUL, f -> fpop(f, b -> fpop(f, a -> exec(() -> f.fpush(a * b)))));
+        opcode(DMUL, f -> dpop(f, b -> dpop(f, a -> exec(() -> f.dpush(a * b)))));
+        opcode(IREM, f -> ipop(f, b -> ipop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.ipush(a % b)))));
+        opcode(LREM, f -> lpop(f, b -> lpop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.lpush(a % b)))));
+        opcode(FREM, f -> fpop(f, b -> fpop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.fpush(a % b)))));
+        opcode(DREM, f -> dpop(f, b -> dpop(f, a -> (b == 0) ? aex(() -> a % b) : exec(() -> f.dpush(a % b)))));
+        opcode(IDIV, f -> ipop(f, b -> ipop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.ipush(a / b)))));
+        opcode(LDIV, f -> ipop(f, b -> lpop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.lpush(a / b)))));
+        opcode(FDIV, f -> fpop(f, b -> fpop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.fpush(a / b)))));
+        opcode(DDIV, f -> dpop(f, b -> dpop(f, a -> (b == 0) ? aex(() -> a / b) : exec(() -> f.dpush(a / b)))));
+        opcode(IAND, f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a & b)))));
+        opcode(LAND, f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a & b)))));
+        opcode(IOR, f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a | b)))));
+        opcode(LOR, f -> lpop(f, b -> lpop(f, a -> exec(() -> f.dpush(a | b)))));
+        opcode(IXOR, f -> ipop(f, b -> ipop(f, a -> exec(() -> f.ipush(a ^ b)))));
+        opcode(LXOR, f -> lpop(f, b -> lpop(f, a -> exec(() -> f.lpush(a ^ b)))));
+        opcode(INEG, f -> ipop(f, a -> exec(() -> f.ipush(-a))));
+        opcode(LNEG, f -> lpop(f, a -> exec(() -> f.lpush(-a))));
+        opcode(FNEG, f -> fpop(f, a -> exec(() -> f.fpush(-a))));
+        opcode(DNEG, f -> dpop(f, a -> exec(() -> f.dpush(-a))));
+        opcode(ISHL, f -> ipop(f, n -> ipop(f, a -> exec(() -> f.ipush(a << n)))));
+        opcode(LSHL, f -> ipop(f, n -> lpop(f, a -> exec(() -> f.lpush(a << n)))));
+        opcode(ISHR, f -> ipop(f, n -> ipop(f, a -> exec(() -> f.ipush(a >> n)))));
+        opcode(LSHR, f -> ipop(f, n -> lpop(f, a -> exec(() -> f.lpush(a >> n)))));
+        opcode(IUSHR, f -> ipop(f, n -> ipop(f, a -> exec(() -> f.ipush(a >>> n)))));
+        opcode(LUSHR, f -> ipop(f, n -> lpop(f, a -> exec(() -> f.lpush(a >>> n)))));
+        opcode(ARRAYLENGTH, f -> apop(f, arr -> (arr == null) ? npe() : exec(() -> f.ipush(arraylength(arr)))));
+        opcode(BIPUSH, 1, f -> exec(() -> f.ipush(f.read8u(1))));
+        opcode(SIPUSH, 2, f -> exec(() -> f.ipush(f.read16u(1))));
+        opcode(ILOAD, 1, f -> exec(() -> f.ipush(f.iload(f.read8u(1)))));
+        opcode(Opcode.ILOAD_0.opcode, f -> exec(() -> f.ipush(f.iload(0))));
+        opcode(Opcode.ILOAD_1.opcode, f -> exec(() -> f.ipush(f.iload(1))));
+        opcode(Opcode.ILOAD_2.opcode, f -> exec(() -> f.ipush(f.iload(2))));
+        opcode(Opcode.ILOAD_3.opcode, f -> exec(() -> f.ipush(f.iload(3))));
+        opcode(LLOAD, 1, f -> exec(() -> f.lpush(f.lload(f.read8u(1)))));
+        opcode(Opcode.LLOAD_0.opcode, f -> exec(() -> f.lpush(f.lload(0))));
+        opcode(Opcode.LLOAD_1.opcode, f -> exec(() -> f.lpush(f.lload(1))));
+        opcode(Opcode.LLOAD_2.opcode, f -> exec(() -> f.lpush(f.lload(2))));
+        opcode(Opcode.LLOAD_3.opcode, f -> exec(() -> f.lpush(f.lload(3))));
+        opcode(FLOAD, 1, f -> exec(() -> f.fpush(f.fload(f.read8u(1)))));
+        opcode(Opcode.FLOAD_0.opcode, f -> exec(() -> f.fpush(f.fload(0))));
+        opcode(Opcode.FLOAD_1.opcode, f -> exec(() -> f.fpush(f.fload(1))));
+        opcode(Opcode.FLOAD_2.opcode, f -> exec(() -> f.fpush(f.fload(2))));
+        opcode(Opcode.FLOAD_3.opcode, f -> exec(() -> f.fpush(f.fload(3))));
+        opcode(DLOAD, 1, f -> exec(() -> f.dpush(f.dload(f.read8u(1)))));
+        opcode(Opcode.DLOAD_0.opcode, f -> exec(() -> f.dpush(f.dload(0))));
+        opcode(Opcode.DLOAD_1.opcode, f -> exec(() -> f.dpush(f.dload(1))));
+        opcode(Opcode.DLOAD_2.opcode, f -> exec(() -> f.dpush(f.dload(2))));
+        opcode(Opcode.DLOAD_3.opcode, f -> exec(() -> f.dpush(f.dload(3))));
+        opcode(ALOAD, 1, f -> exec(() -> f.apush(f.aload(f.read8u(1)))));
+        opcode(Opcode.ALOAD_0.opcode, f -> exec(() -> f.apush(f.aload(0))));
+        opcode(Opcode.ALOAD_1.opcode, f -> exec(() -> f.apush(f.aload(1))));
+        opcode(Opcode.ALOAD_2.opcode, f -> exec(() -> f.apush(f.aload(2))));
+        opcode(Opcode.ALOAD_3.opcode, f -> exec(() -> f.apush(f.aload(3))));
+        opcode(ISTORE, 1, f -> ipop(f, a -> exec(() -> f.istore(f.read8u(1), a))));
+        opcode(Opcode.ISTORE_0.opcode, f -> ipop(f, i -> exec(() -> f.istore(0, i))));
+        opcode(Opcode.ISTORE_1.opcode, f -> ipop(f, i -> exec(() -> f.istore(1, i))));
+        opcode(Opcode.ISTORE_2.opcode, f -> ipop(f, i -> exec(() -> f.istore(2, i))));
+        opcode(Opcode.ISTORE_3.opcode, f -> ipop(f, i -> exec(() -> f.istore(3, i))));
+        opcode(LSTORE, 1, f -> lpop(f, a -> exec(() -> f.lstore(f.read8u(1), a))));
+        opcode(Opcode.LSTORE_0.opcode, f -> lpop(f, i -> exec(() -> f.lstore(0, i))));
+        opcode(Opcode.LSTORE_1.opcode, f -> lpop(f, i -> exec(() -> f.lstore(1, i))));
+        opcode(Opcode.LSTORE_2.opcode, f -> lpop(f, i -> exec(() -> f.lstore(2, i))));
+        opcode(Opcode.LSTORE_3.opcode, f -> lpop(f, i -> exec(() -> f.lstore(3, i))));
+        opcode(FSTORE, 1, f -> fpop(f, a -> exec(() -> f.fstore(f.read8u(1), a))));
+        opcode(Opcode.FSTORE_0.opcode, f -> fpop(f, i -> exec(() -> f.fstore(0, i))));
+        opcode(Opcode.FSTORE_1.opcode, f -> fpop(f, i -> exec(() -> f.fstore(1, i))));
+        opcode(Opcode.FSTORE_2.opcode, f -> fpop(f, i -> exec(() -> f.fstore(2, i))));
+        opcode(Opcode.FSTORE_3.opcode, f -> fpop(f, i -> exec(() -> f.fstore(3, i))));
+        opcode(DSTORE, 1, f -> dpop(f, a -> exec(() -> f.dstore(f.read8u(1), a))));
+        opcode(Opcode.DSTORE_0.opcode, f -> dpop(f, i -> exec(() -> f.dstore(0, i))));
+        opcode(Opcode.DSTORE_1.opcode, f -> dpop(f, i -> exec(() -> f.dstore(1, i))));
+        opcode(Opcode.DSTORE_2.opcode, f -> dpop(f, i -> exec(() -> f.dstore(2, i))));
+        opcode(Opcode.DSTORE_3.opcode, f -> dpop(f, i -> exec(() -> f.dstore(3, i))));
+        opcode(ASTORE, 1, f -> apop(f, a -> exec(() -> f.astore(f.read8u(1), a))));
+        opcode(Opcode.ASTORE_0.opcode, f -> apop(f, a -> exec(() -> f.astore(0, a))));
+        opcode(Opcode.ASTORE_1.opcode, f -> apop(f, a -> exec(() -> f.astore(1, a))));
+        opcode(Opcode.ASTORE_2.opcode, f -> apop(f, a -> exec(() -> f.astore(2, a))));
+        opcode(Opcode.ASTORE_3.opcode, f -> apop(f, a -> exec(() -> f.astore(3, a))));
+        opcode(IINC, 2, f -> exec(() -> f.istore(f.read8u(1), f.iload(f.read8u(1)) + f.read8s(2))));
+
+        opcode(INVOKESTATIC, 2, this::invokeOp);
+        opcode(INVOKEINTERFACE, 4, this::invokeOp);
+        opcode(INVOKEVIRTUAL, 2, this::invokeOp);
+        opcode(INVOKESPECIAL, 2, this::invokeOp);
+        opcode(INVOKEDYNAMIC, 4, this::invokeDynamicOp);
+        opcode(GETFIELD, 2, this::fieldOp);
+        opcode(PUTFIELD, 2, this::fieldOp);
+        opcode(GETSTATIC, 2, this::fieldOp);
+        opcode(PUTSTATIC, 2, this::fieldOp);
+
+        opcode(LDC, 1, f -> loadConstantOp(f, f.read8u(1)));
+        opcode(Opcode.LDC_W.opcode, 2, f -> loadConstantOp(f, f.read16u(1)));
+        opcode(Opcode.LDC2_W.opcode, 2, f -> loadConstantOp(f, f.read16u(1)));
+
+        opcode(GOTO, 2, f -> branch(f.read16s(1)));
+        opcode(IFNULL, 2, f -> apop(f, a -> (a == null) ? branch(f.read16s(1)) : next()));
+        opcode(IFNONNULL, 2, f -> apop(f, a -> (a != null) ? branch(f.read16s(1)) : next()));
+        opcode(IF_ACMPEQ, 2, f -> apop(f, a -> apop(f, b -> (a == b) ? branch(f.read16s(1)) : next())));
+        opcode(IF_ACMPNE, 2, f -> apop(f, a -> apop(f, b -> (a != b) ? branch(f.read16s(1)) : next())));
+        opcode(IF_ICMPEQ, 2, f -> ipop(f, b -> ipop(f, a -> (a == b) ? branch(f.read16s(1)) : next())));
+        opcode(IF_ICMPNE, 2, f -> ipop(f, b -> ipop(f, a -> (a != b) ? branch(f.read16s(1)) : next())));
+        opcode(IF_ICMPLT, 2, f -> ipop(f, b -> ipop(f, a -> (a < b) ? branch(f.read16s(1)) : next())));
+        opcode(IF_ICMPGE, 2, f -> ipop(f, b -> ipop(f, a -> (a >= b) ? branch(f.read16s(1)) : next())));
+        opcode(IF_ICMPGT, 2, f -> ipop(f, b -> ipop(f, a -> (a > b) ? branch(f.read16s(1)) : next())));
+        opcode(IF_ICMPLE, 2, f -> ipop(f, b -> ipop(f, a -> (a <= b) ? branch(f.read16s(1)) : next())));
+        opcode(LCMP, f -> lpop(f, b -> lpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1)))));
+        opcode(FCMPL, f -> fpop(f, b -> fpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1)))));
+        opcode(FCMPG, f -> fpop(f, b -> fpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1)))));
+        opcode(DCMPL, f -> dpop(f, b -> dpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1)))));
+        opcode(DCMPG, f -> dpop(f, b -> dpop(f, a -> exec(() -> f.ipush((a == b) ? 0 : (a < b) ? -1 : 1)))));
+        opcode(IFEQ, 2, f -> ipop(f, a -> (a == 0) ? branch(f.read16s(1)) : next()));
+        opcode(IFNE, 2, f -> ipop(f, a -> (a != 0) ? branch(f.read16s(1)) : next()));
+        opcode(IFLT, 2, f -> ipop(f, a -> (a < 0) ? branch(f.read16s(1)) : next()));
+        opcode(IFGE, 2, f -> ipop(f, a -> (a >= 0) ? branch(f.read16s(1)) : next()));
+        opcode(IFGT, 2, f -> ipop(f, a -> (a > 0) ? branch(f.read16s(1)) : next()));
+        opcode(IFLE, 2, f -> ipop(f, a -> (a <= 0) ? branch(f.read16s(1)) : next()));
+
+        opcode(MONITORENTER, f -> apop(f, a -> (a == null) ? npe() : exec(() -> UNSAFE.monitorEnter(a))));
+        opcode(MONITOREXIT, f -> apop(f, a -> (a == null) ? npe() : exec(() -> UNSAFE.monitorExit(a))));
+
+        opcode(NEW, 2, f -> {
+            try {
+                f.apush(UNSAFE.allocateInstance(toClass(f, f.readClassName(1))));
+                return next();
+            }
+            catch (InstantiationException e) {
+                throw new InterpreterError(f, "NEW " + f.readClassName(1), e);
+            }
+        });
+        opcode(INSTANCEOF, 2,
+               f -> apop(f, a -> exec(() -> f.ipush((a != null
+                                                     && toClass(f, f.readClassName(1)).isAssignableFrom(a.getClass())) ? 1 : 0))));
+        opcode(CHECKCAST, 2, f -> apop(f, a -> {
+            Class<?> clazz = toClass(f, f.readClassName(1));
+            return (a == null || clazz.isAssignableFrom(a.getClass()))
+                   ? exec(() -> f.apush(a))
+                   : dothrow(() -> clazz.cast(a), ClassCastException.class);
+        }));
+
+        opcode(NEWARRAY, 1, f -> newarray(f, toPrimClass(f.read8u(1)), f.ipop()));
+        opcode(ANEWARRAY, 2, f -> newarray(f, toClass(f, f.readClassName(1)), f.ipop()));
+        opcode(MULTIANEWARRAY, 3, f -> {
+            int[] lens = new int[f.read8u(3)];
+            Class<?> clazz = toClass(f, f.readClassName(1));
+            for (int i = lens.length; --i >= 0; ) {
+                lens[i] = f.ipop();
+                clazz = requireNonNull(clazz.getComponentType());
+            }
+            return newarray(f, clazz, lens);
+        });
+        opcode(TABLESWITCH, -1, f -> {
+            int target = f.ipop();
+            int pad = 4 - (f.curBCI() & 3);
+            int dflt = f.read32s(pad);
+            int low = f.read32s(pad + 4);
+            int high = f.read32s(pad + 8);
+            return branch((target < low || target > high) ? dflt : f.read32s(pad + 12 + 4 * (target - low)));
+        });
+        opcode(LOOKUPSWITCH, -1, f -> {
+            int target = f.ipop();
+            int pad = 4 - (f.curBCI() & 3);
+            int dflt = f.read32s(pad);
+            int npairs = f.read32s(pad + 4);
+            for (int i=0, ix = pad + 8; i<npairs; i++, ix += 8) {
+                if (target == f.read32s(ix))
+                    return branch(f.read32s(ix+4));
+            }
+            return branch(dflt);
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/StandardMethodModel.java	Thu Jun 09 11:04:48 2016 -0400
@@ -0,0 +1,116 @@
+/*
+ * 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.util.function.Predicate;
+import java.util.stream.Stream;
+
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.Code_attribute;
+import com.sun.tools.classfile.Method;
+
+import static valhalla.interpreter.Util.launder;
+
+/**
+ * MethodModel
+ *
+ * @author Brian Goetz
+ */
+public class StandardMethodModel implements MethodModel {
+    private final StandardClassModel classModel;
+    private final ClassFile classFile;
+    private final Method method;
+    private final Code_attribute code;
+    private final byte[] bytecode;
+    private final Insn[] insns;
+
+    public StandardMethodModel(StandardClassModel classModel, ClassFile classFile, Method method) {
+        this.classModel = classModel;
+        this.classFile = classFile;
+        this.method = method;
+        this.code = (Code_attribute) method.attributes.get("Code");
+        this.bytecode = code == null ? new byte[0] : code.code;
+        this.insns = new Insn[bytecode.length];
+
+        for (int bci=0; bci < bytecode.length; ) {
+            Insn in = classModel.interpreter().recognizeInstruction(bytecode, bci);
+            insns[bci] = in;
+            bci += in.getLength();
+        }
+    }
+
+    @Override
+    public ClassModel getClassModel() {
+        return classModel;
+    }
+
+    @Override
+    public String getName() {
+        return launder(() -> method.getName(classFile.constant_pool));
+    }
+
+    @Override
+    public String getDesc() {
+        return launder(() -> method.descriptor.getValue(classFile.constant_pool));
+    }
+
+    @Override
+    public int getAccess() {
+        return method.access_flags.flags;
+    }
+
+    @Override
+    public int getMaxStack() {
+        return code.max_stack;
+    }
+
+    @Override
+    public int getMaxLocals() {
+        return code.max_locals;
+    }
+
+    @Override
+    public byte[] getBytecode() {
+        return bytecode;
+    }
+
+    @Override
+    public Insn getInsn(int bci) {
+        return insns[bci];
+    }
+
+    @Override
+    public int findExceptionHandler(int bci, Predicate<String> typeMatcher) {
+        if (code.exception_table_length == 0)
+            return 0;
+        else
+            return Stream.of(code.exception_table)
+                         .filter(ete -> ete.start_pc <= bci && bci < ete.end_pc)
+                         .filter(ete -> typeMatcher.test(classModel.cpClass(ete.catch_type)))
+                         .mapToInt(ete -> ete.handler_pc - bci)
+                         .findFirst()
+                         .orElse(0);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/Util.java	Thu Jun 09 11:04:48 2016 -0400
@@ -0,0 +1,82 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.sun.tools.classfile.ConstantPoolException;
+
+/**
+ * Util
+ *
+ * @author Brian Goetz
+ */
+public class Util {
+    public static int read8u(byte[] bytes, int offset) {
+        return bytes[offset] & 0xFF;
+    }
+
+    public static int read8s(byte[] bytes, int offset) {
+        return bytes[offset];
+    }
+
+    public static int read16u(byte[] bytes, int offset) {
+        return read8s(bytes, offset) << 8 | read8u(bytes, offset + 1);
+    }
+
+    public static int read16s(byte[] bytes, int offset) {
+        return bytes[offset] << 8 | read8u(bytes, offset + 1);
+    }
+
+    public static int read32s(byte[] bytes, int offset) {
+        return bytes[offset] << 24 | read8u(bytes, offset + 1) << 16 | read8u(bytes, offset + 2) << 8 | read8u(bytes, offset + 3);
+    }
+
+    interface CpExpr<T> {
+        T get() throws ConstantPoolException;
+    }
+
+    static <T> T launder(CpExpr<T> expr) {
+        try {
+            return expr.get();
+        }
+        catch (ConstantPoolException e) {
+            throw new InterpreterError(e);
+        }
+    }
+
+    static byte[] readFully(InputStream is) throws IOException {
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            byte[] buf = new byte[8192];
+            int count;
+            while ((count = is.read(buf)) != -1) {
+                baos.write(buf, 0, count);
+            }
+            return baos.toByteArray();
+        }
+    }
+}
--- a/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper1.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper1.java	Thu Jun 09 11:04:48 2016 -0400
@@ -40,8 +40,9 @@
     public byte b;
     public boolean z;
     public Object o;
+    public Class clazz;
 
-    public InterpreterTestHelper1(int i, long l, char c, float f, double d, short s, byte b, boolean z, Object o) {
+    public InterpreterTestHelper1(int i, long l, char c, float f, double d, short s, byte b, boolean z, Object o, Class clazz) {
         this.i = i;
         this.l = l;
         this.c = c;
@@ -51,6 +52,7 @@
         this.b = b;
         this.z = z;
         this.o = o;
+        this.clazz = clazz;
     }
 
     public int instanceMethod() {
@@ -67,6 +69,7 @@
         if (instance.b != 99) throw new AssertionError();
         if (!instance.z) throw new AssertionError();
         if (!instance.o.equals("blarg")) throw new AssertionError();
+        if (!instance.clazz.equals(String.class)) throw new AssertionError();
         if (instance.instanceMethod() != 1) throw new AssertionError();
         if (!(instance instanceof InterpreterTestHelper1)) throw new AssertionError();
         if (!(instance instanceof Object)) throw new AssertionError();
@@ -116,7 +119,7 @@
         return h;
     }
 
-    public static InterpreterTestHelper1 make(int i, long l, char c, float f, double d, short s, byte b, boolean z, Object o) {
-        return new InterpreterTestHelper1(i, l, c, f, d, s, b, z, o);
+    public static InterpreterTestHelper1 make(int i, long l, char c, float f, double d, short s, byte b, boolean z, Object o, Class clazz) {
+        return new InterpreterTestHelper1(i, l, c, f, d, s, b, z, o, clazz);
     }
 }
--- a/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper3.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper3.java	Thu Jun 09 11:04:48 2016 -0400
@@ -23,6 +23,8 @@
  * questions.
  */
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Supplier;
 
 /**
@@ -31,6 +33,8 @@
  * @author Brian Goetz
  */
 class InterpreterTestHelper3 {
+    String x = "wooga";
+
     static String testSaysHello() {
         Supplier<String> ss = () -> "hello";
         return ss.get();
@@ -41,6 +45,15 @@
         return ss.get();
     }
 
+    static String instanceLambda() {
+        return new InterpreterTestHelper3().instanceLambdaHelper();
+    }
+
+    private String instanceLambdaHelper() {
+        Supplier<String> ss = () -> x;
+        return ss.get();
+    }
+
     static int[] intArray(int n) {
         int[] arr = new int[n];
         for (int i=0; i<n; i++)
@@ -63,6 +76,16 @@
         return arr;
     }
 
+    static void testCast() {
+        Object o = new ArrayList();
+        List asList = (List) o;
+        ArrayList asArrayList = (ArrayList) o;
+        if (!(o instanceof List))
+            throw new AssertionError();
+        if (!(o instanceof ArrayList))
+            throw new AssertionError();
+    }
+
     static String testTableSwitch(int i) {
         switch (i) {
             case -1: return "minus one";
--- a/interpreter/test/valhalla/interpreter/InterpretBootclassTest.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/test/valhalla/interpreter/InterpretBootclassTest.java	Thu Jun 09 11:04:48 2016 -0400
@@ -24,6 +24,7 @@
  */
 package valhalla.interpreter;
 
+import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
@@ -48,7 +49,7 @@
 
     @BeforeMethod
     public void setUp() {
-        interpreter = new Interpreter("out/test/test-helpers");
+        interpreter = new StandardInterpreter("out/test/test-helpers");
         interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
         // interpreter.setEventListener(System.err::println);
     }
@@ -89,8 +90,31 @@
                        .map(e -> e.name)
                        .findAny()
                        .orElseThrow(() -> new AssertionError("Didn't find EXECUTE AbstractList.<init>"));
+    }
 
-        for (InterpreterEvent event : interpreter.log) {
-        }
+    public void testAbstractList() throws Throwable {
+        interpreter.addBootclass(ArrayList.class);
+        interpreter.addBootclass(AbstractList.class);
+
+        Object o = interpreter.invokestatic("valhalla/interpreter/ArrayListTestHelper", "main", "()Ljava/lang/Object;");
+        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>"));
     }
 }
--- a/interpreter/test/valhalla/interpreter/InterpreterTest.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/test/valhalla/interpreter/InterpreterTest.java	Thu Jun 09 11:04:48 2016 -0400
@@ -52,7 +52,7 @@
 
     @BeforeMethod
     public void setUp() {
-        interpreter = new Interpreter("out/test/test-helpers");
+        interpreter = new StandardInterpreter("out/test/test-helpers");
         // interpreter.setEventListener(e -> System.err.println(e));
         interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
     }
@@ -103,8 +103,8 @@
         assertEquals(o, interpreter.resolveClass(HELPER_1).getRepresentationClass());
 
         Object instance =
-                interpreter.invokestatic(HELPER_1, "make", "(IJCFDSBZLjava/lang/Object;)L" + HELPER_1 + ";",
-                                         (int) 3, (long) Long.MAX_VALUE, (char) 'a', 3.14f, 9.9999999999999D, (short) 9999, (byte) 99, true, "blarg");
+                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);
         Class<?> ic = instance.getClass();
         Field f;
         f = ic.getDeclaredField("i"); f.setAccessible(true);
@@ -125,6 +125,8 @@
         assertEquals(f.get(instance), true);
         f = ic.getDeclaredField("o"); f.setAccessible(true);
         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);
     }
@@ -132,13 +134,27 @@
     public void testLambda() throws Throwable {
         assertEquals(interpreter.invokestatic(HELPER_3, "testSaysHello", "()Ljava/lang/String;"), "hello");
         assertEquals(interpreter.invokestatic(HELPER_3, "testSaysHelloThere", "(Ljava/lang/String;)Ljava/lang/String;", "there"), "hellothere");
+
+        // @@@ Invokespecial MH lookup fails
+        // assertEquals(interpreter.invokestatic(HELPER_3, "instanceLambda", "()Ljava/lang/String;"), "wooga");
     }
 
     public void testSwitch() throws Throwable {
+        assertEquals(interpreter.invokestatic(HELPER_3, "testTableSwitch", "(I)Ljava/lang/String;", -2), "something funny");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testTableSwitch", "(I)Ljava/lang/String;", -1), "minus one");
         assertEquals(interpreter.invokestatic(HELPER_3, "testTableSwitch", "(I)Ljava/lang/String;", 1), "one");
         assertEquals(interpreter.invokestatic(HELPER_3, "testTableSwitch", "(I)Ljava/lang/String;", 5), "five");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testTableSwitch", "(I)Ljava/lang/String;", 19), "something funny");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testLookupSwitch", "(I)Ljava/lang/String;", 0), "minus infinity");
         assertEquals(interpreter.invokestatic(HELPER_3, "testLookupSwitch", "(I)Ljava/lang/String;", 1), "zero");
         assertEquals(interpreter.invokestatic(HELPER_3, "testLookupSwitch", "(I)Ljava/lang/String;", 1000), "three");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testLookupSwitch", "(I)Ljava/lang/String;", 100000), "five");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testLookupSwitch", "(I)Ljava/lang/String;", 999), "something funny");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testLookupSwitch", "(I)Ljava/lang/String;", Integer.MAX_VALUE), "something funny");
+    }
+
+    public void testCast() throws Throwable {
+        interpreter.invokestatic(HELPER_3, "testCast", "()V");
     }
 
     public void testArray() throws Throwable {
--- a/interpreter/test/valhalla/interpreter/ResolveTest.java	Mon Jun 06 17:13:49 2016 -0400
+++ b/interpreter/test/valhalla/interpreter/ResolveTest.java	Thu Jun 09 11:04:48 2016 -0400
@@ -45,7 +45,7 @@
 @Test
 public class ResolveTest {
     public void testResolve() throws Throwable {
-        Interpreter interpreter = new Interpreter();
+        Interpreter interpreter = new StandardInterpreter();
 
         MethodHandles.Lookup lookup = MethodHandles.lookup();
         // ArrayList.size declared in and resolves to ArrayList.size
@@ -65,7 +65,7 @@
     }
 
     private void assertDeclaredIn(MethodHandle mh, Class<?> clazz) {
-        MethodHandleInfo mhi = Interpreter.Internal.crackMethodHandle(mh);
+        MethodHandleInfo mhi = InternalHelpers.crackMethodHandle(mh);
         assertEquals(mhi.getDeclaringClass(), clazz);
     }
 }