changeset 14072:00a9cf4de5b4

Merge
author dsimms
date Tue, 21 Jun 2016 12:43:44 +0200
parents 2800c266d243 7e5201fad2a2
children c92f241d60b6
files interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper5.java
diffstat 76 files changed, 5567 insertions(+), 873 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/.idea/ant.xml	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AntConfiguration">
+    <buildFile url="file://$PROJECT_DIR$/build.xml" />
+  </component>
+</project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/build.xml	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<project name="interpreter" default="dist" basedir=".">
+
+    <property name="common" value="test-common"/>
+    <property name="helpers" value="test-helpers"/>
+
+    <property name="build.dir" value="out"/>
+    <property name="interpreter.src" value="src"/>
+    <property name="interpreter.jar" value="interpreter.jar"/>
+    <property name="common.src" value="${common}/src"/>
+    <property name="interpreter.test" value="test"/>
+    <property name="helpers.test" value="${helpers}/test"/>
+    <property name="interpreter.classes" value="${build.dir}/production/interpreter"/>
+    <property name="common.classes" value="${build.dir}/production/test-common"/>
+    <property name="interpreter.test.classes" value="${build.dir}/test/interpreter"/>
+    <property name="helpers.test.classes" value="${build.dir}/test/test-helpers"/>
+    <property name="dist" value="dist"/>
+    <property name="testng.jar" value="testng-6.9.10.jar" />
+    <property name="jcommander.jar" value="jcommander-1.48.jar" />
+    <property name="tools.jar" value="${java.home}/../lib/tools.jar" />
+
+    <taskdef resource="testngtasks" classpath="${testng.jar}"/>
+
+    <path id="test.compile.path">
+        <pathelement path="${interpreter.classes}" />
+        <pathelement path="${common.classes}" />
+        <pathelement path="${testng.jar}" />
+        <pathelement path="${jcommander.jar}" />
+    </path>
+
+    <path id="test.class.path">
+        <path refid="test.compile.path"/>
+        <pathelement path="${interpreter.test.classes}"/>
+        <pathelement path="${helpers.test.classes}"/>
+        <pathelement path="${tools.jar}"/>
+    </path>
+
+    <target name="prepare">
+        <mkdir dir="${interpreter.classes}"/>
+        <mkdir dir="${interpreter.test.classes}"/>
+        <mkdir dir="${common.classes}"/>
+        <mkdir dir="${helpers.test.classes}"/>
+    </target>
+
+    <target name="clean">
+        <delete dir="${build.dir}"/>
+        <delete dir="${interpreter.jar}"/>
+    </target>
+
+    <target name="compile" depends="prepare">
+        <javac srcdir="${interpreter.src}" destdir="${interpreter.classes}">
+            <compilerarg value="-XDignore.symbol.file"
+            />
+        </javac>
+
+        <javac srcdir="${common.src}" destdir="${common.classes}"/>
+    </target>
+
+    <target name="compile-test" depends="prepare, compile">
+        <javac srcdir="${interpreter.test}" destdir="${interpreter.test.classes}"
+               classpathref="test.compile.path"
+               includeantruntime="false" >
+            <compilerarg value="-XDignore.symbol.file" />
+        </javac>
+
+        <javac srcdir="${helpers.test}" destdir="${helpers.test.classes}"
+               classpathref="test.compile.path" />
+    </target>
+
+    <target name="test" depends="compile,compile-test">
+        <testng classpathref="test.class.path"
+                haltOnFailure="true" verbose="2"
+                usedefaultlisteners="false" listeners="org.testng.reporters.jq.Main">
+            <classfileset dir="${interpreter.test.classes}" includes="**/*.class" />
+            <jvmarg value="-ea" />
+            <jvmarg value="-esa" />
+        </testng>
+    </target>
+
+    <target name="dist" depends="compile">
+        <jar jarfile="${interpreter.jar}" basedir="${interpreter.classes}"/>
+    </target>
+
+</project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/interpreter.iml	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="false">
+    <output url="file://$MODULE_DIR$/out/production/interpreter" />
+    <output-test url="file://$MODULE_DIR$/out/test/interpreter" />
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://$APPLICATION_HOME_DIR$/plugins/testng/lib/testng.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="test-common" />
+  </component>
+</module>
\ No newline at end of file
--- a/interpreter/src/valhalla/interpreter/ClassModel.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/ClassModel.java	Tue Jun 21 12:43:44 2016 +0200
@@ -24,49 +24,76 @@
  */
 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;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("MemberDesc[%s.%s:%s]", owner, name, 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;
+        @Override
+        public String toString() {
+            return String.format("MethodHandleDesc[%s.%s:%s(%d)]", owner, name, desc, refKind);
+        }
     }
 }
--- a/interpreter/src/valhalla/interpreter/Frame.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/Frame.java	Tue Jun 21 12:43:44 2016 +0200
@@ -24,11 +24,7 @@
  */
 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 +34,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 final Lookup lookup;
+    private Frame caller;
+    private int stackTop;
+    private int pc;
+    private Insn insn;
+    private String expectedResult;
+
     enum SlotKind {
         REF(Object.class, 1, true),
         INT(Integer.class, 1, false),
@@ -49,17 +58,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,49 +88,42 @@
         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,
+                  Lookup lookup,
+                  Frame caller,
+                  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.lookup = lookup;
+        this.caller = caller;
+        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,
+                 Lookup lookup,
+                 Frame caller,
+                 ClassModel clazz,
+                 MethodModel method) {
+        this(interpreter, lookup, caller, clazz, method, method.getMaxStack(), method.getMaxLocals());
+        insn = method.getInsn(pc);
     }
 
     // For testing
-    public Frame(int maxStack, int maxLocals) {
-        this(null, null, null, null, maxStack, maxLocals);
-    }
-
-    public Frame(Interpreter interpreter, ClassNode clazz, MethodNode method) {
-        this(interpreter, clazz, method, method.instructions, method.maxStack, method.maxLocals);
+    public Frame(Lookup lookup, int maxStack, int maxLocals) {
+        this(null, lookup, null, null, null, maxStack, maxLocals);
     }
 
     // root frame
     public Frame(Lookup lookup) {
-        this(0, 0);
-        setLookup(lookup);
+        this(lookup, 0, 0);
     }
 
     private void assertValKind(SlotVal slotVal, SlotKind kind) {
@@ -305,11 +307,10 @@
     }
 
     public Object[] popArgs(String methodDesc) {
-        Type type = Type.getType(methodDesc);
-        Type[] argTypes = type.getArgumentTypes();
+        String[] argTypes = interpreter.methodArgTypes(methodDesc);
         Object[] res = new Object[argTypes.length];
         for (int i = res.length - 1; i >= 0; i--) {
-            res[i] = popBoxed(argTypes[i].getDescriptor());
+            res[i] = popBoxed(argTypes[i]);
         }
         return res;
     }
@@ -360,13 +361,12 @@
     }
 
     public void storeArgs(String methodDesc, Object[] args) {
-        Type type = Type.getType(methodDesc);
-        Type[] argTypes = type.getArgumentTypes();
+        String[] argTypes = interpreter.methodArgTypes(methodDesc);
         assert argTypes.length == args.length;
         int slot = 0;
         for (int i = 0; i < args.length; i++) {
             Object arg = args[i];
-            String typeDesc = argTypes[i].getDescriptor();
+            String typeDesc = argTypes[i];
             if (typeDesc.equals("J")) {
                 lstore(slot, (Long) arg);
                 slot += 2;
@@ -400,6 +400,7 @@
         assert(expectedResult == null);
         expectedResult = descr;
     }
+
     public void pushExpectedResult(Object result) {
         String descr = expectedResult;
         expectedResult = null;
@@ -407,57 +408,99 @@
         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 ClassLoader curClassLoader() {
+        return curClass().interpreter().classLoader;
+    }
+
+    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);
         return slot;
     }
 
+    public Frame getCaller() {
+        return caller;
+    }
+
+    public Lookup getLookup() {
+        return lookup;
+    }
+
     public void istore(int slot, int val) { locals[checkSlot(slot)] = new SlotVal(INT, val); }
     public void lstore(int slot, long val) { locals[checkSlot(slot)] = new SlotVal(LONG, val); locals[checkSlot(slot + 1)] = PAD; }
     public void fstore(int slot, float val) { istore(slot, f2iBitwise(val)); }
@@ -472,22 +515,6 @@
     public Object aload(int i) { return asRef(locals[checkSlot(i)]); }
     public ValueBox vload(int i) { return asVal(locals[checkSlot(i)]); }
 
-    public Frame getCaller() {
-        return caller;
-    }
-
-    public void setCaller(Frame caller) {
-        this.caller = caller;
-    }
-
-    public Lookup getLookup() {
-        return lookup;
-    }
-
-    public void setLookup(Lookup lookup) {
-        this.lookup = lookup;
-    }
-
     private static long d2lBitwise(double d) { return Double.doubleToRawLongBits(d); }
     private static double l2dBitwise(long l) { return Double.longBitsToDouble(l); }
     private static int f2iBitwise(float f) { return Float.floatToRawIntBits(f); }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/Insn.java	Tue Jun 21 12:43:44 2016 +0200
@@ -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	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,139 @@
+/*
+ * 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<?> defineClass(Interpreter interpreter, String name, byte[] bytes) {
+        return UNSAFE.defineClass(name, bytes, 0, bytes.length, interpreter.classLoader, null);
+    }
+
+    public static MethodHandleInfo crackMethodHandle(MethodHandle mh) {
+        MethodHandleInfo mhi = LOOKUP.revealDirect(mh);
+        try {
+            return LOOKUP.in(mhi.getDeclaringClass()).revealDirect(mh);
+        } catch (IllegalArgumentException ex) {
+            // this happens if mh is caller sensitive; FIXME: make a more direct test for mh.isCS
+            return null;
+        }
+    }
+
+    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);
+    }
+
+    // FIXME: put this as a privileged Lookup method
+    static MethodHandle convertVirtualToSpecial(MethodHandleInfo mhi, Class dynamicReceiver) {
+        try {
+            return LOOKUP.findSpecial(dynamicReceiver, mhi.getName(), mhi.getMethodType(), dynamicReceiver);
+        }
+        catch (Throwable ex) {
+            throw new InterpreterError(String.format("error converting %s to special for %s", mhi, dynamicReceiver.getName()), ex);
+        }
+    }
+
+    // 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;
+    private static final MethodHandle MH_MethodHandleNatives_linkMethodHandleConstant;
+
+    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");
+            Class<?> methodHandleNativesClass = Class.forName("java.lang.invoke.MethodHandleNatives");
+            MH_MemberName_init = LOOKUP.findConstructor(memberNameClass, MethodType.methodType(void.class, Class.class, String.class, MethodType.class, byte.class));
+            MH_MemberName_getFactory = LOOKUP.findStatic(memberNameClass, "getFactory", MethodType.methodType(memberNameFactoryClass));
+            MH_MemberNameFactory_resolveOrFail = LOOKUP.findVirtual(memberNameFactoryClass, "resolveOrFail", MethodType.methodType(memberNameClass, byte.class, memberNameClass, Class.class, Class.class));
+            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));
+            MH_MethodHandleNatives_linkMethodHandleConstant = LOOKUP.findStatic(methodHandleNativesClass, "linkMethodHandleConstant", MethodType.methodType(MethodHandle.class, Class.class, int.class, Class.class, String.class, Object.class));
+        }
+        catch (Throwable e) {
+            throw new AssertionError(e);
+        }
+    }
+}
--- a/interpreter/src/valhalla/interpreter/Interpreter.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/Interpreter.java	Tue Jun 21 12:43:44 2016 +0200
@@ -24,34 +24,34 @@
  */
 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 sun.misc.Resource;
+import sun.misc.URLClassPath;
 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.lang.reflect.Modifier;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
+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,31 +61,38 @@
  * @author Brian Goetz
  * @author John Rose
  */
-public class Interpreter {
-    static boolean TRACING = false;
+public abstract class Interpreter {
+    protected final static Pattern methodDescPattern = Pattern.compile("\\(([^\\)]*)\\)(.*)");
 
-    private final OpcodeHandler[] handlers = new OpcodeHandler[255];
+    boolean TRACING = false;
+
     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 InterpreterClassLoader 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;
 
     public final List<InterpreterEvent> log = new ArrayList<>();
 
 
-    public Interpreter(String... paths) {
-        for (String p : paths)
-            classPath.add(new File(p));
-        classLoader = new InterpreterClassLoader(this, new URL[0], Interpreter.class.getClassLoader());
+    public Interpreter(String... paths) throws IOException {
+        URL[] urls = new URL[paths.length];
+        for (int i=0; i<paths.length; i++)
+            urls[i] = new File(paths[i]).toURI().toURL();
+        classLoader = new InterpreterClassLoader(this, urls, Interpreter.class.getClassLoader());
     }
 
+    public abstract Insn recognizeInstruction(byte[] bytes, int offset);
+
+    public abstract ClassModel newClassModel(String binaryName, byte[] bytes);
+
+    public abstract String opcodeToString(int opcode);
+
+    public abstract Pattern descriptorPattern();
+
     public void setEventFilter(Set<InterpreterEvent.Kind> eventFilter) {
         this.eventFilter = eventFilter;
     }
@@ -96,292 +103,119 @@
 
     private static class InterpreterClassLoader extends URLClassLoader {
         public final Interpreter interpreter;
+        public final URLClassPath ucp;
 
         public InterpreterClassLoader(Interpreter interpreter, URL[] urls, ClassLoader parent) {
-            super(urls, parent);
+            super(new URL[0], parent); // don't let parent do any actual classloading!
             this.interpreter = interpreter;
+            this.ucp = new URLClassPath(urls);
+        }
+
+        @Override
+        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+            ClassModel cm = interpreter.tryLoad(name.replace('.', '/'));
+            if (cm != null)
+                return cm.getRepresentationClass();
+            else
+                return super.loadClass(name, resolve);
         }
     }
 
-    // Opcode handlers
-
-    {
-        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));
-        };
-
-        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;
-
-        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();
-        };
-
-        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());
-
-        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");
+    protected void trace(Frame f, String message) {
+        if (TRACING)
+            System.out.printf("%s[bci=%d]: opcode=%d (%s) %s %n",
+                              f.curMethod().getName(), f.curBCI(), f.curOpcode(), opcodeToString(f.curOpcode()),
+                              message.isEmpty() ? "" : "-- " + message);
     }
 
-    private void primMapping(int index, Class clazz, String descr) {
-        primClasses[index] = clazz;
-        primToClass.put(descr, clazz);
-        descrOf.put(clazz, descr);
-    }
+    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);
 
-    private void logEvent(InterpreterEvent e) {
-        if (eventListener != null)
-            eventListener.accept(e);
-        if (eventFilter.contains(e.kind))
-            log.add(e);
-    }
+            case CONSTANT_Class:
+                return toClass(f, f.curClass().cpClass(index));
 
-    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);
+            case CONSTANT_MethodType:
+                return MethodType.fromMethodDescriptorString(f.curClass().cpMethodType(index), f.curClassLoader());
+
+            case CONSTANT_MethodHandle:
+                ClassModel.MethodHandleDesc mh = f.curClass().cpMethodHandle(index);
+                try {
+                    switch (mh.refKind) {
+                        case REF_invokeStatic:
+                            return f.getLookup()
+                                    .findStatic(toClass(f, mh.owner), mh.name,
+                                                MethodType.fromMethodDescriptorString(mh.desc, f.curClassLoader()));
+                        case REF_invokeVirtual:
+                        case REF_invokeInterface:
+                            return f.getLookup()
+                                    .findVirtual(toClass(f, mh.owner), mh.name,
+                                                 MethodType.fromMethodDescriptorString(mh.desc, f.curClassLoader()));
+
+                        case REF_invokeSpecial:
+                            return f.getLookup()
+                                    .findSpecial(toClass(f, mh.owner), mh.name,
+                                                 MethodType.fromMethodDescriptorString(mh.desc, f.curClassLoader()),
+                                                 f.curClass().getRepresentationClass());
+                        default:
+                            throw new InterpreterError("Unknown refkind: " + mh.refKind);
+                    }
+                }
+                catch (NoSuchMethodException | IllegalAccessException e) {
+                    throw new InterpreterError(f, "lookup error on " + mh, e);
+                }
+
+            default:
+                throw new IllegalArgumentException("Unknown constant tag: " + tag);
         }
     }
 
-    private Class<?> toPrimClass(int basicType) throws InterpreterError {
-        return Objects.requireNonNull(primClasses[basicType]);
+    protected boolean validTypeDescriptor(String desc) {
+        return descriptorPattern().matcher(desc).matches();
     }
 
-    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);
-        }
+    protected String methodReturnType(String methodDesc) {
+        Matcher matcher = methodDescPattern.matcher(methodDesc);
+        if (!matcher.matches())
+            throw new InterpreterError("Error parsing method descriptor " + methodDesc);
+        String ret = matcher.group(2);
+        if (!descriptorPattern().matcher(ret).matches())
+            throw new InterpreterError(String.format("Error parsing method descriptor; %s is not a valid descriptor", ret));
+        return ret;
+
     }
 
-    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);
+    protected String[] methodArgTypes(String methodDesc) {
+        Matcher matcher = methodDescPattern.matcher(methodDesc);
+        if (!matcher.matches())
+            throw new InterpreterError("Error parsing method descriptor " + methodDesc);
+        String args = matcher.group(1);
+        ArrayList<String> list = new ArrayList<>();
+        int start = 0;
+        while (start < args.length()) {
+            Matcher mm = descriptorPattern().matcher(args);
+            if (!mm.find(start) || mm.start() != start)
+                throw new InterpreterError("Error parsing method descriptor " + methodDesc);
+            list.add(args.substring(start, mm.end()));
+            start = mm.end();
+        }
+        return list.toArray(new String[list.size()]);
     }
 
-    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);
+    protected Class<?> toPrimClass(int basicType) throws InterpreterError {
+        return Objects.requireNonNull(primClasses[basicType]);
     }
 
     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,22 +223,12 @@
         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());
+        f.expectResult(methodReturnType(type.toMethodDescriptorString()));
         HandlerAction action = interpretOrExecute(f, resolved, args);
         return takeRetOrException(f, action);
     }
@@ -420,33 +244,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,8 +266,8 @@
 
         String debugInfo = "";
         try {
-            mhi = Internal.crackMethodHandle(resolved);  // privileged cracking to get bytecodes
-            switch (mhi.getReferenceKind()) {
+            mhi = InternalHelpers.crackMethodHandle(resolved);  // privileged cracking to get bytecodes
+            switch (mhi == null ? -1 : mhi.getReferenceKind()) {
                 case H_INVOKESTATIC:
                 case H_INVOKESPECIAL:
                     interpretable = true;
@@ -479,7 +276,13 @@
                 case H_INVOKEVIRTUAL:
                 case H_INVOKEINTERFACE:
                     // do a dispatch to find out where the bytecodes really are
-                    selected = doMethodSelection(mhi, args[0].getClass());
+                    selected = tryMethodSelection(mhi, args[0].getClass());
+                    if (selected != null) {
+                        mhi = InternalHelpers.crackMethodHandle(selected);
+                        if (mhi == null) { selected = null; break; }
+                        // method selection should not select an abstract method
+                        assert((mhi.getModifiers() & Opcodes.ACC_ABSTRACT) == 0);
+                    }
                     if (args[0].getClass().getName().contains("/"))
                         altOwner = args[0].getClass().getName();
                     interpretable = true;
@@ -487,22 +290,27 @@
 
                 default:
                     // do not crack this kind of MH; it has no bytecodes
-                    debugInfo = mhi.toString();
+                    if (mhi != null)
+                        debugInfo = mhi.toString();
                     mhi = null;
                     break;
             }
         } catch (IllegalArgumentException ex) {
             // this can happen if selected MH if the target of an indy call site
         }
-        MethodHandle target = (selected != null) ? selected : resolved;
+        MethodHandle target = ((selected != null) ? selected : resolved).asFixedArity();
 
         if (interpretable) {
-            ClassModel cm = classes.get(mhi.getDeclaringClass().getName());
+            ClassModel cm = tryLoad(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,36 +328,149 @@
         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);
-        if (f == null)
-            return null;
-        f.setCaller(caller);
-        f.setLookup(lookup);
-        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());
+    private Frame makeNewFrame(Frame caller, ClassModel cm, Lookup lookup, boolean isStatic, String methodName, String methodDesc) {
+        return cm.methods()
+                 .filter(m -> m.getName().equals(methodName)
+                              && m.getDesc().equals(methodDesc)
+                              && ((m.getAccess() & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0)
+                              && ((isStatic && (m.getAccess() & Opcodes.ACC_STATIC) != 0)
+                                  || (!isStatic && (m.getAccess() & Opcodes.ACC_STATIC) == 0)))
+                 .findFirst()
+                 .map(mm -> new Frame(this, lookup, caller, cm, mm))
+                 .orElse(null);
     }
 
     // Helper methods for opcode handlers
 
-    private HandlerAction newarray(Frame f, Class<?> elementClass, int... lens) {
+    protected HandlerAction fieldOp(Frame f) throws InterpreterError {
+        ClassModel.MemberDesc ref = f.readMemberRef(1);
+        Class type = MethodType.fromMethodDescriptorString("()" + ref.desc, f.curClassLoader()).returnType();
+
+        // Special handling for writes to own fields from ctor/static initializer: use reflection instead of MHI
+        if (((f.curOpcode() == PUTSTATIC && f.curMethod().getName().equals("<clinit>"))
+            || (f.curOpcode() == PUTFIELD && f.curMethod().getName().equals("<init>")))
+            && ref.owner.equals(f.curClass().getName())) {
+            try {
+                trace(f, "special field write handling for " + ref.name);
+                Field field = finalFieldSetter(f.curClass().getRepresentationClass(), ref.name);
+                if (f.curOpcode() == PUTSTATIC)
+                    field.set(null, f.popBoxed(ref.desc));
+                else {
+                    Object val = f.popBoxed(ref.desc);
+                    Object receiver = f.apop();
+                    field.set(receiver, val);
+                }
+                return next();
+            }
+            catch (ReflectiveOperationException e) {
+                if (TRACING)
+                    System.out.printf("Exception in field lookup: " + e);
+                // fall through to standard processing
+            }
+        }
+        return memberOp(f, toClass(f, ref.owner), ref.name, type);
+    }
+
+    private Field finalFieldSetter(Class<?> clazz, String name) throws ReflectiveOperationException {
+        Field field = clazz.getDeclaredField(name);
+        field.setAccessible(true);
+        Field modifiersField = Field.class.getDeclaredField("modifiers");
+        modifiersField.setAccessible(true);
+        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+        return field;
+    }
+
+    protected HandlerAction invokeOp(Frame f) throws InterpreterError {
+        ClassModel.MemberDesc ref = f.readMemberRef(1);
+        MethodType methodType = MethodType.fromMethodDescriptorString(ref.desc, f.curClassLoader());
+        return memberOp(f, toClass(f, ref.owner), ref.name, methodType);
+    }
+
+    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, f.curClassLoader())));
+        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, f.curClassLoader());
+            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 (AssertionError|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 {
+        // If the class is (or can be made to be) known to the interpreter, use the proxy class
+        ClassModel cm = tryLoad(name);
+        if (cm != null)
+            return cm.getRepresentationClass();
+
+        // 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, "cannot find class " + name, 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 +478,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 +495,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 +531,7 @@
         return action;
     }
 
-    public OpcodeHandler exec(Consumer<Frame> c) {
+    protected OpcodeHandler exec(Consumer<Frame> c) {
         return f -> { c.accept(f); return HandlerAction.NEXT; };
     }
 
@@ -655,21 +572,15 @@
                                   Class owner,
                                   String name,
                                   Object type)
-            throws LinkageError
-    {
-        switch (opcode) {
-            case INVOKEVIRTUAL:
-                if (owner.isInterface())  // corner case for List.hashCode (ugly!)
-                    opcode = INVOKEINTERFACE;
-                break;
-
-            case INVOKESPECIAL:
-                if (name.equals("<init>"))
-                    return Internal.findSpecialConstructor(owner, (MethodType) type);
-                break;
-        }
+            throws LinkageError {
+        if (opcode == INVOKEVIRTUAL && owner.isInterface())  // corner case for List.hashCode (ugly!)
+            opcode = INVOKEINTERFACE;
+        if (opcode == INVOKEVIRTUAL && owner.isArray())  // workaround for bugs with lookup to T[].clone, etc.
+            owner = Object[].class;
         try {
-            return Internal.linkMethodHandleConstant(lookup.lookupClass(), opcodeToRefKind(opcode), owner, name, type);
+            return opcode == INVOKESPECIAL && name.equals("<init>")
+                   ? InternalHelpers.findSpecialConstructor(owner, (MethodType) type)
+                   : InternalHelpers.linkMethodHandleConstant(lookup.lookupClass(), opcodeToRefKind(opcode), owner, name, type);
         } catch (Error ex) {
             throw ex;
         } catch (Throwable ex) {
@@ -678,11 +589,11 @@
     }
 
     MethodHandle doMethodSelection(MethodHandle mh, Class dynamicReceiver) throws InterpreterError {
-        MethodHandleInfo mhi = Internal.crackMethodHandle(mh);
-        MethodHandle res = doMethodSelection(mhi, dynamicReceiver);
+        MethodHandle res = tryMethodSelection(InternalHelpers.crackMethodHandle(mh), dynamicReceiver);
         return (res != null) ? res : mh;
     }
-    private MethodHandle doMethodSelection(MethodHandleInfo mhi, Class dynamicReceiver) throws InterpreterError {
+
+    private MethodHandle tryMethodSelection(MethodHandleInfo mhi, Class dynamicReceiver) throws InterpreterError {
         switch (mhi.getReferenceKind()) {
             case H_INVOKEVIRTUAL:
             case H_INVOKEINTERFACE:
@@ -692,27 +603,27 @@
         }
         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();
-                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);
+                Insn insn = f.curInsn();
+                int opcode = insn.getOpcode();
+                OpcodeHandler handler = insn.getHandler();
+                if (handler == null)
+                    throw new InterpreterError(f, "No handler for opcode " + insn);
+                trace(f, "");
+
+                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,17 +631,15 @@
 
                     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
+                        trace(f, String.format("throwing %s", a.returnValue));
+
                         return a;
                 }
             }
@@ -740,159 +649,46 @@
         }
     }
 
-    // This is the part the reaches into the internal parts of the JVM.
-    static class Internal {
-        private Internal() {} // all static
+    // Public interpreter API
 
-        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 ClassModel tryLoad(String binaryName) {
+        String humanName = binaryName.replace('/', '.');
+        ClassModel model = systemDictionary.get(humanName);
+        if (model != null)
+            return model;
+
+        Resource r = classLoader.ucp.getResource(binaryName + ".class");
+        if (r == null)
+            return null;
+
+        try {
+            ClassModel classModel = newClassModel(binaryName, r.getBytes());
+            systemDictionary.put(humanName, classModel);
+            logEvent(InterpreterEvent.load(humanName));
+            // Run <clinit> by forced interpretation
+            classModel.methods()
+                      .filter(m -> m.getName().equals("<clinit>") && m.getDesc().equals("()V"))
+                      .findFirst()
+                      .ifPresent(m -> forceInterpret(INVOKESTATIC, binaryName, "<clinit>", "()V") );
+
+            return classModel;
         }
-
-        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);
-            }
+        catch (IOException e) {
+            return null;
         }
     }
 
-    // Public interpreter API
-
-    public ClassModel resolveClass(String className) {
-        ClassModel model = classes.get(className.replace('/', '.'));
+    public ClassModel resolveClass(String binaryName) {
+        ClassModel model = tryLoad(binaryName);
         if (model != null)
             return model;
 
-        try {
-            for (File dir : classPath) {
-                File f = new File(dir, className + ".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));
-                    // Run <clinit> by forced interpretation
-                    cn.methods.stream()
-                              .filter(m -> m.name.equals("<clinit>") && m.desc.equals("()V"))
-                              .findFirst()
-                              .ifPresent(m -> forceInterpret(INVOKESTATIC, className, "<clinit>", "()V") );
-
-                    return classModel;
-                }
-            }
-            throw new InterpreterError("Cannot resolve class " + className);
-        }
-        catch (IOException e) {
-            throw new InterpreterError("Cannot resolve class " + className, e);
-        }
+        throw new InterpreterError("Cannot resolve class " + binaryName);
     }
 
-    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 +698,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, classLoader));
         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 +721,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	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/InterpreterError.java	Tue Jun 21 12:43:44 2016 +0200
@@ -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;
+        }
     }
 }
--- a/interpreter/src/valhalla/interpreter/InterpreterEvent.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/InterpreterEvent.java	Tue Jun 21 12:43:44 2016 +0200
@@ -36,7 +36,7 @@
  */
 public class InterpreterEvent {
     public enum Kind { LOAD, INTERPRET, EXECUTE }
-    private static final Pattern pattern = Pattern.compile("(\\w+) ([a-zA-Z0-9_.]+):(.*)");
+    private static final Pattern pattern = Pattern.compile("(\\w+) ([a-zA-Z0-9$_.]+):(.*)");
 
     public final Kind kind;
     public final String clazz;
@@ -78,7 +78,7 @@
     @Override
     public String toString() {
         if (kind == LOAD)
-            return String.format("LOAD:%s", name);
+            return String.format("LOAD:%s", clazz);
         else
             return String.format("%s:%s.%s%s", kind, clazz, name, desc);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/Main.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,65 @@
+/*
+ * 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.File;
+
+/**
+ * Main
+ *
+ * @author Brian Goetz
+ */
+public class Main {
+    public static void main(String[] args) throws Throwable {
+        String mainClass = null;
+        String[] classPath = new String[0];
+        String[] programArgs = null;
+        boolean trace = false;
+
+        for (int i=0; i<args.length; i++) {
+            if (args[i].equals("-cp"))
+                classPath = args[++i].split(File.pathSeparator);
+            else if (args[i].equals("-trace"))
+                trace = true;
+            else {
+                mainClass = args[i];
+                programArgs = new String[args.length - i - 1];
+                System.arraycopy(args, i+1, programArgs, 0, programArgs.length);
+                break;
+            }
+        }
+
+        if (mainClass == null) {
+            System.err.println("Usage: Main [ -trace ] [ -cp path ] main-class program-args...");
+            System.exit(0);
+        }
+
+        Interpreter interpreter = new StandardInterpreter(classPath);
+        interpreter.TRACING = trace;
+        if (trace)
+            interpreter.setEventListener(System.err::println);
+        interpreter.invokestatic(mainClass.replace('.', '/'), "main", "([Ljava/lang/String;)V", (Object) programArgs);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/MethodModel.java	Tue Jun 21 12:43:44 2016 +0200
@@ -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	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/OpcodeHandler.java	Tue Jun 21 12:43:44 2016 +0200
@@ -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,30 +71,34 @@
         }
 
         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 {
                 res = fn.runOrThrow();
-            } catch (InterpreterError ex) {
+            } catch (AssertionError|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	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/ProxyClassBuilder.java	Tue Jun 21 12:43:44 2016 +0200
@@ -24,6 +24,10 @@
  */
 package valhalla.interpreter;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 import jdk.internal.org.objectweb.asm.ClassWriter;
 import jdk.internal.org.objectweb.asm.MethodVisitor;
 import jdk.internal.org.objectweb.asm.Opcodes;
@@ -33,6 +37,7 @@
 
 import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
 import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
+import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
 
@@ -55,6 +60,7 @@
 public class ProxyClassBuilder {
     private static final String INTERPRETER_CLASS = "valhalla/interpreter/Interpreter";
     private static final String CALLBACK_DESC = "(Ljava/lang/Class;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;";
+    public static final String INIT_NAME = "<init>";
     public static final String CLINIT_NAME = "<clinit>";
     public static final String FAKE_INIT_NAME = "__clinit__";
     public static final String CLINIT_DESC = "()V";
@@ -62,12 +68,16 @@
     public final String className;
     private final ClassWriter cw;
     private final Interpreter interpreter;
+    private final List<String> loadDependencies = new ArrayList<>();
 
     private ProxyClassBuilder(Interpreter interpreter, int access, String className, String superClass, String[] interfaces) {
         this.interpreter = interpreter;
         this.className = className;
         this.cw = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
         cw.visit(52, access, className, null, superClass, interfaces);
+        loadDependencies.add(superClass);
+        if (interfaces != null)
+            Collections.addAll(loadDependencies, interfaces);
     }
 
     public static ProxyClassBuilder make(Interpreter interpreter, int access, String name, String superClass, String[] interfaces) {
@@ -84,7 +94,28 @@
         if (methodName.equals(CLINIT_NAME) && desc.equals(CLINIT_DESC))
             return this;
 
+        // Make constructors always throw; this is unfortunate, because it means that
+        // we can't instantiate intepreted classes reflectively from native code, but
+        // otherwise the verifier balks at our translation strategy
+        if (methodName.equals(INIT_NAME)) {
+            MethodVisitor mv = cw.visitMethod(access, methodName, desc, null, null);
+            GeneratorAdapter ga = new GeneratorAdapter(mv, access, methodName, desc);
+            ga.newInstance(Type.getObjectType("java/lang/UnsupportedOperationException"));
+            ga.dup();
+            ga.invokeConstructor(Type.getObjectType("java/lang/UnsupportedOperationException"), new Method(INIT_NAME, "()V"));
+            ga.throwException();
+            ga.returnValue();
+            ga.endMethod();
+            return this;
+        }
+
         MethodVisitor mv = cw.visitMethod(access, methodName, desc, null, null);
+        if ((access & ACC_ABSTRACT) != 0) {
+            // Abstract methods get no code attribute
+            mv.visitEnd();
+            return this;
+        }
+
         Type methodType = Type.getMethodType(desc);
         GeneratorAdapter ga = new GeneratorAdapter(mv, access, methodName, desc);
         mv.visitCode();
@@ -117,6 +148,11 @@
         }
 
         ga.invokeStatic(Type.getObjectType(INTERPRETER_CLASS), new Method("callback", CALLBACK_DESC));
+        if (methodType.getReturnType().getSort() == Type.OBJECT || methodType.getReturnType().getSort() == Type.ARRAY)
+            ga.checkCast(methodType.getReturnType());
+        else if (methodType.getReturnType().getSort() != Type.VOID) {
+            ga.unbox(methodType.getReturnType());
+        }
         ga.returnValue();
         ga.endMethod();
 
@@ -127,6 +163,9 @@
         cw.visitEnd();
         byte[] bytes = cw.toByteArray();
 //        new ClassReader(bytes).accept(new TraceClassVisitor(new PrintWriter(System.out)), 0);
-        return Interpreter.Internal.loadClass(interpreter, className, bytes);
+        for (String s : loadDependencies) {
+            ClassModel model = interpreter.tryLoad(s);
+        }
+        return InternalHelpers.defineClass(interpreter, className, bytes);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/StandardClassModel.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,214 @@
+/*
+ * 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 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;
+        ProxyClassBuilder builder = 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 -> builder.addField(f.access_flags.flags,
+                                             launder(() -> f.getName(classFile.constant_pool)),
+                                             launder(() -> f.descriptor.getValue(classFile.constant_pool))));
+        Stream.of(classFile.methods)
+              .forEach(m -> builder.addMethod(m.access_flags.flags,
+                                              launder(() -> m.getName(classFile.constant_pool)),
+                                              launder(() -> m.descriptor.getValue(classFile.constant_pool))));
+        model.representationClass = builder.build();
+        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() {
+        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	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package valhalla.interpreter;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+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 static final Pattern DESCRIPTOR_PATTERN = Pattern.compile("\\[*(I|J|D|F|C|S|B|Z|V|L[^;]*;)");
+
+    private final OpcodeHandler[] handlers = new OpcodeHandler[255];
+    private final int[] opcodeLen = new int[255];
+
+    public StandardInterpreter(String... paths) throws IOException {
+        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);
+    }
+
+    @Override
+    public String opcodeToString(int opcode) {
+        return Opcode.get(opcode).name();
+    }
+
+    @Override
+    public Pattern descriptorPattern() {
+        return DESCRIPTOR_PATTERN;
+    }
+
+    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	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,118 @@
+/*
+ * 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 Method method;
+    private final Code_attribute code;
+    private final byte[] bytecode;
+    private final Insn[] insns;
+    private final String name;
+    private final String desc;
+
+    public StandardMethodModel(StandardClassModel classModel, ClassFile classFile, Method method) {
+        this.classModel = classModel;
+        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];
+        this.name = launder(() -> method.getName(classFile.constant_pool));
+        this.desc = launder(() -> method.descriptor.getValue(classFile.constant_pool));
+
+        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 name;
+    }
+
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+
+    @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	Tue Jun 21 12:43:44 2016 +0200
@@ -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-common/test-common.iml	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test-common/test-common.iml	Tue Jun 21 12:43:44 2016 +0200
@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <module type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
+  <component name="NewModuleRootManager" inherit-compiler-output="false">
+    <output url="file://$MODULE_DIR$/../out/production/test-common" />
+    <output-test url="file://$MODULE_DIR$/../out/test/test-common" />
     <exclude-output />
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
--- a/interpreter/test-helpers/test-helpers.iml	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test-helpers/test-helpers.iml	Tue Jun 21 12:43:44 2016 +0200
@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <module type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
+  <component name="NewModuleRootManager" inherit-compiler-output="false">
+    <output url="file://$MODULE_DIR$/../out/production/test-helpers" />
+    <output-test url="file://$MODULE_DIR$/../out/test/test-helpers" />
     <exclude-output />
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="true" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/AnnoHelper.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,40 @@
+/*
+ * 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.annotation.Annotation;
+import java.lang.annotation.Repeatable;
+import java.lang.reflect.Method;
+
+/**
+ * AnnoHelper
+ *
+ * @author Brian Goetz
+ */
+public class AnnoHelper {
+    public static void testAnno() throws NoSuchMethodException, ClassNotFoundException {
+        Repeatable.class.getMethod("value");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/ConstructorTestHelper.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package valhalla.interpreter;
+
+/**
+ * ConstructorTestHelper
+ *
+ * @author Brian Goetz
+ */
+public class ConstructorTestHelper {
+    static class CTH {
+        public int x;
+        public final int y;
+
+        public CTH(int x, int y) {
+            this.x = x;
+            this.y = y;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public int getY() {
+            return y;
+        }
+    }
+
+    public static Object testConstructor(int x, int y) {
+        CTH cth = new CTH(x, y);
+        if (cth.x != x || cth.y != y)
+            throw new AssertionError("constructor error");
+        return cth;
+    }
+
+    public static Object[] testArray(int x, int y) {
+        CTH cth = new CTH(x, y);
+        CTH[] array = new CTH[] { cth } ;
+        return array;
+    }
+
+    public static Object[] testArrayClone(int x, int y) {
+        CTH cth = new CTH(x, y);
+        CTH[] array = new CTH[] { cth } ;
+        return array.clone();
+    }
+}
--- a/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper1.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper1.java	Tue Jun 21 12:43:44 2016 +0200
@@ -23,7 +23,10 @@
  * questions.
  */
 
+import java.util.EnumSet;
 import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
  * InterpreterTestHelper1
@@ -40,8 +43,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 +55,7 @@
         this.b = b;
         this.z = z;
         this.o = o;
+        this.clazz = clazz;
     }
 
     public int instanceMethod() {
@@ -67,6 +72,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 +122,52 @@
         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);
+    }
+
+    public static int testOutboard() {
+        return InterpreterTestHelper1a.three();
+    }
+
+    public static int testInterfaceCall() {
+        AnInterface f = new AnImplementingClass();
+        return f.three();
+    }
+
+    public static void testEnum() {
+        Set<TimeUnit> allTime = EnumSet.allOf(TimeUnit.class);
+        Set<TimeUnit> noneTime = EnumSet.noneOf(TimeUnit.class);
+
+        Pets p = Pets.DOG;
+        p = Pets.CAT;
+        Set<Pets> all = EnumSet.allOf(Pets.class);
+        Set<Pets> none = EnumSet.noneOf(Pets.class);
+    }
+
+    enum Pets { DOG, CAT; }
+
+    interface AnInterface {
+        int three();
+    }
+
+    static class AnImplementingClass implements AnInterface {
+        @Override
+        public int three() {
+            return 3;
+        }
+    }
+
+    public static int testInterfaceCall2() {
+        AnInterface2 f = new AnImplementingClass2();
+        return f.four();
+    }
+
+    interface AnInterface2 {
+        default int four() { return 4; }
+    }
+
+    static class AnImplementingClass2 implements AnInterface2{
+        // keep the default four() = 4
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper1a.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * InterpreterTestHelper1a
+ *
+ * @author Brian Goetz
+ */
+public class InterpreterTestHelper1a {
+    public static int three() {
+        return 3;
+    }
+}
--- a/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper3.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper3.java	Tue Jun 21 12:43:44 2016 +0200
@@ -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,16 @@
         return ss.get();
     }
 
+    static String instanceLambda() {
+        InterpreterTestHelper3 instance = new InterpreterTestHelper3();
+        return instance.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 +77,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-helpers/test/valhalla/interpreter/InterpreterTestHelper5.java	Thu Jun 09 13:08:16 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-package valhalla.interpreter;/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/**
- * InterpreterTestHelper5
- *
- * @author Brian Goetz
- */
-class InterpreterTestHelper5 {
-    public static int x = 3;
-
-    public static int x() { return x; }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/StaticInitTestHelper.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,37 @@
+package valhalla.interpreter;/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * InterpreterTestHelper5
+ *
+ * @author Brian Goetz
+ */
+class StaticInitTestHelper {
+    public static int x = 3;
+    public static final String y = StaticInitTestHelper.class.getName();
+
+    public static int x() { return x; }
+    public static String y() { return y; }
+}
--- a/interpreter/test/valhalla/interpreter/FrameTest.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test/valhalla/interpreter/FrameTest.java	Tue Jun 21 12:43:44 2016 +0200
@@ -54,23 +54,23 @@
     }
 
     public void testPopEmpty() {
-        Frame f = new Frame(1, 0);
+        Frame f = new Frame(null, 1, 0);
         assertThrows(() -> f.pop());
     }
 
     public void testPushPopInt() {
-        Frame f1 = new Frame(1, 0);
+        Frame f1 = new Frame(null, 1, 0);
         f1.ipush(Integer.MAX_VALUE);
         assertTrue(f1.ipop() == Integer.MAX_VALUE);
         assertThrows(() -> f1.ipop());
 
-        Frame f2 = new Frame(1, 0);
+        Frame f2 = new Frame(null, 1, 0);
         f1.ipush(Integer.MAX_VALUE);
         assertThrows(() -> f1.apop());
     }
 
     public void testPushPopRef() {
-        Frame f1 = new Frame(2, 0);
+        Frame f1 = new Frame(null, 2, 0);
         f1.apush("a");
         f1.apush("b");
         assertTrue(f1.apop().equals("b"));
@@ -79,67 +79,67 @@
     }
 
     public void testPushPopLong() {
-        Frame f = new Frame(2, 0);
+        Frame f = new Frame(null, 2, 0);
         f.lpush(Long.MAX_VALUE);
         assertTrue(f.lpop() == Long.MAX_VALUE);
         assertThrows(() -> f.lpop());
 
-        Frame f2 = new Frame(1, 0);
+        Frame f2 = new Frame(null, 1, 0);
         assertThrows(() -> f2.lpush(0));
     }
 
     public void testOverflow() {
-        assertThrows(() -> new Frame(0, 0).ipush(0));
-        assertThrows(() -> new Frame(0, 0).lpush(0L));
-        assertThrows(() -> new Frame(0, 0).apush(0));
+        assertThrows(() -> new Frame(null, 0, 0).ipush(0));
+        assertThrows(() -> new Frame(null, 0, 0).lpush(0L));
+        assertThrows(() -> new Frame(null, 0, 0).apush(0));
 
-        Frame f1 = new Frame(1, 0);
+        Frame f1 = new Frame(null, 1, 0);
         f1.ipush(0);
         assertThrows(() -> f1.ipush(2));
 
-        Frame f2 = new Frame(1, 0);
+        Frame f2 = new Frame(null, 1, 0);
         f2.apush("");
         assertThrows(() -> f2.apush(""));
 
-        Frame f3 = new Frame(2, 0);
+        Frame f3 = new Frame(null, 2, 0);
         f3.lpush(3L);
         assertThrows(() -> f2.apush(""));
     }
 
     public void testIntLong() {
-        Frame f1 = new Frame(2, 0);
+        Frame f1 = new Frame(null, 2, 0);
         f1.ipush(0);
         f1.ipush(0);
         assertThrows(() -> f1.lpop());
 
-        Frame f2 = new Frame(2, 0);
+        Frame f2 = new Frame(null, 2, 0);
         f2.lpush(0);
         assertThrows(() -> f2.ipop());
     }
 
     public void testMixedPushPop() {
-        Frame f1 = new Frame(2, 0);
+        Frame f1 = new Frame(null, 2, 0);
         f1.ipush(0);
         assertThrows(() -> f1.apop());
 
-        Frame f2 = new Frame(2, 0);
+        Frame f2 = new Frame(null, 2, 0);
         f2.lpush(0);
         assertThrows(() -> f2.apop());
 
-        Frame f3 = new Frame(2, 0);
+        Frame f3 = new Frame(null, 2, 0);
         f3.apush(0);
         assertThrows(() -> f3.ipop());
     }
 
     public void testLoadStore() {
-        assertThrows(() -> new Frame(0, 1).istore(1, 1));
-        assertThrows(() -> new Frame(0, 1).istore(-1, 1));
-        assertThrows(() -> new Frame(0, 1).astore(1, 1));
-        assertThrows(() -> new Frame(0, 1).astore(-1, 1));
-        assertThrows(() -> new Frame(0, 1).lstore(1, 1));
-        assertThrows(() -> new Frame(0, 1).lstore(-1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).istore(1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).istore(-1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).astore(1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).astore(-1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).lstore(1, 1));
+        assertThrows(() -> new Frame(null, 0, 1).lstore(-1, 1));
 
-        Frame f1 = new Frame(0, 1);
+        Frame f1 = new Frame(null, 0, 1);
         f1.istore(0, 3);
         assertEquals(f1.iload(0), 3);
 
@@ -148,7 +148,7 @@
 
         assertThrows(() -> f1.lstore(0, 0L));
 
-        Frame f2 = new Frame(0, 2);
+        Frame f2 = new Frame(null, 0, 2);
         f2.lstore(0, Long.MAX_VALUE);
         assertEquals(f2.lload(0), Long.MAX_VALUE);
     }
--- a/interpreter/test/valhalla/interpreter/InterpretBootclassTest.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test/valhalla/interpreter/InterpretBootclassTest.java	Tue Jun 21 12:43:44 2016 +0200
@@ -24,18 +24,13 @@
  */
 package valhalla.interpreter;
 
+import java.util.AbstractList;
 import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
 
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import static java.util.stream.Collectors.toList;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 /**
  * InterpretBootclassTest
@@ -43,26 +38,7 @@
  * @author Brian Goetz
  */
 @Test
-public class InterpretBootclassTest {
-    Interpreter interpreter;
-
-    @BeforeMethod
-    public void setUp() {
-        interpreter = new Interpreter("out/test/test-helpers");
-        interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
-        // interpreter.setEventListener(System.err::println);
-    }
-
-    @AfterMethod
-    public void assertLog() {
-        // It's OK if the interpreter executes into field MHs, or anonymous classes
-        for (InterpreterEvent event : interpreter.log) {
-            if (event.kind == InterpreterEvent.Kind.EXECUTE && event.clazz.startsWith("valhalla.interpreter.")) {
-                if (!event.name.matches("^(putField|getField|putStatic|getStatic):.*") && !event.clazz.contains("/"))
-                    fail("Questionable execution event: " + event);
-            }
-        }
-    }
+public class InterpretBootclassTest extends InterpreterTestCase {
 
     public void testArrayList() throws Throwable {
         interpreter.addBootclass(ArrayList.class);
@@ -71,26 +47,21 @@
         assertTrue(o instanceof ArrayList);
         assertEquals("[a, b, c]", o.toString());
 
-        // Assertions against log stream
+        assertMethodInterpreted("java.util.ArrayList", "<init>");
+        assertMethodInterpreted("java.util.ArrayList", "add");
+        assertMethodInterpreted("java.util.ArrayList", "contains");
+        assertMethodExecuted("java.util.AbstractList", "<init>");
+    }
 
-        List<String> interpretedMethods
-                = interpreter.log.stream()
-                                 .filter(e -> e.kind == InterpreterEvent.Kind.INTERPRET)
-                                 .filter(e -> e.clazz.equals("java.util.ArrayList"))
-                                 .map(e -> e.name)
-                                 .collect(toList());
-        assertTrue(interpretedMethods.contains("<init>"));
-        assertTrue(interpretedMethods.contains("add"));
-        assertTrue(interpretedMethods.contains("contains"));
+    public void testAbstractList() throws Throwable {
+        interpreter.addBootclass(ArrayList.class);
+        interpreter.addBootclass(AbstractList.class);
 
-        interpreter.log.stream()
-                       .filter(e -> e.kind == InterpreterEvent.Kind.EXECUTE)
-                       .filter(e -> e.clazz.equals("java.util.AbstractList"))
-                       .map(e -> e.name)
-                       .findAny()
-                       .orElseThrow(() -> new AssertionError("Didn't find EXECUTE AbstractList.<init>"));
+        Object o = interpreter.invokestatic("valhalla/interpreter/ArrayListTestHelper", "main", "()Ljava/lang/Object;");
+        assertTrue(o instanceof ArrayList);
+        assertEquals("[a, b, c]", o.toString());
 
-        for (InterpreterEvent event : interpreter.log) {
-        }
+        assertMethodInterpreted("java.util.AbstractList", "<init>");
+        assertMethodExecuted("java.util.AbstractCollection", "<init>");
     }
 }
--- a/interpreter/test/valhalla/interpreter/InterpreterTest.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test/valhalla/interpreter/InterpreterTest.java	Tue Jun 21 12:43:44 2016 +0200
@@ -26,15 +26,11 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
-import java.util.EnumSet;
 
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 /**
  * InterpreterTest
@@ -42,39 +38,23 @@
  * @author Brian Goetz
  */
 @Test
-public class InterpreterTest {
+public class InterpreterTest extends InterpreterTestCase {
     public static final String HELPER_1 = "valhalla/interpreter/InterpreterTestHelper1";
     public static final String HELPER_3 = "valhalla/interpreter/InterpreterTestHelper3";
     public static final String HELPER_4 = "valhalla/interpreter/InterpreterTestHelper4";
-    public static final String HELPER_5 = "valhalla/interpreter/InterpreterTestHelper5";
+    public static final String STATIC_HELPER = "valhalla/interpreter/StaticInitTestHelper";
+    public static final String CONSTRUCTOR_HELPER = "valhalla/interpreter/ConstructorTestHelper";
+    public static final String ANNO_HELPER = "valhalla/interpreter/AnnoHelper";
 
-    Interpreter interpreter;
-
-    @BeforeMethod
-    public void setUp() {
-        interpreter = new Interpreter("out/test/test-helpers");
-        // interpreter.setEventListener(e -> System.err.println(e));
-        interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
+    /** Test that a static initializer actually gets run, with final and nonfinal fields */
+    public void testStaticInit() throws Throwable {
+        Object o = interpreter.invokestatic(STATIC_HELPER, "x", "()I");
+        assertEquals((int) (Integer) o, 3);
+        o = interpreter.invokestatic(STATIC_HELPER, "y", "()Ljava/lang/String;");
+        assertEquals(o, "valhalla.interpreter.StaticInitTestHelper");
     }
 
-    @AfterMethod
-    public void assertLog() {
-        // It's OK if the interpreter executes into field MHs, or anonymous classes
-        for (InterpreterEvent event : interpreter.log) {
-            if (event.kind == InterpreterEvent.Kind.EXECUTE && event.clazz.startsWith("valhalla.interpreter.")) {
-                if (!event.name.matches("^(putField|getField|putStatic|getStatic):.*") && !event.clazz.contains("/"))
-                    fail("Questionable execution event: " + event);
-            }
-        }
-    }
-
-    /** Test that a static initializer actually gets run */
-    public void testStaticInit() throws Throwable {
-        Object o = interpreter.invokestatic(HELPER_5, "x", "()I");
-        assertEquals((int) (Integer) o, 3);
-    }
-
-    public void testLoadSimple() throws Throwable {
+    public void testsStaticCalls() throws Throwable {
         Object o = interpreter.invokestatic(HELPER_1, "one", "()I");
         assertEquals((int) (Integer) o, 1);
 
@@ -101,10 +81,15 @@
 
         o = interpreter.invokestatic(HELPER_1, "thisClass", "()Ljava/lang/Class;");
         assertEquals(o, interpreter.resolveClass(HELPER_1).getRepresentationClass());
+    }
 
+    public void testFactoryMethod() throws Throwable {
         Object instance =
-                interpreter.invokestatic(HELPER_1, "make", "(IJCFDSBZLjava/lang/Object;)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);
+        interpreter.invokestatic(HELPER_1, "assertInstance", "(L" + HELPER_1 + ";)V", instance);
+
+        // Test the fields reflectively too
         Class<?> ic = instance.getClass();
         Field f;
         f = ic.getDeclaredField("i"); f.setAccessible(true);
@@ -125,20 +110,49 @@
         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);
+    public void testOutboardCall() throws Throwable {
+        Integer i = (Integer) interpreter.invokestatic(HELPER_1, "testOutboard", "()I");
+        assertEquals((int) i, 3);
+    }
+
+    public void testInterfaceCall() throws Throwable {
+        Integer i = (Integer) interpreter.invokestatic(HELPER_1, "testInterfaceCall", "()I");
+        assertEquals((int) i, 3);
+        Integer i2 = (Integer) interpreter.invokestatic(HELPER_1, "testInterfaceCall2", "()I");
+        assertEquals((int) i2, 4);
+    }
+
+    public void testEnum() throws Throwable {
+        interpreter.invokestatic(HELPER_1, "testEnum", "()V");
     }
 
     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");
+
+        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 {
@@ -200,16 +214,6 @@
             interpreter.invokestatic(HELPER_4, "testidiv", "(II)I", 4, 0));
     }
 
-    private void assertThrows(Class<? extends Throwable> exc, XRunnable test) throws Throwable {
-        try {
-            test.runOrThrow();
-            throw new AssertionError("should not return normally!");
-        } catch (Throwable ex) {
-            if (exc.isInstance(ex))  return;
-            throw ex;
-        }
-    }
-
     public void testCatch() throws Throwable {
         XRunnable donothing = () -> { };
         final String testCatchSig = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;";
@@ -238,6 +242,31 @@
         }
     }
 
+    public void testConstructor() throws Throwable {
+        Object o = interpreter.invokestatic(CONSTRUCTOR_HELPER, "testConstructor", "(II)Ljava/lang/Object;", 7, 9);
+        Class<?> ic = o.getClass();
+        Field f;
+        f = ic.getDeclaredField("x"); f.setAccessible(true);
+        assertEquals(f.get(o), 7);
+        f = ic.getDeclaredField("y"); f.setAccessible(true);
+        assertEquals(f.get(o), 9);
+
+        Object[] arr = (Object[]) interpreter.invokestatic(CONSTRUCTOR_HELPER, "testArray", "(II)[Ljava/lang/Object;", 7, 9);
+        f = ic.getDeclaredField("x"); f.setAccessible(true);
+        assertEquals(f.get(arr[0]), 7);
+        f = ic.getDeclaredField("y"); f.setAccessible(true);
+        assertEquals(f.get(arr[0]), 9);
+
+        arr = (Object[]) interpreter.invokestatic(CONSTRUCTOR_HELPER, "testArrayClone", "(II)[Ljava/lang/Object;", 7, 9);
+        f = ic.getDeclaredField("x"); f.setAccessible(true);
+        assertEquals(f.get(arr[0]), 7);
+        f = ic.getDeclaredField("y"); f.setAccessible(true);
+        assertEquals(f.get(arr[0]), 9);
+    }
+
+    public void testAnno() throws Throwable {
+        interpreter.invokestatic(ANNO_HELPER, "testAnno", "()V");
+    }
 }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test/valhalla/interpreter/InterpreterTestCase.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package valhalla.interpreter;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import static java.util.stream.Collectors.toList;
+import static org.testng.Assert.fail;
+
+/**
+ * InterpreterTestCase
+ *
+ * @author Brian Goetz
+ */
+public class InterpreterTestCase {
+    Interpreter interpreter;
+
+    @BeforeMethod
+    public void setUp() throws IOException {
+        interpreter = new StandardInterpreter("out/test/test-helpers");
+        interpreter.setEventFilter(EnumSet.allOf(InterpreterEvent.Kind.class));
+    }
+
+    @AfterMethod
+    public void assertLog() {
+        // It's OK if the interpreter executes into field MHs, or anonymous classes
+        for (InterpreterEvent event : interpreter.log) {
+            if (event.kind == InterpreterEvent.Kind.EXECUTE && event.clazz.startsWith("valhalla.interpreter.")) {
+                if (!event.name.matches("^(putField|getField|putStatic|getStatic):.*") && !event.clazz.contains("/"))
+                    fail("Questionable execution event: " + event);
+            }
+        }
+    }
+
+    protected void assertMethodExecuted(String clazz, String name) {
+        interpreter.log.stream()
+                       .filter(e -> e.kind == InterpreterEvent.Kind.EXECUTE)
+                       .filter(e -> e.clazz.equals(clazz))
+                       .filter(e -> e.name.equals(name))
+                       .map(e -> e.name)
+                       .findAny()
+                       .orElseThrow(() -> new AssertionError(String.format("Didn't find EXECUTE %s.%s", clazz, name)));
+    }
+
+    protected void assertMethodInterpreted(String clazz, String name) {
+        interpreter.log.stream()
+                       .filter(e -> e.kind == InterpreterEvent.Kind.INTERPRET)
+                       .filter(e -> e.clazz.equals(clazz))
+                       .filter(e -> e.name.equals(name))
+                       .findAny()
+                       .orElseThrow(() -> new AssertionError(String.format("Didn't find INTERPRET %s.%s", clazz, name)));
+    }
+
+    protected void assertThrows(Class<? extends Throwable> exc, XRunnable test) throws Throwable {
+        try {
+            test.runOrThrow();
+            throw new AssertionError("should not return normally!");
+        } catch (Throwable ex) {
+            if (exc.isInstance(ex))  return;
+            throw ex;
+        }
+    }
+}
--- a/interpreter/test/valhalla/interpreter/ResolveTest.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/interpreter/test/valhalla/interpreter/ResolveTest.java	Tue Jun 21 12:43:44 2016 +0200
@@ -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);
     }
 }
--- a/src/java.base/share/classes/java/lang/reflect/TypeVariable.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/java/lang/reflect/TypeVariable.java	Tue Jun 21 12:43:44 2016 +0200
@@ -99,4 +99,9 @@
      * @since 1.8
      */
      AnnotatedType[] getAnnotatedBounds();
+
+    /**
+     * Returns true if this is an 'any' type-variable.
+     */
+     default boolean isAny() { return false; }
 }
--- a/src/java.base/share/classes/java/net/URLClassLoader.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/java/net/URLClassLoader.java	Tue Jun 21 12:43:44 2016 +0200
@@ -401,7 +401,7 @@
                             try {
                                 byte[] bytes = res.getBytes();
                                 if (converter.isModel3Class(bytes)) {
-                                    System.err.printf("%s: M3.register(%s)%n", URLClassLoader.this, name);
+                                    //System.err.printf("%s: M3.register(%s)%n", URLClassLoader.this, name);
                                     Model3Converter.TemplateClass key = converter.register(bytes);
                                     res = new ByteResource(res, key.getErasedBytes());
                                     if (dumper != null) {
@@ -428,7 +428,7 @@
                             }
                             byte[] bytes = template.getBytes(pt);
                             if (bytes != null) {
-                                System.err.printf("%s: M3.specialize(%s)%n", URLClassLoader.this, name);
+                                //System.err.printf("%s: M3.specialize(%s)%n", URLClassLoader.this, name);
                                 if (dumper != null)
                                     dumper.dumpClass(name, bytes);
                                 try {
--- a/src/java.base/share/classes/sun/reflect/generics/factory/CoreReflectionFactory.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/sun/reflect/generics/factory/CoreReflectionFactory.java	Tue Jun 21 12:43:44 2016 +0200
@@ -89,9 +89,9 @@
         return new CoreReflectionFactory(d, s);
     }
 
-    public TypeVariable<?> makeTypeVariable(String name,
+    public TypeVariable<?> makeTypeVariable(boolean isAny, String name,
                                             FieldTypeSignature[] bounds){
-        return TypeVariableImpl.make(getDecl(), name, bounds, this);
+        return TypeVariableImpl.make(isAny, getDecl(), name, bounds, this);
     }
 
     public WildcardType makeWildcard(FieldTypeSignature[] ubs,
--- a/src/java.base/share/classes/sun/reflect/generics/factory/GenericsFactory.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/sun/reflect/generics/factory/GenericsFactory.java	Tue Jun 21 12:43:44 2016 +0200
@@ -57,7 +57,7 @@
      * @throws NullPointerException if any of the actual parameters
      * or any of the elements of {@code bounds} are {@code null}.
      */
-    TypeVariable<?> makeTypeVariable(String name,
+    TypeVariable<?> makeTypeVariable(boolean isAny, String name,
                                      FieldTypeSignature[] bounds);
     /**
      * Returns an instance of the {@code ParameterizedType} interface
--- a/src/java.base/share/classes/sun/reflect/generics/parser/SignatureParser.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/sun/reflect/generics/parser/SignatureParser.java	Tue Jun 21 12:43:44 2016 +0200
@@ -246,9 +246,15 @@
      *     Identifier ClassBound InterfaceBound*
      */
     private FormalTypeParameter parseFormalTypeParameter(){
+        char c = current();
+        boolean isAny = false;
+        if (c == '^') {
+            isAny = true;
+            advance();
+        }
         String id = parseIdentifier();
         FieldTypeSignature[] bs = parseBounds();
-        return FormalTypeParameter.make(id, bs);
+        return FormalTypeParameter.make(isAny, id, bs);
     }
 
     private String parseIdentifier(){
@@ -433,7 +439,7 @@
             return Wildcard.make(ub, lb);
         }
         default:
-            return parseFieldTypeSignature();
+            return (TypeArgument)parseTypeSignature(); //type signatures are also legal type arguments... pssst
         }
     }
 
--- a/src/java.base/share/classes/sun/reflect/generics/reflectiveObjects/TypeVariableImpl.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/sun/reflect/generics/reflectiveObjects/TypeVariableImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -51,6 +51,7 @@
  */
 public class TypeVariableImpl<D extends GenericDeclaration>
     extends LazyReflectiveObjectGenerator implements TypeVariable<D> {
+    private final boolean isAny;
     private final D genericDeclaration;
     private final String name;
 
@@ -63,9 +64,10 @@
     private volatile Object[] bounds;
 
     // constructor is private to enforce access through static factory
-    private TypeVariableImpl(D decl, String n, FieldTypeSignature[] bs,
+    private TypeVariableImpl(boolean isAny, D decl, String n, FieldTypeSignature[] bs,
                              GenericsFactory f) {
         super(f);
+        this.isAny = isAny;
         genericDeclaration = decl;
         name = n;
         bounds = bs;
@@ -84,7 +86,7 @@
      * specified
      */
     public static <T extends GenericDeclaration>
-                             TypeVariableImpl<T> make(T decl, String name,
+                             TypeVariableImpl<T> make(boolean isAny, T decl, String name,
                                                       FieldTypeSignature[] bs,
                                                       GenericsFactory f) {
 
@@ -94,7 +96,7 @@
             throw new AssertionError("Unexpected kind of GenericDeclaration" +
                     decl.getClass().toString());
         }
-        return new TypeVariableImpl<T>(decl, name, bs, f);
+        return new TypeVariableImpl<T>(isAny, decl, name, bs, f);
     }
 
 
@@ -246,4 +248,9 @@
         }
         return result;
     }
+
+    @Override
+    public boolean isAny() {
+        return isAny;
+    }
 }
--- a/src/java.base/share/classes/sun/reflect/generics/tree/BaseType.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/sun/reflect/generics/tree/BaseType.java	Tue Jun 21 12:43:44 2016 +0200
@@ -31,4 +31,4 @@
  * section on signatures.
  */
 public interface BaseType
-    extends TypeSignature{}
+    extends TypeArgument, TypeSignature{}
--- a/src/java.base/share/classes/sun/reflect/generics/tree/FormalTypeParameter.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/sun/reflect/generics/tree/FormalTypeParameter.java	Tue Jun 21 12:43:44 2016 +0200
@@ -29,10 +29,12 @@
 
 /** AST that represents a formal type parameter. */
 public class FormalTypeParameter implements TypeTree {
+    private final boolean isAny;
     private final String name;
     private final FieldTypeSignature[] bounds;
 
-    private FormalTypeParameter(String n, FieldTypeSignature[] bs) {
+    private FormalTypeParameter(boolean isAny, String n, FieldTypeSignature[] bs) {
+        this.isAny = isAny;
         name = n;
         bounds = bs;
     }
@@ -44,12 +46,13 @@
      * @param bs - the bounds of the type variable to be created by this method.
      * @return a formal type parameter with the requested name and bounds
      */
-    public static FormalTypeParameter make(String n, FieldTypeSignature[] bs){
-        return new FormalTypeParameter(n,bs);
+    public static FormalTypeParameter make(boolean isAny, String n, FieldTypeSignature[] bs){
+        return new FormalTypeParameter(isAny, n,bs);
     }
 
     public FieldTypeSignature[] getBounds(){return bounds;}
     public String getName(){return name;}
+    public boolean isAny() {return isAny;}
 
     public void accept(TypeTreeVisitor<?> v){v.visitFormalTypeParameter(this);}
 }
--- a/src/java.base/share/classes/sun/reflect/generics/visitor/Reifier.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/sun/reflect/generics/visitor/Reifier.java	Tue Jun 21 12:43:44 2016 +0200
@@ -80,7 +80,7 @@
     public Type getResult() { assert resultType != null;return resultType;}
 
     public void visitFormalTypeParameter(FormalTypeParameter ftp){
-        resultType = getFactory().makeTypeVariable(ftp.getName(),
+        resultType = getFactory().makeTypeVariable(ftp.isAny(), ftp.getName(),
                                                    ftp.getBounds());
     }
 
--- a/src/java.base/share/classes/valhalla/classdyn/ClassDynHelper.java	Thu Jun 09 13:08:16 2016 +0200
+++ b/src/java.base/share/classes/valhalla/classdyn/ClassDynHelper.java	Tue Jun 21 12:43:44 2016 +0200
@@ -70,7 +70,7 @@
     public static Class<?> bootstrapLoaderHelper(final String name)
             throws ClassNotFoundException {
         final Class<?> result;
-        System.out.printf("boot: M3.probe(%s)", name);
+        //System.out.printf("boot: M3.probe(%s)", name);
         try {
             return AccessController.doPrivileged(
                     new PrivilegedExceptionAction<Class<?>>() {
@@ -93,7 +93,7 @@
                                 }
                                 byte[] bytes = template.getBytes(pt);
                                 if (bytes != null) {
-                                    System.err.printf("boot: M3.specialize(%s)%n", name);
+                                    //System.err.printf("boot: M3.specialize(%s)%n", name);
                                     if (dumper != null)
                                         dumper.dumpClass(name, bytes);
                                     return Unsafe.getUnsafe().defineClass(name, bytes, 0, bytes.length, null, null);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/ArrayMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,12 @@
+package valhalla.reflect.runtime;
+
+/**
+ * {@code ArrayMirror} represents an array type with a given component type.
+ */
+public interface ArrayMirror extends RuntimeMirror {
+    /**
+     * Retrieves the array component type.
+     * @return the array component type
+     */
+    RuntimeMirror getComponentType();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/ClassLookup.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,26 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandles.Lookup;
+
+public interface ClassLookup extends MemberLookup<ClassMirror> {
+
+    @Override
+    ClassLookup withFlags(int mods);
+
+    @Override
+    ClassLookup withAccessContext(Lookup lookup);
+
+    /**
+     * Forces a given inheritance mode on this class lookup.
+     * @param kind inheritance mode
+     * @return this class lookup
+     */
+    ClassLookup withInheritanceKind(InheritanceMode kind);
+
+    /**
+     * Restrict lookup to fields with given name.
+     * @param name field name
+     * @return this field lookup
+     */
+    ClassLookup withName(String name);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/ClassMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,16 @@
+package valhalla.reflect.runtime;
+
+import java.util.List;
+
+/**
+ * {@code ClassMirror} represents a (possibly parameterized) class type.
+ */
+public interface ClassMirror extends RuntimeMirror, GenericMirror<ClassMirror>,
+                                     MemberMirror, ReflectableMirror<Class<?>>, ScopeMirror {
+
+    /**
+     * Retrieves the list of direct supertypes of this class mirror.
+     * @return The list of supertypes of this mirror.
+     */
+    List<? extends ClassMirror> getSupertypes();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/ConstructorLookup.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,19 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandles.Lookup;
+
+public interface ConstructorLookup extends MemberLookup<ConstructorMirror> {
+
+    @Override
+    ConstructorLookup withFlags(int mods);
+
+    @Override
+    ConstructorLookup withAccessContext(Lookup lookup);
+
+    /**
+     * Restrict lookup to constructors with given parameter types.
+     * @param paramTypes constructors's parameter types
+     * @return this constructor lookup
+     */
+    ConstructorLookup withParameterTypes(RuntimeMirror... paramTypes);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/ConstructorMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,25 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+/**
+ * {@code ConstructorMirror} represents a constructor member mirror.
+ */
+public interface ConstructorMirror extends MemberMirror, ReflectableMirror<Constructor<?>> {
+
+    /**
+     * Retrieve the constructor's parameter type list.
+     * @return the constructor's parameter type list
+     */
+    List<? extends RuntimeMirror> getParameterTypes();
+
+    /**
+     * Returns a {@link MethodHandle} object which can be used to call the constructor programmatically.
+     * @return a {@link MethodHandle} object for this constructor member
+     * @throws IllegalAccessException
+     * @throws IllegalStateException if this mirror contains one or more type-variables
+     */
+    MethodHandle asHandle() throws IllegalAccessException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/FieldLookup.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,36 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandles.Lookup;
+
+/**
+ * {@code MemberLookup} is a specialized lookup helper targeting fields.
+ */
+public interface FieldLookup extends MemberLookup<FieldMirror> {
+
+    @Override
+    FieldLookup withFlags(int mods);
+
+    @Override
+    FieldLookup withAccessContext(Lookup lookup);
+
+    /**
+     * Forces a given inheritance mode on this field lookup.
+     * @param kind inheritance mode
+     * @return this field lookup
+     */
+    FieldLookup withInheritanceMode(InheritanceMode kind);
+
+    /**
+     * Restrict lookup to fields with given name.
+     * @param name field name
+     * @return this field lookup
+     */
+    FieldLookup withName(String name);
+
+    /**
+     * Restrict lookup to fields with given type.
+     * @param type field type
+     * @return this field lookup
+     */
+    FieldLookup withType(RuntimeMirror type);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/FieldMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,41 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+
+/**
+ * {@code FieldMirror} represents a method member mirror.
+ */
+public interface FieldMirror extends MemberMirror, ReflectableMirror<Field> {
+
+    /**
+     * Retrieve the field's type.
+     * @return the field's return type
+     */
+    RuntimeMirror getType();
+
+    /**
+     * Returns a {@link MethodHandle} object which can be used to get the field programmatically.
+     * @return a {@link MethodHandle} object for this field member' getter
+     * @throws IllegalAccessException
+     * @throws IllegalStateException if this mirror contains one or more type-variables
+     */
+    MethodHandle asGetterHandle() throws IllegalAccessException;
+
+    /**
+     * Returns a {@link MethodHandle} object which can be used to set the field programmatically.
+     * @return a {@link MethodHandle} object for this field member' setter
+     * @throws IllegalAccessException
+     * @throws IllegalStateException if this mirror contains one or more type-variables
+     */
+    MethodHandle asSetterHandle() throws IllegalAccessException;
+
+    /**
+     * Returns a {@link VarHandle} object which can be used to access the field programmatically.
+     * @return a {@link VarHandle} object for this field member
+     * @throws IllegalAccessException
+     * @throws IllegalStateException if this mirror contains one or more type-variables
+     */
+    VarHandle asHandle() throws IllegalAccessException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/GenericMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,29 @@
+package valhalla.reflect.runtime;
+
+import java.util.List;
+
+/**
+ * {@code GenericMirror} is the common superinterface for all mirrors that can be specialized.
+ * @param <S> the type of the unspecialized mirror associated with this specializable mirror
+ */
+public interface GenericMirror<S extends GenericMirror<S>> {
+    /**
+     * Is this mirror specializable?
+     * @return true if this mirror can be instantiated.
+     */
+    boolean isSpecializable();
+
+    /**
+     * Retrieves the type-argument list associated with this mirror.
+     * @return the type-argument list; if the mirror is non-specializable, an empty list is returned
+     */
+    List<? extends RuntimeMirror> getTypeArguments();
+
+    /**
+     * Returns a specialized mirror given an type-argument array.
+     * @param typeArgs an array of type-argument mirrors
+     * @return a new specialization of this specializable mirror
+     */
+    S asSpecializedMirror(RuntimeMirror... typeArgs);
+    S asGenericMirror();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/MemberLookup.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,49 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandles.Lookup;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * {@code MemberLookup} is the common superinterface for all lookup helpers..
+ * @param <M> the member mirror targeted by this lookup
+ */
+public interface MemberLookup<M extends MemberMirror> {
+
+    /**
+     * The inheritance mode tells as to whether the lookup should propagate recursively to supertypes.
+     */
+    enum InheritanceMode {
+        /** recursive lookup including all inherited members */
+        INHERITED,
+        /** only declared members are returned the lookup */
+        DECLARED
+    }
+
+    /**
+     * Restrict lookup to members with given flags.
+     * @param mods flag mask
+     * @return this member lookup
+     */
+    MemberLookup<M> withFlags(int mods);
+
+    /**
+     * Restrict lookup to members that are accessible within a given {@link MethodLookup}.
+     * @param lookup the lookup object to be used for access checking
+     * @return this member lookup
+     */
+    MemberLookup<M> withAccessContext(Lookup lookup);
+
+    /**
+     * Retrieves all the members matched by this lookup.
+     * @return members matched by this lookup
+     */
+    List<? extends M> findAll();
+
+    /**
+     * Retrieves the single member matched by this lookup (provided an unambiguous match exists).
+     * @return member matched by this lookup
+     * @throws NoSuchElementException if the lookup returns either no member or more than one members.
+     */
+    M findOrFail() throws NoSuchElementException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/MemberMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,20 @@
+package valhalla.reflect.runtime;
+
+/**
+ * {@code MemberMirror} is the common superinterface for all mirrors that can appear as members of other mirrors
+ * {@link ScopeMirror}.
+ */
+public interface MemberMirror {
+
+    /**
+     * Retrieves the name of the member.
+     * @return the name of the member.
+     */
+     String getName();
+
+     /**
+      * Retrieves the enclosing type of this class type.
+      * @return the enclosing type or {@code null} if no such type exists
+      */
+     ScopeMirror getEnclosingType();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/MethodLookup.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,61 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandles.Lookup;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+public interface MethodLookup extends MemberLookup<MethodMirror> {
+
+    @Override
+    MethodLookup withFlags(int mods);
+
+    @Override
+    MethodLookup withAccessContext(Lookup lookup);
+
+    /**
+     * Forces a given inheritance mode on this method lookup.
+     * @param kind inheritance mode
+     * @return this method lookup
+     */
+    MethodLookup withInheritanceKind(InheritanceMode kind);
+
+    /**
+     * Restrict lookup to methods with given name.
+     * @param name method name
+     * @return this method lookup
+     */
+    MethodLookup withName(String name);
+
+    /**
+     * Restrict lookup to methods with given parameter types.
+     * @param paramTypes method's parameter types
+     * @return this method lookup
+     */
+    MethodLookup withParameterTypes(RuntimeMirror... paramTypes);
+
+    /**
+     * Restrict lookup to methods with given parameter types.
+     * @param paramsFactory a function that takes the (possibly empty) list of method type-parameters and (optionally) constructs
+     *                      the list of parameter types to be used in the lookup; if the list of type-variables does not
+     *                      satisfy the lookup, the factory should return an empty {@link Optional}
+     * @return this method lookup
+     */
+    MethodLookup withParameterTypes(Function<List<? extends TypeVariableMirror<?>>, Optional<List<? extends RuntimeMirror>>> paramsFactory);
+
+    /**
+     * Restrict lookup to methods with given return type.
+     * @param type method's return type
+     * @return this method lookup
+     */
+    MethodLookup withReturnType(RuntimeMirror type);
+
+    /**
+     * Restrict lookup to methods with given return type.
+     * @param returnFactory a function that takes the (possibly empty) list of method type-parameters and (optionally)
+     *                      constructs the return type to be used in the lookup; if the list of type-variables does not
+     *                      satisfy the lookup, the factory should return an empty {@link Optional}
+     * @return this method lookup
+     */
+    MethodLookup withReturnType(Function<List<? extends TypeVariableMirror<?>>, Optional<RuntimeMirror>> returnFactory);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/MethodMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,31 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * {@code MethodMirror} represents a method member mirror.
+ */
+public interface MethodMirror extends GenericMirror<MethodMirror>, MemberMirror, ReflectableMirror<Method> {
+
+    /**
+     * Retrieve the method's return type.
+     * @return the method's return type
+     */
+    RuntimeMirror getReturnType();
+
+    /**
+     * Retrieve the method's parameter type list.
+     * @return the method's parameter type list
+     */
+    List<? extends RuntimeMirror> getParameterTypes();
+
+    /**
+     * Returns a {@link MethodHandle} object which can be used to call the method programmatically.
+     * @return a {@link MethodHandle} object for this method member
+     * @throws IllegalAccessException
+     * @throws IllegalStateException if this mirror contains one or more type-variables
+     */
+    MethodHandle asHandle() throws IllegalAccessException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/MirrorFactory.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,71 @@
+package valhalla.reflect.runtime;
+
+import valhalla.reflect.runtime.impl.MirrorFactoryImpl;
+
+import java.lang.reflect.TypeVariable;
+
+/**
+ * A factory for creating runtime mirrors.
+ */
+public interface MirrorFactory {
+
+    /**
+     * Creates a class mirror from an existing {@link Class}.
+     * @param clazz {@link Class} object for which the mirror has to be created
+     * @return a new class mirror
+     * @throws IllegalArgumentException if {@code clazz} is either primitive ({@link Class#isPrimitive()} returns {@code true}),
+     * or an array ({@link Class#isArray()} returns {@code true}).
+     */
+    ClassMirror classMirror(Class<?> clazz) throws IllegalArgumentException;
+
+    /**
+     * Creates an array mirror with given component type.
+     * @param componentType the component type of the array mirror
+     * @return a new array mirror
+     */
+    ArrayMirror arrayMirror(RuntimeMirror componentType);
+
+    /**
+     * Creates an array mirror from an existing {@link Class}.
+     * @param clazz {@link Class} object for which the mirror has to be created
+     * @return a new array mirror
+     * @throws IllegalArgumentException if {@code clazz} is not an array ({@link Class#isArray()} returns {@code false})
+     */
+    ArrayMirror arrayMirror(Class<?> clazz) throws IllegalArgumentException;
+
+    /**
+     * Creates a new type-variable mirror from an existing {@link TypeVariable}.
+     * @param typeVariable {@link TypeVariable} object for which the mirror has to be created
+     * @param <Z> the generic mirror the type-variable mirror belongs to
+     * @return
+     */
+    <Z extends GenericMirror<Z>> TypeVariableMirror<Z> typeVarMirror(TypeVariable<?> typeVariable);
+
+    /**
+     * Creates a primitive mirror from an existing {@link Class}.
+     * @param clazz {@link Class} object for which the mirror has to be created
+     * @return a new primitive mirror
+     * @throws IllegalArgumentException if {@code clazz} is not a primitive ({@link Class#isPrimitive()} returns {@code false})
+     */
+    RuntimeMirror primitiveMirror(Class<?> clazz) throws IllegalArgumentException;
+
+    /**
+     * Creates a special {@code erased} type-argument mirror.
+     * @return special {@code erased} type-argument mirror
+     */
+    RuntimeMirror erasedMirror();
+
+    /**
+     * Creates a special {@code any} type-argument mirror.
+     * @return special {@code any} type-argument mirror
+     */
+    RuntimeMirror anyMirror();
+
+    /**
+     * Returns an instance of this mirror factory interface.
+     * @return a mirror factory
+     */
+    static MirrorFactory instance() {
+        return MirrorFactoryImpl.instance();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/ReflectableMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,17 @@
+package valhalla.reflect.runtime;
+
+import java.lang.reflect.Method;
+
+/**
+ * {@code ReflectiveMirror} is the common superinterface for all mirrors that can be mapped back to legacy reflective objects.
+ * @param <X> the reflective object this mirror can be mapped to (e.g. {@link Class}, {@link Method}, etc.)
+ */
+public interface ReflectableMirror<X> {
+
+    /**
+     * Map this mirror into a legacy reflective object.
+     * @return the legacy reflective object for this mirror.
+     * @throws IllegalStateException if this mirror contains one or more type-variables
+     */
+    X reflect() throws IllegalStateException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/RuntimeMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,251 @@
+package valhalla.reflect.runtime;
+
+import java.lang.invoke.MethodHandles;
+
+import java.lang.reflect.TypeVariable;
+import java.util.function.Function;
+
+/**
+ * {@code RuntimeMirror} is the common superinterface for all types representable in a Java programming language classfile.
+ * These include parameterized types, array types, type variables, primitive types and special type-arguments (such as
+ * {@code erased} or {@code any}).
+ *
+ * <h1>Overview</h1>
+ * The root of the reflection type hierarchy is {@code RuntimeMirror}. There are six kinds of mirrors; each mirror has a
+ * unique kind (see {@link Kind} and can be created using the corresponding factory method in {@link MirrorFactory},
+ * as summarized in the table below:
+ *
+ * <table BORDER CELLPADDING=3 CELLSPACING=1>
+ *   <caption>Summary of mirror kinds</caption>
+ *   <tr>
+ *     <th>Kind</th>
+ *     <th>Class</th>
+ *     <th>Factory</th>
+ *     <th>{@link Kind}</th>
+ *   </tr>
+ *   <tr>
+ *     <td>class mirror</td>
+ *     <td>{@link ClassMirror}</td>
+ *     <td>{@link MirrorFactory#classMirror(Class)}</td>
+ *     <td>{@link Kind#CLASS}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>array mirror</td>
+ *     <td>{@link ArrayMirror}</td>
+ *     <td>{@link MirrorFactory#arrayMirror(Class)} <br>
+ *         {@link MirrorFactory#arrayMirror(RuntimeMirror)}
+ *     </td>
+ *     <td>{@link Kind#ARRAY}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>type-variable mirror</td>
+ *     <td>{@link TypeVariableMirror}</td>
+ *     <td>{@link MirrorFactory#typeVarMirror(TypeVariable)}</td>
+ *     <td>{@link Kind#TYPEVAR}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>primitive mirror</td>
+ *     <td>{@link RuntimeMirror}</td>
+ *     <td>{@link MirrorFactory#primitiveMirror(Class)}</td>
+ *     <td>{@link Kind#VALUE}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@code erased}</td>
+ *     <td>{@link RuntimeMirror}</td>
+ *     <td>{@link MirrorFactory#erasedMirror()}</td>
+ *     <td>{@link Kind#ERASED}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>{@code any}</td>
+ *     <td>{@link RuntimeMirror}</td>
+ *     <td>{@link MirrorFactory#anyMirror()}</td>
+ *     <td>{@link Kind#ANY}</td>
+ *   </tr>
+ * </table>
+ *
+ * <p>Every runtime mirror support basic operations such as kind query (see {@link RuntimeMirror#getKind()} as well as
+ * basic type-related operations (e.g. {@link RuntimeMirror#isSubtypeOf(RuntimeMirror)}.
+ *
+ * <p>Additionally, each mirror class provide some specific structural access to the mirror's contents; for instance,
+ * array mirrors provide access to the component type (see {@link ArrayMirror#getComponentType()}), class mirrors give
+ * access to supertypes (see {@link ClassMirror#getSupertypes()}, etc.
+ *
+ * <h1>Specialization support</h1>
+ *
+ * Some mirrors are specializable (see {@link GenericMirror}); when a mirror is specializable, it contains one or more type-variables,
+ * which are accessible using {@link GenericMirror#getTypeArguments()}. If a mirror is specializable, it can be specialized using
+ * the method {@link GenericMirror#asSpecializedMirror(RuntimeMirror...)} - as follows:
+ * <p>
+ * <blockquote><pre>{@code
+ * RuntimeMirror typeArgs = { ... };
+ * if (mirror.isSpecializable()) {
+ *     ClassMirror s_mirror = mirror.asSpecializedMirror(typeArgs);
+ * }
+ * }</pre></blockquote>
+ * <p>The resulting mirror will be obtained by replacing the type-variable in the specializable mirror with the provided
+ * actual type-arguments. Once a specialized mirror is obtained, one can always go back to the unspecialized version - using
+ * {@link GenericMirror#asGenericMirror()}:
+ *
+ * <p>
+ * {@code assertEquals(s_mirror.asGenericMirror(), mirror);}
+ *
+ * <p>There are two kinds of generic mirrors: class mirrors and method mirrors (since both classes and methods can be specialized).
+ *
+ * <h1>Member lookup</h1>
+ * Some mirrors support lookup operations (see {@link ScopeMirror}). Such mirrors can be asked the list of classes, methods,
+ * fields and constructors defined within themselves (either directly or indirectly, through inheritance). The lookup operations
+ * are supported through lookup objects (see {@link MemberLookup}. The table below groups the available lookup objects
+ * by mirror kind.
+ *
+ * <table BORDER CELLPADDING=3 CELLSPACING=1>
+ *   <caption>Summary of member lookup</caption>
+ *   <tr>
+ *     <th>Member kind</th>
+ *     <th>Class</th>
+ *     <th>{@link MemberLookup}</th>
+ *   </tr>
+ *   <tr>
+ *     <td>class</td>
+ *     <td>{@link ClassMirror}</td>
+ *     <td>{@link ClassLookup}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>field</td>
+ *     <td>{@link FieldMirror}</td>
+ *     <td>{@link FieldLookup}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>method</td>
+ *     <td>{@link MethodMirror}</td>
+ *     <td>{@link MethodLookup}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>constructor</td>
+ *     <td>{@link ConstructorMirror}</td>
+ *     <td>{@link ConstructorLookup}</td>
+ *   </tr>
+ * </table>
+ *
+ * <p>Lookups can be either loose or strict, depending on whether the method {@link MemberLookup#findAll()} or
+ * {@link MemberLookup#findOrFail()} is used to perform the lookup. A loose lookup looks like the following:
+ *
+ * <p>
+ * {@code mirror.methodLookup().findAll();}
+ *
+ * <p>Which returns all methods in {@code mirror} (either declared or inherited).
+ *
+ * <p>If the user knows more about the method to be looked up, a more precise lookup can be attempted:
+ * <blockquote><pre>{@code
+ * RuntimeMirror[] paramTypes = { ... }
+ * mirror.findMethod("foo", paramTypes);
+ * }</pre></blockquote>
+ *
+ * <p>Since a method can be generic, sometimes a strict lookup cannot be defined using the above method (because a generic
+ * method signature could depend on type-parameters declared by the method itself, which are normally not known ahead of lookup).
+ * To address this, a lookup method specifically targeting generic method is provided (see {@link MethodLookup#withParameterTypes(Function)},
+ * which can be used as follows:
+ *
+ * <p>
+ * {@code mirror.findGenericMethod("gfoo", tvars -> Arrays.asList(tvars.get(1)));}
+ *
+ * <p>The above lookup searches for a generic method whose name is 'gfoo' and whose parameter list is the generic method's
+ * second type-variable.
+ *
+ * <p>Fields, constructors and member inner classes can be looked up in a similar fashion.
+ *
+ * <h1>Reflective features</h1>
+ * Some mirrors (see {@link ReflectableMirror}) can be projected back into a legacy reflective object using {@link ReflectableMirror#reflect()}.
+ * In order to perform this projection, a mirror should not contain any open type-variables, <b>or an exception will occur</b>:
+ * <p>
+ * <blockquote><pre>{@code
+ * Class<?> c_m = mirror.reflect(); //throws!
+ * Class<?> c_sm = smirror.reflect(); //ok
+ * }</pre></blockquote>
+ *
+ * <p>This compatibility layers allow new new mirror API to interoperate with existing {@link Class}-based API,
+ * such as {@link MethodHandles.Lookup}.
+ *
+ * <p>In addition to the basic legacy reflective mapping, some mirrors (like method, field and constructor mirrors) provide
+ * extra reflective capabilities to, for example, map a method mirror into a method handle (see {@link MethodMirror#asHandle()}).
+ */
+public interface RuntimeMirror extends ReflectableMirror<Class<?>> {
+
+    /**
+     * The kind of the runtime mirror.
+     */
+    enum Kind {
+        /**
+         * Primitive or value mirror.
+         */
+        VALUE,
+        /**
+         * Array mirror.
+         */
+        ARRAY,
+        /**
+         * Class mirror.
+         */
+        CLASS,
+        /**
+         * Type-variable mirror.
+         */
+        TYPEVAR,
+        /**
+         * Special {@code erased} type-argument mirror.
+         */
+        ERASED,
+        /**
+         * Special {@code any} type-argument mirror.
+         */
+        ANY
+    }
+
+    /**
+     * Retrieves this mirror's kind.
+     * @return the mirror kind
+     */
+    Kind getKind();
+
+    /**
+     * This method implements the type equality relationship {@code T == S}. Equality is determined as follows:
+     * <ul>
+     *     <li>Given two array mirrors {@code S[]} and {@code T[]}, {@code S[] == T[]} if {@code S == T}</li>
+     *     <li>Given two class mirrors {@code G<X1, X2, ... Xn>} and {@code D<Y1, X2, ... Xm>}, {@code G<X1, X2, ... Xn> == D<Y1, Y2, ... Ym>} iff:
+     *     <ul>
+     *         <li>
+     *             G and D represent the same class declaration
+     *             n == m
+     *             for each i in 1..n {@code Xi == Yi}
+     *         </li>
+     *     </ul></li>
+     *     <li>Otherwise given two mirrors {@code S} and {@code T}, {@code S == T} if the two mirrors represent the same underlying entity
+     *     (primitive type, type-variable or special type-argument).</li>
+     * </ul>
+     * @param that the other operand of the equality test
+     * @return true if this type is the same type as {@code that}
+     */
+    boolean equals(Object that);
+
+    /**
+     * This method implements the type equality relationship {@code T <: S}. Equality is determined as follows:
+     * <ul>
+     *     <li>Given two array mirrors {@code S[]} and {@code T[]}, {@code S[] <: T[]} if {@code S <: T}</li>
+     *     <li>Given two class mirrors {@code G<X1, X2, ... Xn>} and {@code D<Y1, X2, ... Xm>}, {@code G<X1, X2, ... Xn> == D<Y1, Y2, ... Yn>} iff:
+     *     <ul>
+     *         <li>{@code G<X1, X2, ... Xn>} has a supertype of the kind {@code D<U1, U2 ... Un>}</li>
+     *         <li>for each i in 1..n either {@code Ui == Yi} or {@code Yi == any}</li>
+     *     </ul></li>
+     *     <li>Given a type-variable mirror X and a mirror T, {@code X <: T} iff there exist a bound B of X such that {@code B <: T}</li>
+     *     <li>Otherwise given two mirrors {@code S} and {@code T}, {@code S <: T} iff {@code S == T}.</li>
+     * </ul>
+     * @param that the other operand of the subtyping test
+     * @return true if this type is a subtype of {@code that}
+     */
+    boolean isSubtypeOf(RuntimeMirror that);
+
+    /**
+     * Retrieve a string-based representation of this mirror.
+     * @return string representation of this mirror
+     */
+    String getTypeString();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/ScopeMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,31 @@
+package valhalla.reflect.runtime;
+
+/**
+ * {@code ScopedMirror} is the common superinterface for all mirrors that can contain members.
+ */
+public interface ScopeMirror {
+
+    /**
+     * Retrieves a lookup helper for member classes of this scoped mirror.
+     * @return lookup helper for member classes
+     */
+    ClassLookup classLookup();
+
+    /**
+     * Retrieves a lookup helper for member fields of this scoped mirror.
+     * @return lookup helper for member fields
+     */
+    FieldLookup fieldLookup();
+
+    /**
+     * Retrieves a lookup helper for member methods of this scoped mirror.
+     * @return lookup helper for member methods
+     */
+    MethodLookup methodLookup();
+
+    /**
+     * Retrieves a lookup helper for member constructors of this scoped mirror.
+     * @return lookup helper for member constructors
+     */
+    ConstructorLookup constructorLookup();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/TypeVariableMirror.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,21 @@
+package valhalla.reflect.runtime;
+
+import java.util.List;
+
+/**
+ * {@code TypeVariableMirror} represents a type-variable.
+ */
+public interface TypeVariableMirror<S extends GenericMirror<S>> extends RuntimeMirror {
+
+    /**
+     * Retrieves the type-variable bounds.
+     * @return a list containing type-variable bounds
+     */
+    List<RuntimeMirror> getBounds();
+
+    /**
+     * Retrieves the type-variable owner.
+     * @return the type-variable owner
+     */
+    S getOwner();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/AbstractLookupImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,260 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.ClassLookup;
+import valhalla.reflect.runtime.ClassMirror;
+import valhalla.reflect.runtime.ConstructorLookup;
+import valhalla.reflect.runtime.ConstructorMirror;
+import valhalla.reflect.runtime.FieldLookup;
+import valhalla.reflect.runtime.FieldMirror;
+import valhalla.reflect.runtime.MemberLookup;
+import valhalla.reflect.runtime.MemberMirror;
+import valhalla.reflect.runtime.MethodLookup;
+import valhalla.reflect.runtime.MethodMirror;
+import valhalla.reflect.runtime.RuntimeMirror;
+import valhalla.reflect.runtime.TypeVariableMirror;
+import valhalla.reflect.runtime.impl.AbstractMemberMirrorImpl.ConstructorMirrorImpl;
+import valhalla.reflect.runtime.impl.AbstractMemberMirrorImpl.FieldMirrorImpl;
+import valhalla.reflect.runtime.impl.AbstractMemberMirrorImpl.MethodMirrorImpl;
+
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+abstract class AbstractLookupImpl<M extends MemberMirror, I extends M, R> implements MemberLookup<M> {
+
+    protected final Function<Class<?>, R[]> membersFunc;
+    protected Predicate<I> membersFilter;
+    protected final Function<R, I> memberMapper;
+    protected final ClassMirrorImpl site;
+    protected InheritanceMode inheritanceMode;
+
+    protected AbstractLookupImpl(Function<Class<?>, R[]> membersFunc, Function<R, I> memberMapper,
+                              ClassMirrorImpl site, InheritanceMode inheritanceMode) {
+        this.membersFunc = membersFunc;
+        this.memberMapper = memberMapper;
+        this.membersFilter = x -> true;
+        this.site = site;
+        this.inheritanceMode = inheritanceMode;
+    }
+
+    @Override
+    public List<? extends M> findAll() {
+        return membersClosure(site.clazz).stream()
+                .map(memberMapper)
+                .filter(membersFilter)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public M findOrFail() {
+        List<? extends M> members = findAll();
+        if (findAll().size() == 1) {
+            return members.get(0);
+        } else {
+            throw new NoSuchElementException("No results found");
+        }
+    }
+
+    private static final int ALL_ACCESS_MODES = Modifier.PUBLIC |
+            Modifier.PRIVATE |
+            Modifier.PROTECTED |
+            java.lang.invoke.MethodHandles.Lookup.PACKAGE;
+
+    protected boolean isMemberAccessible(Object... args) {
+        try {
+            Class<?> c = MirrorUtils.factory.forName("sun.invoke.util.VerifyAccess", getClass());
+            return (Boolean) c.getMethod("isMemberAccessible", new Class<?>[] { Class.class, Class.class, int.class, Class.class, int.class }).invoke(null, args);
+        } catch (ReflectiveOperationException ex) {
+            ex.printStackTrace();
+            return false;
+        }
+    }
+
+    private List<R> membersClosure(Class<?> site) {
+        List<R> members = new ArrayList<>();
+        while (site != null) {
+            members.addAll(Arrays.asList(membersFunc.apply(site)));
+            site = site.getSuperclass();
+            if (inheritanceMode == InheritanceMode.DECLARED) break;
+        }
+        return members;
+    }
+
+    static class FieldLookupImpl extends AbstractLookupImpl<FieldMirror, FieldMirrorImpl, Field> implements FieldLookup {
+
+        FieldLookupImpl(ClassMirrorImpl site) {
+            super(Class::getDeclaredFields,
+                    f -> (FieldMirrorImpl)MirrorUtils.factory.field(MirrorUtils.asSuper(site, f.getDeclaringClass()), f),
+                    site, InheritanceMode.INHERITED);
+        }
+
+        @Override
+        public FieldLookupImpl withFlags(int flags) {
+            membersFilter = membersFilter.and(f -> (f.member.getModifiers() & flags) != 0);
+            return this;
+        }
+
+        @Override
+        public FieldLookup withAccessContext(Lookup lookup) {
+            membersFilter = membersFilter.and(f -> isMemberAccessible(f.member.getDeclaringClass(),
+                    f.member.getDeclaringClass(), f.member.getModifiers(), lookup.lookupClass(), ALL_ACCESS_MODES));
+            return this;
+        }
+
+        @Override
+        public FieldLookup withInheritanceMode(InheritanceMode kind) {
+            inheritanceMode = kind;
+            return this;
+        }
+
+        @Override
+        public FieldLookup withName(String name) {
+            membersFilter = membersFilter.and(f -> f.getName().equals(name));
+            return this;
+        }
+
+        @Override
+        public FieldLookup withType(RuntimeMirror type) {
+            membersFilter = membersFilter.and(f -> f.getType().equals(type));
+            return this;
+        }
+    }
+    
+    static class ConstructorLookupImpl extends AbstractLookupImpl<ConstructorMirror, ConstructorMirrorImpl, Constructor<?>> implements ConstructorLookup {
+
+        ConstructorLookupImpl(ClassMirrorImpl site) {
+            super(Class::getDeclaredConstructors,
+                    c -> (ConstructorMirrorImpl)MirrorUtils.factory.constructor(site, c),
+                    site, InheritanceMode.DECLARED);
+        }
+
+        @Override
+        public ConstructorLookup withFlags(int flags) {
+            membersFilter = membersFilter.and(c -> (c.member.getModifiers() & flags) != 0);
+            return this;
+        }
+
+        @Override
+        public ConstructorLookup withAccessContext(Lookup lookup) {
+            membersFilter = membersFilter.and(f -> isMemberAccessible(f.member.getDeclaringClass(),
+                    f.member.getDeclaringClass(), f.member.getModifiers(), lookup.lookupClass(), ALL_ACCESS_MODES));
+            return this;
+        }
+
+        @Override
+        public ConstructorLookup withParameterTypes(RuntimeMirror... paramTypes) {
+            membersFilter = membersFilter.and(f -> f.getParameterTypes().equals(Arrays.asList(paramTypes)));
+            return this;
+        }
+    }
+    
+    static class MethodLookupImpl extends AbstractLookupImpl<MethodMirror, MethodMirrorImpl, Method> implements MethodLookup {
+
+        MethodLookupImpl(ClassMirrorImpl site) {
+            super(Class::getDeclaredMethods,
+                    f -> (MethodMirrorImpl)MirrorUtils.factory.method(MirrorUtils.asSuper(site, f.getDeclaringClass()), f),
+                    site, InheritanceMode.INHERITED);
+        }
+
+        @Override
+        public MethodLookupImpl withFlags(int flags) {
+            membersFilter = membersFilter.and(f -> (f.member.getModifiers() & flags) != 0);
+            return this;
+        }
+
+        @Override
+        public MethodLookup withAccessContext(Lookup lookup) {
+            membersFilter = membersFilter.and(f -> isMemberAccessible(f.member.getDeclaringClass(),
+                    f.member.getDeclaringClass(), f.member.getModifiers(), lookup.lookupClass(), ALL_ACCESS_MODES));
+            return this;
+        }
+
+        @Override
+        public MethodLookup withInheritanceKind(InheritanceMode kind) {
+            inheritanceMode = kind;
+            return this;
+        }
+
+        @Override
+        public MethodLookup withName(String name) {
+            membersFilter = membersFilter.and(f -> f.getName().equals(name));
+            return this;
+        }
+
+        @Override
+        public MethodLookup withReturnType(RuntimeMirror type) {
+            membersFilter = membersFilter.and(f -> f.getReturnType().equals(type));
+            return this;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public MethodLookup withReturnType(Function<List<? extends TypeVariableMirror<?>>, Optional<RuntimeMirror>> returnFactory) {
+            membersFilter = membersFilter.and(m -> {
+                Optional<RuntimeMirror> ret = returnFactory.apply((List<TypeVariableMirror<?>>)m.getTypeArguments());
+                return ret.isPresent() && m.getReturnType().equals(ret.get());
+            });
+            return this;
+        }
+        
+        @Override
+        public MethodLookup withParameterTypes(RuntimeMirror... paramTypes) {
+            membersFilter = membersFilter.and(f -> f.getParameterTypes().equals(Arrays.asList(paramTypes)));
+            return this;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public MethodLookup withParameterTypes(Function<List<? extends TypeVariableMirror<?>>, Optional<List<? extends RuntimeMirror>>> paramsFactory) {
+            membersFilter = membersFilter.and(m -> {
+                Optional<List<? extends RuntimeMirror>> params = paramsFactory.apply((List<TypeVariableMirror<?>>)m.getTypeArguments());
+                return params.isPresent() && m.getParameterTypes().equals(params.get());
+            });
+            return this;
+        }
+    }
+    
+    static class ClassLookupImpl extends AbstractLookupImpl<ClassMirror, ClassMirrorImpl, Class<?>> implements ClassLookup {
+
+        ClassLookupImpl(ClassMirrorImpl site) {
+            super(Class::getDeclaredClasses,
+                    f -> (ClassMirrorImpl)MirrorUtils.factory.memberClass(MirrorUtils.asSuper(site, f.getDeclaringClass()), f),
+                    site, InheritanceMode.INHERITED);
+        }
+
+        @Override
+        public ClassLookupImpl withFlags(int flags) {
+            membersFilter = membersFilter.and(f -> (f.clazz.getModifiers() & flags) != 0);
+            return this;
+        }
+
+        @Override
+        public ClassLookup withAccessContext(Lookup lookup) {
+            membersFilter = membersFilter.and(f -> isMemberAccessible(f.clazz.getDeclaringClass(),
+                    f.clazz.getDeclaringClass(), f.clazz.getModifiers(), lookup.lookupClass(), ALL_ACCESS_MODES));
+            return this;
+        }
+
+        @Override
+        public ClassLookup withInheritanceKind(InheritanceMode kind) {
+            inheritanceMode = kind;
+            return this;
+        }
+
+        @Override
+        public ClassLookup withName(String name) {
+            membersFilter = membersFilter.and(f -> f.clazz.getName().equals(name));
+            return this;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/AbstractMemberMirrorImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,312 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.ClassMirror;
+import valhalla.reflect.runtime.ConstructorMirror;
+import valhalla.reflect.runtime.FieldMirror;
+import valhalla.reflect.runtime.MemberLookup.InheritanceMode;
+import valhalla.reflect.runtime.MemberMirror;
+import valhalla.reflect.runtime.MethodMirror;
+import valhalla.reflect.runtime.RuntimeMirror;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+class AbstractMemberMirrorImpl<M extends Member> implements MemberMirror {
+
+    protected final ClassMirrorImpl owner;
+    protected final M member;
+
+    @SuppressWarnings("unchecked")
+    protected <Z extends RuntimeMirror> Z map(Type t) {
+        return (Z)owner.map(t);
+    }
+
+    protected AbstractMemberMirrorImpl(ClassMirrorImpl owner, M member) {
+        this.owner = owner;
+        this.member = member;
+    }
+
+    @Override
+    public String getName() {
+        return member.getName();
+    }
+
+    @Override
+    public ClassMirror getEnclosingType() {
+        return owner;
+    }
+
+    static class FieldMirrorImpl extends AbstractMemberMirrorImpl<Field> implements FieldMirror {
+
+        FieldMirrorImpl(ClassMirrorImpl owner, Field field) {
+            super(owner, field);
+        }
+
+        @Override
+        public RuntimeMirror getType() {
+            return owner.map(member.getGenericType());
+        }
+
+        @Override
+        public VarHandle asHandle() throws IllegalAccessException {
+            throw new UnsupportedOperationException(); //how to unreflect a field?
+        }
+
+        @Override
+        public MethodHandle asGetterHandle() throws IllegalAccessException {
+            return MethodHandles.lookup().unreflectGetter(reflect());
+        }
+
+        @Override
+        public MethodHandle asSetterHandle() throws IllegalAccessException {
+            return MethodHandles.lookup().unreflectSetter(reflect());
+        }
+
+        @Override
+        public Field reflect() {
+            if (owner.isSpecializable()) {
+                throw new IllegalStateException("Open type variables in owner!");
+            }
+            try {
+                return owner.reflect().getDeclaredField(member.getName());
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException();
+            }
+        }
+    }
+
+    static class MethodMirrorImpl extends AbstractMemberMirrorImpl<Method> implements MethodMirror {
+
+        MethodMirrorImpl(ClassMirrorImpl owner, Method method) {
+            super(owner, method);
+        }
+
+        @Override
+        public RuntimeMirror getReturnType() {
+            return map(member.getGenericReturnType());
+        }
+
+        @Override
+        public List<? extends RuntimeMirror> getParameterTypes() {
+            return Stream.of(member.getGenericParameterTypes())
+                    .map(this::<RuntimeMirror>map)
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        public MethodHandle asHandle() throws IllegalAccessException {
+            return MethodHandles.lookup().unreflect(reflect());
+        }
+
+        @Override
+        public Method reflect() {
+            if (owner.isSpecializable()) {
+                throw new IllegalStateException("Open type variables in owner!");
+            }
+            try {
+                return owner.reflect().getDeclaredMethod(member.getName(),
+                        getParameterTypes().stream().map(RuntimeMirror::reflect).toArray(Class<?>[]::new));
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException();
+            }
+        }
+
+        @Override
+        public boolean isSpecializable() {
+            return false;
+        }
+
+        @Override
+        public List<? extends RuntimeMirror> getTypeArguments() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public MethodMirror asSpecializedMirror(RuntimeMirror... typeArgs) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public MethodMirror asGenericMirror() {
+            throw new IllegalStateException();
+        }
+    }
+
+    static class ConstructorMirrorImpl extends AbstractMemberMirrorImpl<Constructor<?>> implements ConstructorMirror {
+
+        ConstructorMirrorImpl(ClassMirrorImpl owner, Constructor<?> member) {
+            super(owner, member);
+        }
+
+        @Override
+        public List<? extends RuntimeMirror> getParameterTypes() {
+            return Stream.of(member.getGenericParameterTypes())
+                    .map(owner::<RuntimeMirror>map)
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        public ClassMirror getEnclosingType() {
+            return owner;
+        }
+
+        @Override
+        public MethodHandle asHandle() throws IllegalAccessException {
+            return MethodHandles.lookup().unreflectConstructor(reflect());
+        }
+
+        @Override
+        public Constructor<?> reflect() {
+            if (owner.isSpecializable()) {
+                throw new IllegalStateException("Open type variables in owner!");
+            }
+            try {
+                return owner.reflect().getDeclaredConstructor(
+                        getParameterTypes().stream().map(RuntimeMirror::reflect).toArray(Class<?>[]::new));
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException();
+            }
+        }
+
+        @Override
+        public String getName() {
+            return "<init>";
+        }
+    }
+
+    static class GenericMethodMirrorImpl extends MethodMirrorImpl {
+
+        final ClassMirrorImpl genericClassMirror;
+        final List<RuntimeMirror> typeArgs;
+
+        GenericMethodMirrorImpl(ClassMirrorImpl owner, Method method, List<RuntimeMirror> typeArgs) {
+            super(owner, method);
+            this.genericClassMirror = (ClassMirrorImpl) owner.classLookup()
+                    .withInheritanceKind(InheritanceMode.DECLARED)
+                    .withName(innerName(method)).findOrFail();
+            this.typeArgs = typeArgs;
+        }
+
+        static String innerName(Method m) {
+            MethodType targetMethod = MethodType.methodType(m.getReturnType(), m.getParameterTypes());
+            String desc = targetMethod.toMethodDescriptorString();
+            String params = desc.substring(1, desc.lastIndexOf(')'));
+            return m.getDeclaringClass().getName() + "$" + m.getName() + "$" + Math.abs(params.hashCode());
+        }
+
+        @Override
+        public List<? extends RuntimeMirror> getTypeArguments() {
+            return typeArgs;
+        }
+
+        @Override
+        public boolean isSpecializable() {
+            return true;
+        }
+
+        @Override
+        public Method reflect() {
+            throw new IllegalStateException("open type variables!");
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public MethodMirror asSpecializedMirror(RuntimeMirror... typeArgs) {
+            if (owner.isSpecializable()) {
+                throw new IllegalStateException("Open type variables in owner!");
+            }
+            List<RuntimeMirror> actualTypeArgs = Arrays.asList(typeArgs);
+            if (actualTypeArgs.size() != getTypeArguments().size()) {
+                new FooImpl(null, null);
+                throw new IllegalStateException();
+            }
+            return new SpecializedMethodMirrorImpl(this, actualTypeArgs);
+        }
+
+        @Override
+        public MethodMirror asGenericMirror() {
+            return this;
+        }
+    }
+
+    static class FooImpl extends MethodMirrorImpl {
+        public FooImpl(ClassMirrorImpl owner, Method method) {
+            super(owner, method);
+        }
+    }
+
+    static class SpecializedMethodMirrorImpl extends MethodMirrorImpl {
+
+        final GenericMethodMirrorImpl genericMethod;
+        final List<RuntimeMirror> actualTypeArgs;
+
+        SpecializedMethodMirrorImpl(GenericMethodMirrorImpl genericMethod, List<RuntimeMirror> actualTypeArgs) {
+            super(genericMethod.owner,
+                    ((MethodMirrorImpl)genericMethod.genericClassMirror.methodLookup()
+                        .withName(genericMethod.getName()).findOrFail()).member);
+            this.genericMethod = genericMethod;
+            this.actualTypeArgs = actualTypeArgs;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        protected RuntimeMirror map(Type t) {
+            return MirrorUtils.subst(super.map(t), (List<RuntimeMirror>)genericMethod.genericClassMirror.getTypeArguments(), actualTypeArgs);
+        }
+
+        @Override
+        public List<? extends RuntimeMirror> getTypeArguments() {
+            return actualTypeArgs;
+        }
+
+        @Override
+        public MethodMirror asGenericMirror() {
+            return genericMethod;
+        }
+
+        @Override
+        public Method reflect() {
+            return genericMethod.genericClassMirror.asSpecializedMirror(actualTypeArgs.stream().toArray(RuntimeMirror[]::new))
+                    .methodLookup()
+                        .withName(member.getName())
+                        .withParameterTypes(getParameterTypes().stream().toArray(RuntimeMirror[]::new)).findOrFail().reflect();
+        }
+
+        @Override
+        public MethodHandle asHandle() throws IllegalAccessException {
+            MethodHandle mh = super.asHandle();
+            if (!Modifier.isStatic(member.getModifiers())) {
+                try {
+                    MethodHandle wrap = MethodHandles.lookup().findVirtual(getClass(), "wrap", MethodType.methodType(Object.class, Object[].class));
+                    MethodHandle boundWrap = wrap.bindTo(this);
+                    return boundWrap.asVarargsCollector(Object[].class);
+                } catch (Throwable ex) {
+                    throw new IllegalStateException(ex);
+                }
+            } else {
+                return mh;
+            }
+        }
+
+        public Object wrap(Object... args) throws ReflectiveOperationException {
+            Method ref = reflect();
+            Object rec = ref.getDeclaringClass().getConstructors()[0].newInstance(args[0]);
+            Object[] args2 = new Object[args.length - 1];
+            System.arraycopy(args, 1, args2, 0, args2.length);
+            return ref.invoke(rec, args2);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/ArrayMirrorImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,59 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.ArrayMirror;
+import valhalla.reflect.runtime.RuntimeMirror;
+
+class ArrayMirrorImpl implements ArrayMirror {
+
+    private final RuntimeMirror elemtype;
+
+    ArrayMirrorImpl(RuntimeMirror elemtype) {
+        this.elemtype = elemtype;
+    }
+
+    @Override
+    public Kind getKind() {
+        return Kind.ARRAY;
+    }
+
+    @Override
+    public boolean isSubtypeOf(RuntimeMirror that) {
+        return equals(that) ||
+                (that.getKind() == Kind.ARRAY && elemtype.isSubtypeOf(((ArrayMirror) that).getComponentType()));
+    }
+
+    @Override
+    public String getTypeString() {
+        return elemtype.getTypeString() + "[]";
+    }
+
+    @Override
+    public Class<?> reflect() {
+        try {
+            Class<?> elemClazz = elemtype.reflect();
+            return MirrorUtils.factory.forName("[" + elemClazz.getCanonicalName(), elemClazz);
+        } catch (ReflectiveOperationException ex) {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof ArrayMirrorImpl) {
+            ArrayMirrorImpl that = (ArrayMirrorImpl)obj;
+            return getComponentType().equals(that.getComponentType());
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return getComponentType().hashCode() << 1;
+    }
+
+    @Override
+    public RuntimeMirror getComponentType() {
+        return elemtype;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/ClassMirrorImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,334 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.ClassLookup;
+import valhalla.reflect.runtime.ClassMirror;
+import valhalla.reflect.runtime.ConstructorLookup;
+import valhalla.reflect.runtime.FieldLookup;
+import valhalla.reflect.runtime.MethodLookup;
+import valhalla.reflect.runtime.RuntimeMirror;
+import valhalla.reflect.runtime.impl.AbstractLookupImpl.ClassLookupImpl;
+import valhalla.reflect.runtime.impl.AbstractLookupImpl.ConstructorLookupImpl;
+import valhalla.reflect.runtime.impl.AbstractLookupImpl.FieldLookupImpl;
+import valhalla.reflect.runtime.impl.AbstractLookupImpl.MethodLookupImpl;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+class ClassMirrorImpl extends DelegatedMirrorImpl implements ClassMirror {
+
+    protected List<RuntimeMirror> typeArgs;
+
+    ClassMirrorImpl(Class<?> clazz) {
+        super(clazz);
+    }
+
+    @SuppressWarnings("unchecked")
+    <Z extends RuntimeMirror> Z map(Type t) {
+        return (Z)MirrorUtils.typeToMirror(t);
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        return super.equals(that) &&
+                ((ClassMirrorImpl)that).getTypeArguments().equals(getTypeArguments());
+
+    }
+
+    @Override
+    public boolean isSubtypeOf(RuntimeMirror that) {
+        if (equals(that)) {
+            //short-circuit
+            return true;
+        } else if (that.getKind() == Kind.CLASS) {
+            ClassMirrorImpl thatClazz = (ClassMirrorImpl)that;
+            ClassMirror sup = MirrorUtils.asSuper(this, thatClazz.clazz);
+            if (sup == null) {
+                return false;
+            }
+            List<? extends RuntimeMirror> thisArgs = sup.getTypeArguments();
+            List<? extends RuntimeMirror> thatArgs = thatClazz.getTypeArguments();
+            for (int i = 0 ; i < thisArgs.size() ; i++) {
+                if (!thisArgs.get(i).equals(thatArgs.get(i)) && thatArgs.get(i).getKind() != Kind.ANY) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() | getTypeArguments().hashCode();
+    }
+
+    //structural queries
+
+    @Override
+    public List<? extends RuntimeMirror> getTypeArguments() {
+        if (typeArgs == null) {
+            //delay computation to prevent factory deadlocks
+            typeArgs = Stream.of(clazz.getTypeParameters())
+                    .filter(TypeVariable::isAny)
+                    .map(MirrorUtils.factory::typeVarMirror)
+                    .collect(Collectors.toList());
+        }
+        return typeArgs;
+    }
+
+    @Override
+    public ClassMirror getEnclosingType() {
+        return null;
+    }
+
+    @Override
+    public List<? extends ClassMirror> getSupertypes() {
+        List<ClassMirror> sups = new ArrayList<>();
+        if (clazz.getGenericSuperclass() != null) {
+            sups.add(map(clazz.getGenericSuperclass()));
+        }
+        Stream.of(clazz.getGenericInterfaces())
+                .map(this::<ClassMirror>map)
+                .collect(Collectors.toCollection(() -> sups));
+        return sups;
+    }
+
+    @Override
+    public ClassLookup classLookup() {
+        return new ClassLookupImpl(this);
+    }
+
+    @Override
+    public FieldLookup fieldLookup() {
+        return new FieldLookupImpl(this);
+    }
+
+    @Override
+    public MethodLookup methodLookup() {
+        return new MethodLookupImpl(this);
+    }
+
+    @Override
+    public ConstructorLookup constructorLookup() {
+        return new ConstructorLookupImpl(this);
+    }
+
+    @Override
+    public boolean isSpecializable() {
+        ClassMirror curr = this;
+        while (curr != null) {
+            if (!curr.getTypeArguments().isEmpty()) {
+                return true;
+            }
+            curr = (ClassMirrorImpl)curr.getEnclosingType();
+        }
+        return false;
+    }
+
+    @Override
+    public ClassMirror asSpecializedMirror(RuntimeMirror... typeArgs) {
+        List<RuntimeMirror> actualTypeArgs = Arrays.asList(typeArgs);
+        if (actualTypeArgs.size() != getTypeArguments().size()) {
+            throw new IllegalStateException();
+        }
+        ClassMirror encl = getEnclosingType();
+        if (encl != null && encl.isSpecializable()) {
+            throw new IllegalStateException("Open type variables in owner!");
+        }
+        return new SpecializedClassMirrorImpl(this, actualTypeArgs);
+    }
+
+    @Override
+    public ClassMirror asGenericMirror() {
+        return this;
+    }
+
+    @Override
+    public String getTypeString() {
+        String res = getEnclosingType() == null ?
+                super.getTypeString() :
+                getEnclosingType().getTypeString() + "." + clazz.getSimpleName();
+        if (getTypeArguments().size() > 0) {
+            res += getTypeArguments().stream()
+                    .map(RuntimeMirror::getTypeString)
+                    .collect(Collectors.joining(",", "<", ">"));
+        }
+        return res;
+    }
+
+    @Override
+    public String getName() {
+        return clazz.getSimpleName();
+    }
+
+    @Override
+    public Class<?> reflect() {
+        if (isSpecializable()) {
+            throw new IllegalStateException("Open variables!");
+        } else {
+            String encl = null;
+            if (getEnclosingType() != null) {
+                encl = getEnclosingType().reflect().getName();
+            }
+            String args = "";
+            if (getTypeArguments().size() > 0 &&
+                    getTypeArguments().stream().anyMatch(ta -> ta.getKind() != Kind.ERASED)) {
+                args = getTypeArguments().stream()
+                        .map(this::toDesc)
+                        .collect(Collectors.joining("", "$${", "}"));
+            }
+            try {
+                String prefix;
+                if (encl != null) {
+                    String innerName = clazz.getName();
+                    if (innerName.contains(".")) { //skip packages here!
+                        innerName = innerName.substring(innerName.lastIndexOf('.') + 1);
+                    }
+                    prefix = encl + "$$$" + innerName;
+                } else {
+                    prefix = clazz.getName();
+                }
+                return MirrorUtils.factory.forName(prefix + args, clazz);
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    private String toDesc(RuntimeMirror r) {
+        if (r instanceof DelegatedMirrorImpl) {
+            Class<?> c = ((DelegatedMirrorImpl)r).clazz;
+            if (c.isPrimitive()) {
+                if (c.equals(Byte.TYPE)) {
+                    return "B";
+                } else if (c.equals(Short.TYPE)) {
+                    return "S";
+                } else if (c.equals(Integer.TYPE)) {
+                    return "I";
+                } else if (c.equals(Float.TYPE)) {
+                    return "F";
+                } else if (c.equals(Long.TYPE)) {
+                    return "J";
+                } else if (c.equals(Double.TYPE)) {
+                    return "D";
+                } else if (c.equals(Character.TYPE)) {
+                    return "C";
+                } else if (c.equals(Boolean.TYPE)) {
+                    return "Z";
+                } else {
+                    throw new IllegalStateException();
+                }
+            } else {
+                return "_";
+            }
+        } else if (r.getKind() == Kind.ERASED) {
+            return "_";
+        } else {
+            throw new IllegalStateException();
+        }
+    }
+
+    private static class SpecializedClassMirrorImpl extends ClassMirrorImpl {
+
+        final ClassMirrorImpl genericMirror;
+        final List<RuntimeMirror> actualTypeArgs;
+
+        private SpecializedClassMirrorImpl(ClassMirrorImpl genericMirror, List<RuntimeMirror> actualTypeArgs) {
+            super(genericMirror.clazz);
+            this.genericMirror = genericMirror;
+            this.actualTypeArgs = actualTypeArgs;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        <Z extends RuntimeMirror> Z map(Type t) {
+            return (Z)MirrorUtils.subst(genericMirror.map(t), (List<RuntimeMirror>)genericMirror.getTypeArguments(), actualTypeArgs);
+        }
+
+        @Override
+        public boolean equals(Object that) {
+            return super.equals(that) &&
+                    genericMirror.equals(((SpecializedClassMirrorImpl)that).genericMirror);
+        }
+
+        @Override
+        public int hashCode() {
+            return super.hashCode() | genericMirror.hashCode();
+        }
+
+        @Override
+        public boolean isSpecializable() {
+            return false;
+        }
+
+        @Override
+        public ClassMirror asSpecializedMirror(RuntimeMirror... typeArgs) {
+            throw new IllegalStateException("already specialized");
+        }
+
+        @Override
+        public List<? extends RuntimeMirror> getTypeArguments() {
+            return actualTypeArgs;
+        }
+
+        @Override
+        public ClassMirror asGenericMirror() {
+            return genericMirror;
+        }
+
+        @Override
+        public ClassMirror getEnclosingType() {
+            return genericMirror.getEnclosingType();
+        }
+    }
+
+    static class InnerClassMirrorImpl extends ClassMirrorImpl {
+
+        final ClassMirrorImpl encl;
+
+        InnerClassMirrorImpl(Class<?> clazz, ClassMirrorImpl encl) {
+            super(clazz);
+            this.encl = encl;
+        }
+
+        @Override
+        <Z extends RuntimeMirror> Z map(Type t) {
+            return encl.map(t);
+        }
+
+        @Override
+        public ClassMirror getEnclosingType() {
+            return encl;
+        }
+
+        @Override
+        public boolean equals(Object that) {
+            return super.equals(that) &&
+                    encl.equals(((ClassMirror)that).getEnclosingType());
+
+        }
+
+        @Override
+        public ClassMirror asSpecializedMirror(RuntimeMirror... typeArgs) {
+            ClassMirror encl1 = encl;
+            while (encl1 != null) {
+                if (encl1.isSpecializable()) {
+                    throw new IllegalStateException("Open type variables in enclosing type!");
+                }
+                encl1 = (ClassMirrorImpl)encl1.getEnclosingType();
+            }
+            return super.asSpecializedMirror(typeArgs);
+        }
+
+        @Override
+        public int hashCode() {
+            return super.hashCode() | getEnclosingType().hashCode();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/DelegatedMirrorImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,50 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.RuntimeMirror;
+
+class DelegatedMirrorImpl implements RuntimeMirror {
+
+    final Class<?> clazz;
+
+    DelegatedMirrorImpl(Class<?> clazz) {
+        this.clazz = clazz;
+    }
+
+    @Override
+    public Kind getKind() {
+        if (clazz.isPrimitive()) {
+            return Kind.VALUE;
+        } else if (clazz.isArray()) {
+            return Kind.ARRAY;
+        } else {
+            return Kind.CLASS;
+        }
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        return that instanceof DelegatedMirrorImpl &&
+                clazz.equals(((DelegatedMirrorImpl) that).clazz);
+    }
+
+    @Override
+    public int hashCode() {
+        return clazz.hashCode();
+    }
+
+    @Override
+    public boolean isSubtypeOf(RuntimeMirror that) {
+        return that instanceof DelegatedMirrorImpl &&
+                ((DelegatedMirrorImpl) that).clazz.isAssignableFrom(clazz);
+    }
+
+    @Override
+    public String getTypeString() {
+        return clazz.getName();
+    }
+
+    @Override
+    public Class<?> reflect() {
+        return clazz;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/MirrorFactoryImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,152 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.ArrayMirror;
+import valhalla.reflect.runtime.ClassMirror;
+import valhalla.reflect.runtime.ConstructorMirror;
+import valhalla.reflect.runtime.FieldMirror;
+import valhalla.reflect.runtime.GenericMirror;
+import valhalla.reflect.runtime.MemberMirror;
+import valhalla.reflect.runtime.MethodMirror;
+import valhalla.reflect.runtime.MirrorFactory;
+import valhalla.reflect.runtime.RuntimeMirror;
+import valhalla.reflect.runtime.RuntimeMirror.Kind;
+import valhalla.reflect.runtime.TypeVariableMirror;
+import valhalla.reflect.runtime.impl.ClassMirrorImpl.InnerClassMirrorImpl;
+
+import valhalla.reflect.runtime.impl.AbstractMemberMirrorImpl.ConstructorMirrorImpl;
+import valhalla.reflect.runtime.impl.AbstractMemberMirrorImpl.FieldMirrorImpl;
+import valhalla.reflect.runtime.impl.AbstractMemberMirrorImpl.GenericMethodMirrorImpl;
+import valhalla.reflect.runtime.impl.AbstractMemberMirrorImpl.MethodMirrorImpl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.TypeVariable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class MirrorFactoryImpl implements MirrorFactory {
+
+    private final Map<Object, RuntimeMirror> type_cache = new ConcurrentHashMap<>();
+    private final Map<Object, Map<RuntimeMirror, MemberMirror>> members_cache = new ConcurrentHashMap<>();
+
+    private final RuntimeMirror erasedMirror = new TypeArgumentMirrorImpl(Kind.ERASED);
+    private final RuntimeMirror anyMirror = new TypeArgumentMirrorImpl(Kind.ANY);
+
+    private static MirrorFactoryImpl singleton;
+
+    private MirrorFactoryImpl() { }
+
+    public static MirrorFactoryImpl instance() {
+        if (singleton == null) {
+            singleton = new MirrorFactoryImpl();
+        }
+        return singleton;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <Z extends GenericMirror<Z>> TypeVariableMirror<Z> typeVarMirror(TypeVariable<?> typeVariable) {
+        return (TypeVariableMirror<Z>)type_cache.computeIfAbsent(typeVariable, tv -> new TypeVariableMirrorImpl<Z>(typeVariable));
+    }
+
+    @Override
+    public ClassMirror classMirror(Class<?> clazz) {
+        if (clazz.isArray() || clazz.isPrimitive()) {
+            throw new IllegalStateException();
+        }
+        return from(clazz);
+    }
+
+    @Override
+    public RuntimeMirror primitiveMirror(Class<?> clazz) {
+        if (!clazz.isPrimitive()) {
+            throw new IllegalStateException();
+        }
+        return from(clazz);
+    }
+
+    @Override
+    public ArrayMirror arrayMirror(RuntimeMirror componentType) {
+        return (ArrayMirror)type_cache.computeIfAbsent(componentType, tv -> new ArrayMirrorImpl(componentType));
+    }
+
+    @Override
+    public ArrayMirror arrayMirror(Class<?> clazz) {
+        if (!clazz.isArray()) {
+            throw new IllegalStateException();
+        }
+        return from(clazz);
+    }
+
+    @Override
+    public RuntimeMirror erasedMirror() {
+        return erasedMirror;
+    }
+
+    @Override
+    public RuntimeMirror anyMirror() {
+        return anyMirror;
+    }
+
+    @SuppressWarnings("unchecked")
+    <Z extends RuntimeMirror> Z from(Class<?> clazz) {
+        if (clazz.isPrimitive()) {
+            return (Z)type_cache.computeIfAbsent(clazz, tv -> new DelegatedMirrorImpl(clazz));
+        } else if (clazz.isArray()) {
+            RuntimeMirror componentType = from(clazz.getComponentType());
+            return (Z)arrayMirror(componentType);
+        } else {
+            return (Z)memberClass(MirrorUtils.needsEnclosingType(clazz) ?
+                from(clazz.getEnclosingClass()) : null, clazz);
+        }
+    }
+
+    FieldMirror field(RuntimeMirror encl, Field field) {
+        Map<RuntimeMirror, MemberMirror> fields =
+                members_cache.computeIfAbsent(field, _unused -> new ConcurrentHashMap<>());
+        return (FieldMirror)fields.computeIfAbsent(encl, _unused -> new FieldMirrorImpl((ClassMirrorImpl)encl, field));
+    }
+
+    MethodMirror method(RuntimeMirror encl, Method method) {
+        Map<RuntimeMirror, MemberMirror> methods =
+                members_cache.computeIfAbsent(method, _unused -> new ConcurrentHashMap<>());
+        return (MethodMirror)methods.computeIfAbsent(encl, _unused -> {
+            List<RuntimeMirror> avars = Stream.of(method.getTypeParameters())
+                    .filter(TypeVariable::isAny)
+                    .map(this::typeVarMirror)
+                    .collect(Collectors.toList());
+            return !avars.isEmpty() ?
+                    new GenericMethodMirrorImpl((ClassMirrorImpl)encl, method, avars) :
+                    new MethodMirrorImpl((ClassMirrorImpl)encl, method);
+        });
+    }
+
+    ConstructorMirror constructor(RuntimeMirror encl, Constructor<?> constr) {
+        Map<RuntimeMirror, MemberMirror> constructors =
+                members_cache.computeIfAbsent(constr, _unused -> new ConcurrentHashMap<>());
+        return (ConstructorMirror)constructors.computeIfAbsent(encl, _unused -> new ConstructorMirrorImpl((ClassMirrorImpl)encl, constr));
+    }
+
+    ClassMirror memberClass(RuntimeMirror encl, Class<?> memberClazz) {
+        if (MirrorUtils.needsEnclosingType(memberClazz)) {
+            Map<RuntimeMirror, MemberMirror> memberClasses =
+                    members_cache.computeIfAbsent(memberClazz, _unused -> new ConcurrentHashMap<>());
+            return (ClassMirror)memberClasses.computeIfAbsent(encl, _unused -> new InnerClassMirrorImpl(memberClazz, (ClassMirrorImpl)encl));
+        } else {
+            return (ClassMirror)type_cache.computeIfAbsent(memberClazz, tv -> new ClassMirrorImpl(memberClazz));
+        }
+    }
+
+    Class<?> forName(String name, Class<?> contextClass) throws ClassNotFoundException {
+        ClassLoader cl = contextClass.getClassLoader();
+        if (cl == null) {
+            //use boot loader
+            cl = getClass().getClassLoader();
+        }
+        return Class.forName(name, false, cl);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/MirrorUtils.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,145 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.ArrayMirror;
+import valhalla.reflect.runtime.ClassMirror;
+import valhalla.reflect.runtime.RuntimeMirror;
+import valhalla.reflect.runtime.RuntimeMirror.Kind;
+import valhalla.reflect.runtime.TypeVariableMirror;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class MirrorUtils {
+
+    static MirrorFactoryImpl factory = MirrorFactoryImpl.instance();
+
+    public static RuntimeMirror typeToMirror(Type type) {
+        if (type instanceof TypeVariable<?>) {
+            TypeVariable<?> tv = (TypeVariable<?>)type;
+            return tv.isAny() ? factory.typeVarMirror(tv) : typeToMirror(tv.getBounds()[0]);
+        } else if (type instanceof GenericArrayType) {
+            GenericArrayType array = (GenericArrayType)type;
+            RuntimeMirror elem = typeToMirror(array.getGenericComponentType());
+            return factory.arrayMirror(elem);
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType pt = (ParameterizedType)type;
+            RuntimeMirror encl = null;
+            if (pt.getOwnerType() != null) {
+                encl = typeToMirror(pt.getOwnerType());
+            }
+            TypeVariable<?>[] formals = ((Class<?>)pt.getRawType()).getTypeParameters();
+            List<RuntimeMirror> typeargs = new ArrayList<>();
+            for (int i = 0; i < formals.length ; i++) {
+                if (formals[i].isAny()) {
+                    typeargs.add(MirrorUtils.typeArgToMirror(pt.getActualTypeArguments()[i]));
+                }
+            }
+            ClassMirror mirror = encl == null ?
+                    factory.classMirror((Class<?>)pt.getRawType()) :
+                    factory.memberClass(encl, (Class<?>)pt.getRawType());
+            return typeargs.isEmpty() ?
+                    mirror :
+                    mirror.asSpecializedMirror(typeargs.stream().toArray(RuntimeMirror[]::new));
+        } else {
+            if (!(type instanceof Class<?>)) {
+                throw new IllegalStateException("Unexpected type: " + type.getTypeName());
+            }
+            Class<?> clazz = (Class<?>)type;
+            return factory.from(clazz);
+        }
+    }
+
+    private static RuntimeMirror typeArgToMirror(Type type) {
+        if (type instanceof TypeVariable<?>) {
+            TypeVariable<?> tv = (TypeVariable<?>)type;
+            return tv.isAny() ? typeToMirror(tv) : factory.erasedMirror();
+        } else if (type instanceof WildcardType) {
+            WildcardType wild = (WildcardType)type;
+            return wild.getLowerBounds().length != 0 ?
+                    typeArgToMirror(wild.getLowerBounds()[0]) : typeArgToMirror(wild.getUpperBounds()[0]);
+        } else if (type instanceof Class<?>) {
+            Class<?> clazz = (Class<?>)type;
+            return clazz.isPrimitive() ?
+                    typeToMirror(type) : factory.erasedMirror();
+        } else {
+            return factory.erasedMirror();
+        }
+    }
+
+    static RuntimeMirror subst(RuntimeMirror type, List<RuntimeMirror> from, List<RuntimeMirror> to) {
+        if (from.size() != to.size()) {
+            throw new IllegalStateException();
+        }
+        Map<RuntimeMirror, RuntimeMirror> substMap = new HashMap<>(from.size());
+        for (int i = 0 ; i < from.size() ; i++) {
+            substMap.put(from.get(i), to.get(i));
+        }
+        return subst(type, substMap, false);
+    }
+
+    private static RuntimeMirror subst(RuntimeMirror type, Map<RuntimeMirror, RuntimeMirror> substMap, boolean typeArgPos) {
+        switch (type.getKind()) {
+            case TYPEVAR: {
+                RuntimeMirror to = substMap.get(type);
+                if (to == null) {
+                    return type;
+                } else if (to.getKind() == Kind.ERASED) { //TODO: any should propagate outwards
+                    if (typeArgPos) {
+                        return to;
+                    } else {
+                        List<? extends RuntimeMirror> bounds = ((TypeVariableMirror<?>) type).getBounds();
+                        return bounds.isEmpty() ?
+                                factory.from(Object.class) : //any type-vars should erase to Object
+                                bounds.get(0);
+                    }
+                } else {
+                    return to;
+                }
+            }
+            case ARRAY: {
+                ArrayMirror arr = (ArrayMirror) type;
+                return factory.arrayMirror(subst(arr.getComponentType(), substMap, false));
+            }
+            case CLASS: {
+                ClassMirrorImpl templateClass = (ClassMirrorImpl) type;
+                List<? extends RuntimeMirror> typeArgs = templateClass.getTypeArguments();
+                if (typeArgs.size() != 0) {
+                    List<RuntimeMirror> newTypeArgs = typeArgs.stream()
+                            .map(ta -> subst(ta, substMap, true))
+                            .collect(Collectors.toList());
+                    if (!newTypeArgs.equals(typeArgs)) {
+                        return templateClass.asGenericMirror().asSpecializedMirror(newTypeArgs.toArray(new RuntimeMirror[typeArgs.size()]));
+                    }
+                }
+                return type;
+            }
+            default:
+                return type;
+        }
+    }
+
+    static ClassMirrorImpl asSuper(ClassMirrorImpl site, Class<?> sym) {
+        if (site.clazz.equals(sym)) {
+            return site;
+        } else {
+            return site.getSupertypes().stream()
+                    .map(rtm -> asSuper((ClassMirrorImpl)rtm, sym))
+                    .filter(rtm -> rtm != null)
+                    .findFirst().orElse(null);
+        }
+    }
+
+    static boolean needsEnclosingType(Class<?> clazz) {
+        return clazz.getEnclosingClass() != null && !Modifier.isStatic(clazz.getModifiers());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/TypeArgumentMirrorImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,52 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.RuntimeMirror;
+
+class TypeArgumentMirrorImpl implements RuntimeMirror {
+
+    private final Kind kind;
+
+    TypeArgumentMirrorImpl(Kind kind) {
+        this.kind = kind;
+    }
+
+    @Override
+    public Kind getKind() {
+        return kind;
+    }
+
+    @Override
+    public boolean isSubtypeOf(RuntimeMirror that) {
+        return false;
+    }
+
+    @Override
+    public String getTypeString() {
+        switch (kind) {
+            case ANY: return "?";
+            case ERASED: return "_";
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    @Override
+    public Class<?> reflect() {
+        throw new IllegalStateException();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof TypeArgumentMirrorImpl) {
+            TypeArgumentMirrorImpl that = (TypeArgumentMirrorImpl)obj;
+            return kind.equals(that.kind);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return kind.hashCode();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/impl/TypeVariableMirrorImpl.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,77 @@
+package valhalla.reflect.runtime.impl;
+
+import valhalla.reflect.runtime.GenericMirror;
+import valhalla.reflect.runtime.RuntimeMirror;
+import valhalla.reflect.runtime.TypeVariableMirror;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.TypeVariable;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+class TypeVariableMirrorImpl<S extends GenericMirror<S>> implements TypeVariableMirror<S> {
+
+    private final TypeVariable<?> typeVar;
+
+    @Override
+    public Kind getKind() {
+        return Kind.TYPEVAR;
+    }
+
+    @Override
+    public boolean isSubtypeOf(RuntimeMirror that) {
+        return equals(that) ||
+                getBounds().stream().anyMatch(b -> b.isSubtypeOf(that));
+    }
+
+    @Override
+    public Class<?> reflect() {
+        throw new IllegalStateException("Cannot reflect type-variable: " + typeVar.getName());
+    }
+
+    TypeVariableMirrorImpl(TypeVariable<?> typeVar) {
+        this.typeVar = typeVar;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof TypeVariableMirrorImpl<?>) {
+            TypeVariableMirrorImpl<?> that = (TypeVariableMirrorImpl<?>)obj;
+            return that.typeVar.equals(typeVar);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return typeVar.hashCode();
+    }
+
+    @Override
+    public List<RuntimeMirror> getBounds() {
+        return Stream.of(typeVar.getBounds())
+                .map(MirrorUtils::typeToMirror)
+                .filter(b -> !MirrorUtils.factory.from(Object.class).equals(b))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public String getTypeString() {
+        return typeVar.getName();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public S getOwner() {
+        if (typeVar.getGenericDeclaration() instanceof Method) {
+            Method method = (Method)typeVar.getGenericDeclaration();
+            return (S)MirrorUtils.factory.method(MirrorUtils.factory.classMirror(method.getDeclaringClass()), method);
+        } else if (typeVar.getGenericDeclaration() instanceof Class<?>) {
+            return (S) MirrorUtils.factory.classMirror((Class<?>) typeVar.getGenericDeclaration());
+        } else {
+            throw new IllegalStateException(); //constructor avars not supported
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/reflect/runtime/package-info.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,4 @@
+/**
+ * Provides classes and interfaces for obtaining reflective information about reifiable types.
+ */
+package valhalla.reflect.runtime;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/valhalla/test/valhalla/reflect/runtime/MirrorBuilder.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,300 @@
+package valhalla.reflect.runtime;
+
+/*
+* Copyright (c) 2016, 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.
+*
+* 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.
+*/
+
+import valhalla.reflect.runtime.impl.MirrorUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.Charset;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+public class MirrorBuilder {
+
+    Map<String, RuntimeMirror> mirror_cache = new ConcurrentHashMap<>();
+
+    static final int MAX_RETRY_COUNT = 10;
+
+    JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
+
+    ThreadLocal<JavaFileManager> jfm = new ThreadLocal<>() {
+        @Override
+        protected JavaFileManager initialValue() {
+            return comp.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
+        }
+    };
+
+    ThreadLocal<URLClassLoader> loaders = new ThreadLocal<>() {
+        @Override
+        protected URLClassLoader initialValue() {
+            try {
+                return new URLClassLoader(new URL[]{new File(tmpDirName()).toURI().toURL()}, this.getClass().getClassLoader());
+            } catch (Throwable ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+    };
+
+    AtomicInteger id = new AtomicInteger();
+
+    String pkg;
+    List<String> imports;
+    List<String> typeVarDecls;
+    List<Type> typeVariables;
+
+    public MirrorBuilder(String pkg, List<String> imports, List<String> typeVarDecls) {
+        this.pkg = pkg;
+        this.imports = imports;
+        this.typeVarDecls = typeVarDecls;
+        this.typeVariables = typeVarDecls.stream()
+                .map(this::typeVarName)
+                .map(this::getType)
+                .collect(Collectors.toList());
+    }
+
+    String tmpDirName() {
+        return "tmp" + Thread.currentThread().getId();
+    }
+
+    String typeVarName(String typeVarDecl) {
+        String[] ss = typeVarDecl.split(" ");
+        return ss[0].equals("any") ?
+                ss[1] : ss[0];
+    }
+
+    /**
+     * Returns a runtime mirror for a given string representing a valid Java type.
+     * An optional list of type-variable declarations can be provided; such variables can be used freely
+     * in the given type string.
+     */
+    @SuppressWarnings("unchecked")
+    public final <R extends RuntimeMirror> R getMirror(String type) {
+        return (R)mirror_cache.computeIfAbsent(type, _unused -> MirrorUtils.typeToMirror(getType(type)));
+    }
+
+    private final Type getType(String type) {
+        File tmp = new File(tmpDirName());
+        tmp.mkdir();
+        try {
+            JavaSource source = new JavaSource(type);
+            List<JavaFileObject> inputs = List.of(source);
+            boolean res = comp.getTask(null, jfm.get(), null, Arrays.asList("-d", tmp.getAbsolutePath()), null, inputs).call();
+            if (!res) {
+                throw new IllegalStateException("Compilation error: " + source.getCharContent(false));
+            }
+            try {
+                URLClassLoader cl = new URLClassLoader(new URL[] {new File(tmpDirName()).toURI().toURL() },
+                        this.getClass().getClassLoader());
+                Class<?> c = cl.loadClass((pkg != null ? pkg + "." : "") + "G" + source.id);
+                for (int retryCount = 0 ; ; retryCount++) {
+                    try {
+                        Type t = c.getDeclaredField("var").getGenericType();
+                        if (typeVariables != null) {
+                            t = subst(List.of(t), List.of(c.getTypeParameters()), typeVariables).get(0);
+                        }
+                        return t;
+                    } catch (ReflectiveOperationException ex) {
+                        throw new IllegalStateException(ex);
+                    } catch (Throwable ex) {
+                        //try again (workaround thread-safety issues in Model3Converter)
+                        if (retryCount == MAX_RETRY_COUNT) {
+                            throw new IllegalStateException(ex);
+                        }
+                    }
+                }
+            } catch (Throwable ex) {
+                throw new IllegalStateException(ex);
+            }
+        } finally {
+            try {
+                removeDir(tmp);
+            } catch (IOException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    class JavaSource extends SimpleJavaFileObject {
+
+        String id;
+        String type;
+        String template = "#Package;\n" +
+                "#Imports\n" +
+                "class G#Id#TypeVars {\n" +
+                "   #FieldType var;" +
+                "}";
+
+        JavaSource(String type) {
+            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
+            this.id = String.valueOf(MirrorBuilder.this.id.incrementAndGet());
+            this.type = type;
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+            String impStmts = imports.size() > 0 ?
+                    imports.stream().map(i -> "import " + i + ";").collect(Collectors.joining("\n")) : "";
+            String tvars = typeVarDecls.size() > 0 ?
+                    typeVarDecls.stream().collect(Collectors.joining(",", "<", ">")) : "";
+            return template
+                    .replace("#Package", (pkg == null) ? "" : "package " + pkg + ";")
+                    .replace("#Imports", impStmts)
+                    .replace("#Id", id)
+                    .replace("#TypeVars", tvars)
+                    .replace("#FieldType", type);
+        }
+    }
+
+    private static void removeDir(File f) throws IOException {
+        Files.walkFileTree(f.toPath(), new SimpleFileVisitor<>() {
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                Files.delete(dir);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+
+    List<Type> subst(List<Type> ts, List<Type> from, List<Type> to) {
+        return substInternal(ts, IntStream.range(0, from.size())
+            .mapToObj(i -> i) //box
+            .collect(Collectors.toMap(i -> from.get(i), i -> to.get(i))));
+    }
+
+    List<Type> substInternal(List<Type> ts, Map<Type, Type> substMap) {
+        return ts.stream()
+                .map(t -> substInternal(t, substMap))
+                .collect(Collectors.toList());
+    }
+
+    Type substInternal(Type t, Map<Type, Type> substMap) {
+        Type subst = substMap.get(t);
+        if (subst != null) {
+            return subst;
+        } else if (t instanceof ParameterizedType) {
+            ParameterizedType pt = (ParameterizedType) t;
+            List<Type> prevArgs = List.of(pt.getActualTypeArguments());
+            List<Type> substArgs = substInternal(prevArgs, substMap);
+            if (prevArgs.equals(substArgs)) {
+                return pt;
+            } else {
+                Type owner = pt.getOwnerType();
+                Type raw = pt.getRawType();
+                return new ParameterizedType() {
+                    @Override
+                    public Type[] getActualTypeArguments() {
+                        return substArgs.stream().toArray(Type[]::new);
+                    }
+
+                    @Override
+                    public Type getRawType() {
+                        return raw;
+                    }
+
+                    @Override
+                    public Type getOwnerType() {
+                        return owner;
+                    }
+                };
+            }
+        } else if (t instanceof WildcardType) {
+            WildcardType wt = (WildcardType) t;
+            List<Type> prevLower = List.of(wt.getLowerBounds());
+            List<Type> prevUpper = List.of(wt.getUpperBounds());
+            List<Type> substLower = substInternal(prevLower, substMap);
+            List<Type> substUpper = substInternal(prevUpper, substMap);
+            if (prevLower.equals(substLower) && prevUpper.equals(substUpper)) {
+                return wt;
+            } else {
+                return new WildcardType() {
+                    @Override
+                    public Type[] getUpperBounds() {
+                        return substUpper.stream().toArray(Type[]::new);
+                    }
+
+                    @Override
+                    public Type[] getLowerBounds() {
+                        return substLower.stream().toArray(Type[]::new);
+                    }
+                };
+            }
+        } else if (t instanceof GenericArrayType) {
+            GenericArrayType gt = (GenericArrayType) t;
+            Type prevElem = gt.getGenericComponentType();
+            Type substElem = substInternal(prevElem, substMap);
+            if (prevElem.equals(substElem)) {
+                return gt;
+            } else {
+                return new GenericArrayType() {
+                    @Override
+                    public Type getGenericComponentType() {
+                        return substElem;
+                    }
+                };
+            }
+        } else {
+            return t;
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        MirrorBuilder tb = new MirrorBuilder(null, List.of("valhalla.reflect.runtime.MirrorBuilder.*"), List.of("any A", "R"));
+        RuntimeMirror m1 = tb.getMirror("Foo2<A, ? super A>[]");
+        RuntimeMirror m2 = tb.getMirror("Foo2<? super A, A>[]");
+        System.err.println(m1.equals(m2));
+    }
+
+    public static class Foo2<any X, any Y> { }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/valhalla/test/valhalla/reflect/runtime/SimpleReflectionTest.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,107 @@
+package valhalla.reflect.runtime;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Test
+public class SimpleReflectionTest extends TestBase {
+
+    public static class Sup<any X, any Y> { }
+
+    public static class Foo<any X> extends Sup<X, int> {
+        public X[] doSomething(X x) {
+            System.out.println("Hello!");
+            return null;
+        }
+
+        <any Z> Z[] doGeneric(Z z, X x) {
+            System.out.println("Hello generic!");
+            return null;
+        }
+    }
+
+    final MirrorFactory factory = MirrorFactory.instance();
+    final RuntimeMirror Object_mirror = factory.classMirror(Object.class);
+    final RuntimeMirror int_mirror = factory.primitiveMirror(int.class);
+    final RuntimeMirror float_mirror = factory.primitiveMirror(float.class);
+
+    public void test() throws Throwable {
+        Class.forName("valhalla.reflect.runtime.SimpleReflectionTest$Foo");
+        ClassMirror foo_x = factory.classMirror(Foo.class);
+        Assert.assertEquals(foo_x.getTypeString(), "valhalla.reflect.runtime.SimpleReflectionTest$Foo<X>");
+        ClassMirror sup_int_X = foo_x.getSupertypes().get(0);
+        Assert.assertEquals(sup_int_X.getTypeString(), "valhalla.reflect.runtime.SimpleReflectionTest$Sup<X,int>");
+        Assert.assertNotEquals(foo_x, sup_int_X);
+        Assert.assertTrue(foo_x.isSubtypeOf(sup_int_X));
+        Assert.assertTrue(((TypeVariableMirror<?>)foo_x.getTypeArguments().get(0)).getOwner() == foo_x);
+        assertThrown(IllegalStateException.class, foo_x::reflect);
+        ClassMirror foo_erased = foo_x.asSpecializedMirror(factory.erasedMirror());
+        Assert.assertEquals(foo_erased.getTypeString(), "valhalla.reflect.runtime.SimpleReflectionTest$Foo<_>");
+        ClassMirror sup_erased_int = foo_erased.getSupertypes().get(0);
+        Assert.assertEquals(sup_erased_int.getTypeString(), "valhalla.reflect.runtime.SimpleReflectionTest$Sup<_,int>");
+        Assert.assertNotEquals(foo_erased, sup_erased_int);
+        Assert.assertTrue(foo_erased.isSubtypeOf(sup_erased_int));
+        Assert.assertNotEquals(foo_erased, equals(sup_int_X));
+        Assert.assertFalse(foo_erased.isSubtypeOf(sup_int_X));
+        foo_erased.reflect();
+        ClassMirror foo_int = foo_x.asSpecializedMirror(int_mirror);
+        Assert.assertEquals(foo_int.getTypeString(), "valhalla.reflect.runtime.SimpleReflectionTest$Foo<int>");
+        ClassMirror sup_int_int = foo_int.getSupertypes().get(0);
+        Assert.assertEquals(sup_int_int.getTypeString(), "valhalla.reflect.runtime.SimpleReflectionTest$Sup<int,int>");
+        Assert.assertNotEquals(foo_int, equals(sup_int_int));
+        Assert.assertTrue(foo_int.isSubtypeOf(sup_int_int));
+        Assert.assertNotEquals(foo_int, equals(sup_int_X));
+        Assert.assertFalse(foo_int.isSubtypeOf(sup_int_X));
+        foo_int.reflect();
+        MethodMirror foo_x_doSomething = foo_x.methodLookup()
+                .withName("doSomething")
+                .withParameterTypes(foo_x.getTypeArguments().get(0)).findOrFail();
+        Assert.assertEquals(sig(foo_x_doSomething), "(X)X[]");
+        assertThrown(IllegalStateException.class, foo_x_doSomething::reflect);
+        assertThrown(IllegalStateException.class, foo_x_doSomething::asHandle);
+        Foo<String> f_string = new Foo<String>();
+        MethodMirror foo_erased_doSomething = foo_erased.methodLookup()
+                .withName("doSomething")
+                .withParameterTypes(Object_mirror).findOrFail();
+        Assert.assertEquals(sig(foo_erased_doSomething), "(java.lang.Object)java.lang.Object[]");
+        foo_erased_doSomething.reflect().invoke(f_string, "Hello!"); //classic reflective call
+        foo_erased_doSomething.asHandle().invoke(f_string, "Hello!"); //method handle-backed reflective call
+        Foo<int> fi = new Foo<int>();
+        MethodMirror foo_int_doSomething = foo_int.methodLookup()
+                .withName("doSomething")
+                .withParameterTypes(int_mirror).findOrFail();
+        Assert.assertEquals(sig(foo_int_doSomething), "(int)int[]");
+        foo_int_doSomething.reflect().invoke(fi, 42); //classic reflective call
+        foo_int_doSomething.asHandle().invoke(fi, 42); //method handle-backed reflective call
+        MethodMirror foo_x_doGeneric_z = foo_x.methodLookup()
+                .withName("doGeneric")
+                .withParameterTypes(tvs -> Optional.of(Arrays.asList(tvs.get(0), foo_x.getTypeArguments().get(0)))).findOrFail();
+        Assert.assertTrue(((TypeVariableMirror<?>)foo_x_doGeneric_z.getTypeArguments().get(0)).getOwner() == foo_x_doGeneric_z);
+        MethodMirror foo_int_doGeneric_z = foo_int.methodLookup()
+                .withName("doGeneric")
+                .withParameterTypes(tvs -> Optional.of(Arrays.asList(tvs.get(0), int_mirror))).findOrFail();
+        Assert.assertEquals(sig(foo_int_doGeneric_z), "<Z>(Z,int)Z[]");
+        assertThrown(IllegalStateException.class, foo_int_doGeneric_z::reflect);
+        assertThrown(IllegalStateException.class, foo_int_doGeneric_z::asHandle);
+        MethodMirror foo_int_doGeneric_float = foo_int_doGeneric_z.asSpecializedMirror(float_mirror);
+        Assert.assertEquals(sig(foo_int_doGeneric_float), "<float>(float,int)float[]");
+        //m_g_int_float.reflect().invoke(fi, 1f, 42); //classic reflective call - blows up!
+        foo_int_doGeneric_float.asHandle().invoke(fi, 1f, 42); //method handle-backed reflective call
+    }
+
+    static String sig(MethodMirror mm) {
+        String sig = mm.getParameterTypes().stream()
+                .map(RuntimeMirror::getTypeString)
+                .collect(Collectors.joining(",", "(", ")")) + mm.getReturnType().getTypeString();
+        if (mm.getTypeArguments().size() > 0) {
+            sig = mm.getTypeArguments().stream()
+                    .map(RuntimeMirror::getTypeString)
+                    .collect(Collectors.joining(",", "<", ">")) + sig;
+        }
+        return sig;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/valhalla/test/valhalla/reflect/runtime/TestBase.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,215 @@
+package valhalla.reflect.runtime;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class TestBase {
+
+    MirrorBuilder B = new MirrorBuilder("valhalla.reflect.runtime",
+            List.of("valhalla.reflect.runtime." + getClass().getSimpleName() + ".*"),
+            List.of("any A", "R"));
+
+    static <X extends Throwable> void assertThrown(Class<X> ex, ThrowableAction action) {
+        try {
+            action.doSomething();
+        } catch (Throwable ex2) {
+            Assert.assertEquals(ex2.getClass(), ex);
+        }
+    }
+
+    interface ThrowableAction {
+        void doSomething() throws Throwable;
+    }
+
+    @DataProvider(name = "typeArgs", parallel = true)
+    public static Object[][] typeArgs(Method m) {
+        int arity = m.getParameterCount();
+        return typeArgsCache.computeIfAbsent(arity, _unused -> {
+            List<Object[]> res = new ArrayList<>();
+            TypeArgument[] typeargs = typeArgs();
+            Object[][] universe = new Object[arity][typeargs.length];
+            Arrays.fill(universe, typeargs);
+            initCombos(0, new Stack<>(), res, universe);
+            return res.stream().toArray(Object[][]::new);
+        });
+    }
+
+    static TypeArgument[] typeArgs() {
+        List<TypeArgument> res = new ArrayList<>();
+        for (WildcardKind wk : WildcardKind.values()) {
+            for (TypeArgumentKind tak : TypeArgumentKind.values()) {
+                if ((tak.primitive && wk != WildcardKind.NONE)) continue;
+                for (ArrayKind ak : ArrayKind.values()) {
+                    if (!tak.isSingleSlot() && ak == ArrayKind.NONE) continue;
+                    res.add(new TypeArgument(wk, tak, ak));
+                }
+            }
+        }
+        return res.stream().toArray(TypeArgument[]::new);
+    }
+
+    @DataProvider(name = "basicTypes", parallel = true)
+    public static Object[][] basicTypes(Method m) {
+        int arity = m.getParameterCount();
+        return basicTypesCache.computeIfAbsent(arity, _unused -> {
+            List<Object[]> res = new ArrayList<>();
+            BasicType[] btypes = basicTypes();
+            Object[][] universe = new Object[arity][btypes.length];
+            Arrays.fill(universe, btypes);
+            initCombos(0, new Stack<>(), res, universe);
+            return res.stream().toArray(Object[][]::new);
+        });
+    }
+
+    static BasicType[] basicTypes() {
+        BasicType[] res = new BasicType[TypeArgumentKind.values().length * ArrayKind.values().length];
+        int i = 0;
+        for (TypeArgumentKind tak : TypeArgumentKind.values()) {
+            for (ArrayKind ak : ArrayKind.values()) {
+                res[i++] = new BasicType(tak, ak);
+            }
+        }
+        return res;
+    }
+
+    protected static void initCombos(int index, Stack<Object> bindings, List<Object[]> acc, Object[]... universe) {
+        if (index == universe.length) {
+            acc.add(bindings.stream().toArray());
+        } else {
+            for (Object z : universe[index]) {
+                bindings.push(z);
+                initCombos(index + 1, bindings, acc, universe);
+                bindings.pop();
+            }
+        }
+    }
+
+    static Map<Integer, Object[][]> basicTypesCache = new ConcurrentHashMap<>();
+    static Map<Integer, Object[][]> typeArgsCache = new ConcurrentHashMap<>();
+
+    enum TypeArgumentKind {
+        STRING("java.lang.String", false, false),
+        BYTE("byte", true, true),
+        SHORT("short", true, true),
+        INT("int", true, true),
+        FLOAT("float", true, true),
+        LONG("long", true, true),
+        DOUBLE("double", true, true),
+        CHAR("char", true, true),
+        BOOL("boolean", true, true),
+        REF_VAR("R", false, false),
+        ANY_VAR("A", true, false);
+
+        String typeArg;
+        boolean reified;
+        boolean primitive;
+
+        TypeArgumentKind(String typeArg, boolean reified, boolean primitive) {
+            this.typeArg = typeArg;
+            this.reified = reified;
+            this.primitive = primitive;
+        }
+
+        boolean isSingleSlot() {
+            return this != LONG && this != DOUBLE;
+        }
+    }
+
+    enum WildcardKind {
+        NONE(""),
+        EXTENDS("? extends "),
+        SUPER("? super ");
+
+        String wildStr;
+
+        WildcardKind(String wildStr) {
+            this.wildStr = wildStr;
+        }
+    }
+
+    enum ArrayKind {
+        NONE(""),
+        SINGLE("[]"),
+        DOUBLE("[][]");
+
+        String arrStr;
+
+        ArrayKind(String arrStr) {
+            this.arrStr = arrStr;
+        }
+    }
+
+    static class TypeArgument {
+        WildcardKind wk;
+        TypeArgumentKind tak;
+        ArrayKind ak;
+
+        private final static String typeArgTemplate = "#W#T#A";
+
+        TypeArgument(WildcardKind wk, TypeArgumentKind tak, ArrayKind ak) {
+            this.wk = wk;
+            this.tak = tak;
+            this.ak = ak;
+        }
+
+        String typeArgString() {
+            return typeArgTemplate
+                    .replace("#W", wk.wildStr)
+                    .replace("#T", tak.typeArg)
+                    .replace("#A", ak.arrStr);
+        }
+
+        String erasure() {
+            return (tak.reified && ak == ArrayKind.NONE) ?
+                tak.typeArg : "_";
+        }
+
+        @Override
+        public String toString() {
+            return typeArgString();
+        }
+    }
+
+    static class BasicType {
+
+        TypeArgumentKind tak;
+        ArrayKind ak;
+
+        private final static String basicTypeTemplate = "#T#A";
+
+        BasicType(TypeArgumentKind tak, ArrayKind ak) {
+            this.tak = tak;
+            this.ak = ak;
+        }
+
+        String typeString() {
+            return basicTypeTemplate
+                    .replace("#T", tak.typeArg)
+                    .replace("#A", ak.arrStr);
+        }
+
+        String erasure() {
+            return tak == TypeArgumentKind.REF_VAR ?
+                    "java.lang.Object" + ak.arrStr :
+                    typeString();
+        }
+
+        @Override
+        public String toString() {
+            return typeString();
+        }
+
+        boolean isSingleSlot() {
+            return ak != ArrayKind.NONE ||
+                    tak.isSingleSlot();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/valhalla/test/valhalla/reflect/runtime/TestFactory.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,66 @@
+package valhalla.reflect.runtime;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@Test
+public class TestFactory extends TestBase {
+
+    static class Foo<any X> {
+        class Inner<any Y> { }
+    }
+
+    static class Foo2<any X, any Y> {
+        class Inner2<any U, any W> { }
+    }
+
+    public void testTemplate() {
+        assertTypeEquals("Foo", "valhalla.reflect.runtime.TestFactory$Foo<X>");
+        assertTypeEquals("Foo.Inner", "valhalla.reflect.runtime.TestFactory$Foo<X>.Inner<Y>");
+        assertTypeEquals("Foo[]", "valhalla.reflect.runtime.TestFactory$Foo<X>[]");
+        assertTypeEquals("Foo.Inner[]", "valhalla.reflect.runtime.TestFactory$Foo<X>.Inner<Y>[]");
+        assertTypeEquals("Foo2", "valhalla.reflect.runtime.TestFactory$Foo2<X,Y>");
+        assertTypeEquals("Foo2.Inner2", "valhalla.reflect.runtime.TestFactory$Foo2<X,Y>.Inner2<U,W>");
+        assertTypeEquals("Foo2[]", "valhalla.reflect.runtime.TestFactory$Foo2<X,Y>[]");
+        assertTypeEquals("Foo2.Inner2[]", "valhalla.reflect.runtime.TestFactory$Foo2<X,Y>.Inner2<U,W>[]");
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterized(TypeArgument ta) {
+        assertTypeEquals(String.format("Foo<%s>", ta.typeArgString()),
+                         String.format("valhalla.reflect.runtime.TestFactory$Foo<%s>", ta.erasure()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedInner(TypeArgument ta1, TypeArgument ta2) {
+        assertTypeEquals(String.format("Foo<%s>.Inner<%s>", ta1.typeArgString(), ta2.typeArgString()),
+                         String.format("valhalla.reflect.runtime.TestFactory$Foo<%s>.Inner<%s>", ta1.erasure(), ta2.erasure()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedArray(TypeArgument ta) {
+        assertTypeEquals(String.format("Foo<%s>[]", ta.typeArgString()),
+                         String.format("valhalla.reflect.runtime.TestFactory$Foo<%s>[]", ta.erasure()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedInnerArray(TypeArgument ta1, TypeArgument ta2) {
+        assertTypeEquals(String.format("Foo<%s>.Inner<%s>[]", ta1.typeArgString(), ta2.typeArgString()),
+                         String.format("valhalla.reflect.runtime.TestFactory$Foo<%s>.Inner<%s>[]", ta1.erasure(), ta2.erasure()));
+    }
+
+    @Test(dataProvider = "basicTypes")
+    public void testBasic(BasicType t) {
+        assertTypeEquals(t.typeString(), t.erasure());
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterized2(TypeArgument ta1, TypeArgument ta2) {
+        assertTypeEquals(String.format("Foo2<%s,%s>", ta1.typeArgString(), ta2.typeArgString()),
+                         String.format("valhalla.reflect.runtime.TestFactory$Foo2<%s,%s>", ta1.erasure(), ta2.erasure()));
+    }
+
+    void assertTypeEquals(String typeStr, String expected) {
+        Assert.assertEquals(B.getMirror(typeStr).getTypeString(), expected);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/valhalla/test/valhalla/reflect/runtime/TestLookup.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,210 @@
+package valhalla.reflect.runtime;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import valhalla.reflect.runtime.MemberLookup.InheritanceMode;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.Stack;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+@Test
+public class TestLookup extends TestBase {
+
+    static class Sup<any X> {
+        X m;
+        X m(X x) { return x; }
+        <any Z> Z m(Z z, X x) { return z; }
+    }
+
+    static class Foo<any X> extends Sup<X> {
+        Foo(X x) { }
+    }
+
+    @Test(dataProvider = "namedLookup")
+    public void testFieldLookup(StaticKind staticKind, AccessKind accessKind, NameKind nameKind,
+                                InheritanceMode inheritanceMode, BasicType typeArg, BasicType fieldType) {
+        lookupHelper(staticKind, accessKind, nameKind, inheritanceMode, typeArg, fieldType,
+                ClassMirror::fieldLookup,
+                (l, fieldTypeMirror) -> l.withName(nameKind.name)
+                        .withInheritanceMode(inheritanceMode)
+                        .withType(fieldTypeMirror),
+                (f, fieldTypeMirror) -> {
+                    Assert.assertEquals(f.getType(), fieldTypeMirror);
+                    Assert.assertEquals(f.getName(), nameKind.name);
+                });
+    }
+
+    @Test(dataProvider = "namedLookup")
+    public void testMethodLookupByParam(StaticKind staticKind, AccessKind accessKind, NameKind nameKind,
+                                        InheritanceMode inheritanceMode, BasicType typeArg, BasicType paramType) {
+        lookupHelper(staticKind, accessKind, nameKind, inheritanceMode, typeArg, paramType,
+                ClassMirror::methodLookup,
+                (l, paramTypeMirror) -> l.withName(nameKind.name)
+                        .withInheritanceKind(inheritanceMode)
+                        .withParameterTypes(paramTypeMirror),
+                (m, paramTypeMirror) -> {
+                    Assert.assertEquals(m.getReturnType(), paramTypeMirror);
+                    Assert.assertEquals(m.getParameterTypes().size(), 1);
+                    Assert.assertEquals(m.getParameterTypes().get(0), paramTypeMirror);
+                    Assert.assertEquals(m.getName(), nameKind.name);
+                });
+    }
+
+    @Test(dataProvider = "namedLookup")
+    public void testMethodLookupByReturn(StaticKind staticKind, AccessKind accessKind, NameKind nameKind,
+                                         InheritanceMode inheritanceMode, BasicType typeArg, BasicType paramType) {
+        lookupHelper(staticKind, accessKind, nameKind, inheritanceMode, typeArg, paramType,
+                ClassMirror::methodLookup,
+                (l, retTypeMirror) -> l.withName(nameKind.name)
+                        .withInheritanceKind(inheritanceMode)
+                        .withReturnType(retTypeMirror),
+                (m, retTypeMirror) -> {
+                    Assert.assertEquals(m.getReturnType(), retTypeMirror);
+                    Assert.assertEquals(m.getParameterTypes().size(), 1);
+                    Assert.assertEquals(m.getParameterTypes().get(0), retTypeMirror);
+                    Assert.assertEquals(m.getName(), nameKind.name);
+                });
+    }
+
+    @Test(dataProvider = "unnamedLookup")
+    public void testConstructorLookup(StaticKind staticKind, AccessKind accessKind, BasicType typeArg, BasicType paramType) {
+        lookupHelper(staticKind, accessKind, null, null, typeArg, paramType,
+                ClassMirror::constructorLookup,
+                (l, paramTypeMirror) -> l.withParameterTypes(paramTypeMirror),
+                (c, paramTypeMirror) -> {
+                    Assert.assertEquals(c.getParameterTypes().size(), 1);
+                    Assert.assertEquals(c.getParameterTypes().get(0), paramTypeMirror);
+                    Assert.assertEquals(c.getName(), "<init>");
+                });
+    }
+
+    @Test(dataProvider = "namedLookup")
+    public void testGenericMethodLookupByParam(StaticKind staticKind, AccessKind accessKind, NameKind nameKind,
+                                        InheritanceMode inheritanceMode, BasicType typeArg, BasicType paramType) {
+        lookupHelper(staticKind, accessKind, nameKind, inheritanceMode, typeArg, paramType,
+                ClassMirror::methodLookup,
+                (l, paramTypeMirror) -> l.withName(nameKind.name)
+                        .withInheritanceKind(inheritanceMode)
+                        .withParameterTypes(tvars -> (tvars.size() == 1) ?
+                                Optional.of(List.of(tvars.get(0), paramTypeMirror)) : Optional.empty()),
+                (m, paramTypeMirror) -> {
+                    Assert.assertEquals(m.getReturnType(), m.getTypeArguments().get(0));
+                    Assert.assertEquals(m.getParameterTypes().size(), 2);
+                    Assert.assertEquals(m.getParameterTypes(), List.of(m.getTypeArguments().get(0), paramTypeMirror));
+                    Assert.assertEquals(m.getName(), nameKind.name);
+                });
+    }
+
+    @Test(dataProvider = "namedLookup")
+    public void testGenericMethodLookupByReturn(StaticKind staticKind, AccessKind accessKind, NameKind nameKind,
+                                         InheritanceMode inheritanceMode, BasicType typeArg, BasicType paramType) {
+        lookupHelper(staticKind, accessKind, nameKind, inheritanceMode, typeArg, paramType,
+                ClassMirror::methodLookup,
+                (l, retTypeMirror) -> l.withName(nameKind.name)
+                        .withInheritanceKind(inheritanceMode)
+                        .withReturnType(tvars -> (tvars.size() == 1) ?
+                                Optional.of(tvars.get(0)) : Optional.empty()),
+                (m, retTypeMirror) -> {
+                    Assert.assertEquals(m.getReturnType(), m.getTypeArguments().get(0));
+                    Assert.assertEquals(m.getParameterTypes().size(), 2);
+                    Assert.assertEquals(m.getParameterTypes(), List.of(m.getTypeArguments().get(0), retTypeMirror));
+                    Assert.assertEquals(m.getName(), nameKind.name);
+                });
+    }
+
+    @SuppressWarnings("unchecked")
+    private <M extends MemberMirror,
+             L extends MemberLookup<M>> void lookupHelper(StaticKind staticKind, AccessKind accessKind, NameKind nameKindOpt,
+                                                          InheritanceMode inheritanceModeOpt, BasicType typeArg, BasicType lookupType,
+                                                          Function<ClassMirror, L> lookupFunc, BiConsumer<L, RuntimeMirror> lookupInit,
+                                                          BiConsumer<M, RuntimeMirror> lookupValidation) {
+        ClassMirror mirror = B.getMirror("Foo<" + typeArg.typeString() + ">");
+        RuntimeMirror lookupTypeMirror = B.getMirror(lookupType.typeString());
+        L lookup = lookupFunc.apply(mirror);
+        lookupInit.accept(lookup, lookupTypeMirror);
+        if (staticKind != StaticKind.NON_STATIC) {
+            lookup = (L)lookup.withFlags(staticKind.mods);
+        }
+        if (accessKind != AccessKind.PACKAGE) {
+            lookup = (L)lookup.withFlags(accessKind.mods);
+        }
+        L lookup2 = lookup;
+        if (staticKind == StaticKind.NON_STATIC && accessKind == AccessKind.PACKAGE &&
+                (nameKindOpt == null || nameKindOpt == NameKind.M) &&
+                (inheritanceModeOpt == null || inheritanceModeOpt == InheritanceMode.INHERITED) &&
+                mirror.getTypeArguments().get(0).equals(lookupTypeMirror)) {
+            M mmirror = lookup.findOrFail();
+            lookupValidation.accept(mmirror, lookupTypeMirror);
+        } else {
+            assertThrown(NoSuchElementException.class, () -> lookup2.findOrFail());
+        }
+    }
+
+    enum StaticKind {
+        STATIC(Modifier.STATIC),
+        NON_STATIC(0);
+
+        int mods;
+
+        StaticKind(int mods) {
+            this.mods = mods;
+        }
+    }
+
+    enum AccessKind {
+        PUBLIC(Modifier.PUBLIC),
+        PACKAGE(0);
+
+        int mods;
+
+        AccessKind(int mods) {
+            this.mods = mods;
+        }
+    }
+
+    enum NameKind {
+        M("m"),
+        G("g");
+
+        String name;
+
+        NameKind(String name) {
+            this.name = name;
+        }
+    }
+
+    @DataProvider(name = "namedLookup", parallel = true)
+    public static Object[][] namedLookup() {
+        if (namedLookup == null) {
+            List<Object[]> res = new ArrayList<>();
+            //we need to filter out double slot specializations which are not supported
+            initCombos(0, new Stack<>(), res, StaticKind.values(), AccessKind.values(), NameKind.values(),
+                    InheritanceMode.values(), Stream.of(basicTypes()).filter(BasicType::isSingleSlot).toArray(), basicTypes());
+            namedLookup = res.stream().toArray(Object[][]::new);
+        }
+        return namedLookup;
+    }
+
+    @DataProvider(name = "unnamedLookup", parallel = true)
+    public static Object[][] unnamedLookup() {
+        if (unnamedLookup == null) {
+            List<Object[]> res = new ArrayList<>();
+            //we need to filter out double slot specializations which are not supported
+            initCombos(0, new Stack<>(), res, StaticKind.values(), AccessKind.values(),
+                    Stream.of(basicTypes()).filter(BasicType::isSingleSlot).toArray(), basicTypes());
+            unnamedLookup = res.stream().toArray(Object[][]::new);
+        }
+        return unnamedLookup;
+    }
+
+    private static Object[][] namedLookup;
+    private static Object[][] unnamedLookup;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/valhalla/test/valhalla/reflect/runtime/TestSubtypeOf.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,103 @@
+package valhalla.reflect.runtime;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import valhalla.reflect.runtime.RuntimeMirror.Kind;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Test
+public class TestSubtypeOf extends TestBase {
+
+    static class Sup<any X> { }
+    static class Sup2<any X, any Y> { }
+
+    static class Foo<any X> extends Sup<X> { }
+    static class Foo2<any X, any Y> { }
+
+    public void testTemplate() {
+        assertSubtypeOf("Foo", "Foo");
+        assertSubtypeOf("Foo[]", "Foo[]");
+        assertSubtypeOf("Foo2", "Foo2");
+        assertSubtypeOf("Foo2[]", "Foo2[]");
+    }
+
+    @Test(dataProvider = "basicTypes")
+    public void testBasic(BasicType t1, BasicType t2) {
+        assertSubtypeOf(t1.typeString(), t2.typeString());
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterized(TypeArgument ta1, TypeArgument ta2) {
+        assertSubtypeOf(String.format("Foo<%s>", ta1.typeArgString()),
+                         String.format("Foo<%s>", ta2.typeArgString()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedArray(TypeArgument ta1, TypeArgument ta2) {
+        assertSubtypeOf(String.format("Foo<%s>[]", ta1.typeArgString()),
+                         String.format("Foo<%s>[]", ta2.typeArgString()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedSup(TypeArgument ta1, TypeArgument ta2) {
+        assertSubtypeOf(String.format("Foo<%s>", ta1.typeArgString()),
+                         String.format("Sup<%s>", ta2.typeArgString()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedSupArray(TypeArgument ta1, TypeArgument ta2) {
+        assertSubtypeOf(String.format("Foo<%s>[]", ta1.typeArgString()),
+                         String.format("Sup<%s>[]", ta2.typeArgString()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterized2(TypeArgument ta1, TypeArgument ta2) {
+        assertSubtypeOf(String.format("Foo2<%s,%s>[]", ta1.typeArgString(), ta2.typeArgString()),
+                         String.format("Foo2<%s,%s>[]", ta2.typeArgString(), ta1.typeArgString()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedSup2(TypeArgument ta1, TypeArgument ta2) {
+        assertSubtypeOf(String.format("Foo2<%s,%s>[]", ta1.typeArgString(), ta2.typeArgString()),
+                         String.format("Sup2<%s,%s>[]", ta2.typeArgString(), ta1.typeArgString()));
+    }
+
+    void assertSubtypeOf(String S, String T) {
+        RuntimeMirror Sm = B.getMirror(S);
+        RuntimeMirror Tm = B.getMirror(T);
+        Assert.assertEquals(Sm.isSubtypeOf(Tm),
+                supertypeClosure(Sm).contains(Tm));
+    }
+
+    private Map<RuntimeMirror, Set<RuntimeMirror>> invClosureMap = new ConcurrentHashMap<>();
+
+    Set<RuntimeMirror> supertypeClosure(RuntimeMirror mirror) {
+        return invClosureMap.computeIfAbsent(mirror, this::doClosure);
+    }
+
+    Set<RuntimeMirror> doClosure(RuntimeMirror mirror) {
+        Set<RuntimeMirror> cl = new HashSet<>();
+        doClosure(mirror, cl);
+        return cl;
+    }
+
+    void doClosure(RuntimeMirror mirror, Set<RuntimeMirror> cl) {
+        if (cl.add(mirror)) {
+            if (mirror.getKind() == Kind.CLASS) {
+                ((ClassMirror)mirror).getSupertypes().forEach(sup -> doClosure(sup, cl));
+            } else if (mirror.getKind() == Kind.ARRAY) {
+                ArrayMirror arr = (ArrayMirror)mirror;
+                //avoid recurring on the concurrent map!
+                Set<RuntimeMirror> elemClosure = doClosure(arr.getComponentType());
+                elemClosure.stream()
+                        .map(m -> MirrorFactory.instance().arrayMirror(m))
+                        .forEach(cl::add);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/valhalla/test/valhalla/reflect/runtime/TestTypeEquals.java	Tue Jun 21 12:43:44 2016 +0200
@@ -0,0 +1,53 @@
+package valhalla.reflect.runtime;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+@Test
+public class TestTypeEquals extends TestBase {
+
+    static class Foo<any X> { }
+    static class Foo2<any X, any Y> { }
+
+    public void testTemplate() {
+        assertTypeEquals("Foo", "Foo");
+        assertTypeEquals("Foo[]", "Foo[]");
+        assertTypeEquals("Foo2", "Foo2");
+        assertTypeEquals("Foo2[]", "Foo2[]");
+    }
+
+    @Test(dataProvider = "basicTypes")
+    public void testBasic(BasicType t1, BasicType t2) {
+        assertTypeEquals(t1.typeString(), t2.typeString());
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterized(TypeArgument ta1, TypeArgument ta2) {
+        assertTypeEquals(String.format("Foo<%s>", ta1.typeArgString()),
+                         String.format("Foo<%s>", ta2.typeArgString()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterizedArray(TypeArgument ta1, TypeArgument ta2) {
+        assertTypeEquals(String.format("Foo<%s>[]", ta1.typeArgString()),
+                         String.format("Foo<%s>[]", ta2.typeArgString()));
+    }
+
+    @Test(dataProvider = "typeArgs")
+    public void testParameterized2(TypeArgument ta1, TypeArgument ta2) {
+        assertTypeEquals(String.format("Foo2<%s,%s>[]", ta1.typeArgString(), ta2.typeArgString()),
+                         String.format("Foo2<%s,%s>[]", ta2.typeArgString(), ta1.typeArgString()));
+    }
+
+    void assertTypeEquals(String S, String T) {
+        RuntimeMirror Sm = B.getMirror(S);
+        RuntimeMirror Tm = B.getMirror(T);
+        if (Sm.getTypeString().equals(Tm.getTypeString())) {
+            Assert.assertEquals(Sm, Tm);
+        } else {
+            Assert.assertNotEquals(Sm, Tm);
+        }
+    }
+}