changeset 14071:2800c266d243

Merge
author dsimms
date Thu, 09 Jun 2016 13:08:16 +0200
parents ca246d44ad08 b0f73e4de9d6
children 00a9cf4de5b4
files
diffstat 21 files changed, 1218 insertions(+), 410 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/ClassModel.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,72 @@
+/*
+ * 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 jdk.internal.org.objectweb.asm.tree.ClassNode;
+import jdk.internal.org.objectweb.asm.tree.FieldNode;
+import jdk.internal.org.objectweb.asm.tree.MethodNode;
+
+/**
+ * ClassModel
+ *
+ * @author Brian Goetz
+ */
+public class ClassModel {
+    public final String binaryName;
+    public final ClassNode cn;
+    private ProxyClassBuilder proxyClassBuilder;
+    private Class<?> representationClass;
+
+    public ClassModel(Class<?> nativeClass) {
+        this.binaryName = nativeClass.getName();
+        this.cn = null;
+        this.representationClass = nativeClass;
+    }
+
+    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);
+    }
+
+    public ClassModel(String binaryName, ClassNode cn, Class<?> nativeClass) {
+        this.binaryName = binaryName;
+        this.representationClass = nativeClass;
+        this.cn = cn;
+    }
+
+    public Class<?> getRepresentationClass() {
+        if (representationClass == null && proxyClassBuilder != null) {
+            representationClass = proxyClassBuilder.build();
+            proxyClassBuilder = null;
+        }
+
+        return representationClass;
+    }
+}
--- a/interpreter/src/valhalla/interpreter/Interpreter.java	Thu May 19 11:05:45 2016 +0200
+++ b/interpreter/src/valhalla/interpreter/Interpreter.java	Thu Jun 09 13:08:16 2016 +0200
@@ -32,6 +32,7 @@
 import sun.misc.Unsafe;
 import valhalla.interpreter.OpcodeHandler.HandlerAction;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -39,10 +40,15 @@
 import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.*;
 
@@ -62,10 +68,40 @@
     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;
 
     // Mutable state
-    private final Map<String, ClassNode> classes = new ConcurrentHashMap<>();
+    private final Map<String, ClassModel> classes = new ConcurrentHashMap<>();
     private final Map<InvokeDynamicInsnNode, CallSite> indyCallSites = 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 void setEventFilter(Set<InterpreterEvent.Kind> eventFilter) {
+        this.eventFilter = eventFilter;
+    }
+
+    public void setEventListener(Consumer<InterpreterEvent> eventListener) {
+        this.eventListener = eventListener;
+    }
+
+    private static class InterpreterClassLoader extends URLClassLoader {
+        public final Interpreter interpreter;
+
+        public InterpreterClassLoader(Interpreter interpreter, URL[] urls, ClassLoader parent) {
+            super(urls, parent);
+            this.interpreter = interpreter;
+        }
+    }
 
     // Opcode handlers
 
@@ -286,6 +322,13 @@
         descrOf.put(clazz, descr);
     }
 
+    private void logEvent(InterpreterEvent e) {
+        if (eventListener != null)
+            eventListener.accept(e);
+        if (eventFilter.contains(e.kind))
+            log.add(e);
+    }
+
     private Class<?> toClass(Frame f, String name) throws InterpreterError {
         // FIXME: class name must be access-checked relative to current frame class
         // ...doing it neatly requires a new API point Lookup.findClass
@@ -297,7 +340,7 @@
             throw new InterpreterError(f, e);
         }
     }
-    
+
     private Class<?> toPrimClass(int basicType) throws InterpreterError {
         return Objects.requireNonNull(primClasses[basicType]);
     }
@@ -420,69 +463,85 @@
     private HandlerAction interpretOrExecute(Frame caller, MethodHandle resolved, Object[] args)
             throws InterpreterError {
         MethodHandleInfo mhi = null;
+        MethodHandle selected = null;
+        boolean interpretable = false;
+        String altOwner = null;
+
+        String debugInfo = "";
         try {
             mhi = Internal.crackMethodHandle(resolved);  // privileged cracking to get bytecodes
             switch (mhi.getReferenceKind()) {
                 case H_INVOKESTATIC:
                 case H_INVOKESPECIAL:
+                    interpretable = true;
                     break;
+
                 case H_INVOKEVIRTUAL:
                 case H_INVOKEINTERFACE:
                     // do a dispatch to find out where the bytecodes really are
-                    MethodHandle selected = doMethodSelection(mhi, args[0].getClass());
-                    if (selected != null)
-                        resolved = selected;
+                    selected = doMethodSelection(mhi, args[0].getClass());
+                    if (args[0].getClass().getName().contains("/"))
+                        altOwner = args[0].getClass().getName();
+                    interpretable = true;
                     break;
+
                 default:
-                    mhi = null;  // do not crack this kind of MH; it has no bytecodes
+                    // do not crack this kind of MH; it has no bytecodes
+                    debugInfo = mhi.toString();
+                    mhi = null;
                     break;
             }
         } catch (IllegalArgumentException ex) {
             // this can happen if selected MH if the target of an indy call site
         }
-        Frame newFrame = null;
-        MethodHandle execute = resolved;
-        if (mhi != null) {
-            ClassNode cn = classes.get(mhi.getDeclaringClass().getName());
-            if (cn != null) {
-                newFrame = makeNewFrame(caller, cn, mhi);
+        MethodHandle target = (selected != null) ? selected : resolved;
+
+        if (interpretable) {
+            ClassModel cm = classes.get(mhi.getDeclaringClass().getName());
+            if (cm != null) {
+                logEvent(InterpreterEvent.interpret(mhi.getDeclaringClass().getName(),
+                                                    mhi.getName(),
+                                                    mhi.getMethodType().toMethodDescriptorString()));
+                Frame frame = makeNewFrame(caller, cm.cn, mhi);
+                frame.storeArgs(target.type().toMethodDescriptorString(), args);
+                return interpret(frame);
             }
         }
 
-        if (newFrame != null) {
-            System.err.println("interpret:" + mhi);
-            newFrame.storeArgs(execute.type().toMethodDescriptorString(), args);
-            return interpret(newFrame);
+        if (mhi != null) {
+            logEvent(InterpreterEvent.execute(altOwner != null ? altOwner : mhi.getDeclaringClass().getName(),
+                                              mhi.getName(),
+                                              mhi.getMethodType().toMethodDescriptorString()));
         }
         else {
-            System.err.println("invoke:" + mhi);
-            assert !shouldBeInterpreted(mhi) : mhi;
-            return retOrException(() -> execute.asSpreader(Object[].class, args.length).invoke(args));
+            if (debugInfo != null && debugInfo.length() > 0)
+                logEvent(InterpreterEvent.execute(debugInfo));
         }
+        return retOrException(() -> target.asSpreader(Object[].class, args.length).invoke(args));
     }
 
-    // just for testing, to make sure we are really testing the files we mean to test
-    private boolean shouldBeInterpreted(MethodHandleInfo mhi) {
-        if (mhi == null)  return false;
-        String name = mhi.getDeclaringClass().getName();
-        return name.startsWith("valhalla.interpreter.InterpreterTest") && !name.contains("/");
+    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) {
-        boolean isStatic = mhi.getReferenceKind() == MethodHandleInfo.REF_invokeStatic;
-        String desc = mhi.getMethodType().toMethodDescriptorString();
-        Frame f = cn.methods.stream()
-                .filter(m -> m.name.equals(mhi.getName())
-                        && m.desc.equals(desc)
-                        && ((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(Internal.makeFullPowerLookup(mhi.getDeclaringClass()));
-        return f;
+        return makeNewFrame(caller, cn,
+                            Internal.makeFullPowerLookup(mhi.getDeclaringClass()),
+                            mhi.getReferenceKind() == MethodHandleInfo.REF_invokeStatic,
+                            mhi.getName(),
+                            mhi.getMethodType().toMethodDescriptorString());
     }
 
     // Helper methods for opcode handlers
@@ -621,8 +680,7 @@
     MethodHandle doMethodSelection(MethodHandle mh, Class dynamicReceiver) throws InterpreterError {
         MethodHandleInfo mhi = Internal.crackMethodHandle(mh);
         MethodHandle res = doMethodSelection(mhi, dynamicReceiver);
-        if (res != null)  mh = res;
-        return mh;
+        return (res != null) ? res : mh;
     }
     private MethodHandle doMethodSelection(MethodHandleInfo mhi, Class dynamicReceiver) throws InterpreterError {
         switch (mhi.getReferenceKind()) {
@@ -700,6 +758,10 @@
             }
         }
 
+        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);
         }
@@ -789,36 +851,84 @@
 
     // Public interpreter API
 
-    public String addClassfile(InputStream is) throws IOException {
+    public ClassModel resolveClass(String className) {
+        ClassModel model = classes.get(className.replace('/', '.'));
+        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);
+        }
+    }
+
+    public String addBootclass(Class c) throws IOException {
         ClassNode cn = new ClassNode();
-        new ClassReader(is).accept(cn, 0);
+        InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(c.getName().replace('.', '/') + ".class");
+        new ClassReader(stream).accept(cn, 0);
         String name = cn.name.replace('/', '.');
-        classes.put(name, cn);
-        System.out.println("interpreter:recording " + name);
+        classes.put(name, new ClassModel(cn.name, cn, c));
+        logEvent(InterpreterEvent.load(name));
         return name;
     }
 
-    public Object invoke(int opcode, Class<?> ownerClass, String name, String desc, Object... args) throws Throwable {
-        Lookup lookup = Internal.makeFullPowerLookup(ownerClass);
-        MethodHandle method = linkMemberAccess(lookup, opcode, ownerClass, name,
+    public Object invoke(int opcode, String ownerClass, String name, String desc, Object... args) throws Throwable {
+        ClassModel cm = resolveClass(ownerClass);
+        if (cm == null)
+            throw new InterpreterError(new ClassNotFoundException(ownerClass));
+
+        Lookup lookup = Internal.makeFullPowerLookup(cm.getRepresentationClass());
+        MethodHandle method = linkMemberAccess(lookup, opcode, cm.getRepresentationClass(), name,
                 MethodType.fromMethodDescriptorString(desc, null));
         return interpretOrExecute(new Frame(lookup), method, args).getReturnValueOrThrow();
     }
 
-    public Object invokestatic(Class<?> ownerClass, String name, String desc, Object... args) throws Throwable {
+    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());
+        boolean isStatic = (opcode == INVOKESTATIC);
+        Frame frame = makeNewFrame(new Frame(lookup), cm.cn, 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);
+        return interpret(frame);
+    }
+
+    public Object invokestatic(String ownerClass, String name, String desc, Object... args) throws Throwable {
         return invoke(Opcodes.INVOKESTATIC, ownerClass, name, desc, args);
     }
 
+    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);
+    }
 
-    public static void main(String[] args) throws Throwable {
-        Interpreter interpreter = new Interpreter();
-        String firstClassName = null;
-        for (String s : args) {
-            String name = interpreter.addClassfile(new FileInputStream(s));
-            if (firstClassName == null)
-                firstClassName = name;
-        }
-        interpreter.invokestatic(Class.forName(firstClassName), "main", "([Ljava/lang/String;)V",
-                                 (Object) new String[0]);
+    private Object callback(int opcode, String clazz, String method, String desc, Object[] args) throws Throwable {
+        return invoke(opcode, clazz, method, desc, args);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/src/valhalla/interpreter/InterpreterEvent.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,85 @@
+/*
+ * 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.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static valhalla.interpreter.InterpreterEvent.Kind.LOAD;
+
+/**
+ * InterpreterEvent
+ *
+ * @author Brian Goetz
+ */
+public class InterpreterEvent {
+    public enum Kind { LOAD, INTERPRET, EXECUTE }
+    private static final Pattern pattern = Pattern.compile("(\\w+) ([a-zA-Z0-9_.]+):(.*)");
+
+    public final Kind kind;
+    public final String clazz;
+    public final String name;
+    public final String desc;
+
+    private InterpreterEvent(Kind kind, String clazz, String name, String desc) {
+        this.kind = kind;
+        this.clazz = clazz;
+        this.name = name;
+        this.desc = desc;
+    }
+
+    public static InterpreterEvent load(String clazz) {
+        return new InterpreterEvent(LOAD, clazz, "", "");
+    }
+
+    public static InterpreterEvent interpret(String clazz, String name, String desc) {
+        return new InterpreterEvent(Kind.INTERPRET, clazz, name, desc);
+    }
+
+    public static InterpreterEvent execute(String clazz, String name, String desc) {
+        return new InterpreterEvent(Kind.EXECUTE, clazz, name, desc);
+    }
+
+    public static InterpreterEvent execute(String description) {
+        Matcher m = pattern.matcher(description);
+        if (!m.matches())
+            throw new IllegalArgumentException(description);
+        String verb = m.group(1);
+        String classAndName = m.group(2);
+        String desc = m.group(3);
+        int index = classAndName.lastIndexOf('.');
+        String clazz = classAndName.substring(0, index);
+        String name = verb + ":" + classAndName.substring(index+1);
+        return new InterpreterEvent(Kind.EXECUTE, clazz, name, desc);
+    }
+
+    @Override
+    public String toString() {
+        if (kind == LOAD)
+            return String.format("LOAD:%s", name);
+        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/ProxyClassBuilder.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,132 @@
+/*
+ * 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 jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.internal.org.objectweb.asm.Opcodes;
+import jdk.internal.org.objectweb.asm.Type;
+import jdk.internal.org.objectweb.asm.commons.GeneratorAdapter;
+import jdk.internal.org.objectweb.asm.commons.Method;
+
+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.INVOKESPECIAL;
+import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
+
+/**
+ * In this interpreter, classfiles to be intepreted are projected into a proxy
+ * class that is loaded by the underlying VM, which is used both as a representation
+ * for an interpreted class, and also contains method bodies for the interpreted
+ * classes members that call back into the interpreter.  Proxy classes are loaded
+ * into a class loader associated with an interpreter instance.
+ *
+ * The proxies of the supertypes of the class are lifted onto the proxy.
+ * The fields of the class are lifted onto the proxy.
+ * The methods of the class are lifted onto the proxy, with stub bodies that
+ * call back into the interpreter.
+ * If there is a CLINIT method, it is removed; the interpreter should emulate
+ * this method.
+ *
+ * @author Brian Goetz
+ */
+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 CLINIT_NAME = "<clinit>";
+    public static final String FAKE_INIT_NAME = "__clinit__";
+    public static final String CLINIT_DESC = "()V";
+
+    public final String className;
+    private final ClassWriter cw;
+    private final Interpreter interpreter;
+
+    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);
+    }
+
+    public static ProxyClassBuilder make(Interpreter interpreter, int access, String name, String superClass, String[] interfaces) {
+        return new ProxyClassBuilder(interpreter, access, name, superClass, interfaces);
+    }
+
+    public ProxyClassBuilder addField(int access, String name, String desc) {
+        cw.visitField(access, name, desc, null, null).visitEnd();
+        return this;
+    }
+
+    public ProxyClassBuilder addMethod(int access, String methodName, String desc) {
+        // Drop <clinit> method
+        if (methodName.equals(CLINIT_NAME) && desc.equals(CLINIT_DESC))
+            return this;
+
+        MethodVisitor mv = cw.visitMethod(access, methodName, desc, null, null);
+        Type methodType = Type.getMethodType(desc);
+        GeneratorAdapter ga = new GeneratorAdapter(mv, access, methodName, desc);
+        mv.visitCode();
+        boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+        Type[] argTypes = methodType.getArgumentTypes();
+        Type objectType = Type.getObjectType("java/lang/Object");
+        int receiverOffset = isStatic ? 0 : 1;
+
+        ga.push(Type.getObjectType(className));              // this class type
+        ga.push(isStatic ? INVOKESTATIC : INVOKESPECIAL);    // static or virtual
+        ga.push(className);                                  // invocation owner
+        ga.push(methodName);                                 // invocation name
+        ga.push(desc);                                       // invocation desc
+
+        ga.push(argTypes.length + receiverOffset);           // invocation args, to be boxed into an array
+        ga.newArray(objectType);
+        if (!isStatic) {
+            ga.dup();
+            ga.push(0);
+            ga.loadThis();
+            ga.arrayStore(objectType);
+        }
+
+        for (int i = 0; i < argTypes.length; i++) {
+            ga.dup();
+            ga.push(i + receiverOffset);
+            ga.loadArg(i);
+            ga.box(argTypes[i]);
+            ga.arrayStore(objectType);
+        }
+
+        ga.invokeStatic(Type.getObjectType(INTERPRETER_CLASS), new Method("callback", CALLBACK_DESC));
+        ga.returnValue();
+        ga.endMethod();
+
+        return this;
+    }
+
+    public Class<?> build() {
+        cw.visitEnd();
+        byte[] bytes = cw.toByteArray();
+//        new ClassReader(bytes).accept(new TraceClassVisitor(new PrintWriter(System.out)), 0);
+        return Interpreter.Internal.loadClass(interpreter, className, bytes);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-common/src/valhalla/interpreter/XRunnable.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,39 @@
+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.
+ */
+
+/**
+ * XRunnable
+ *
+ * @author Brian Goetz
+ */
+public interface XRunnable {
+    void runOrThrow() throws Throwable;
+
+    public static Class<?>[] INTERESTING_EXCEPTIONS = {
+            InternalError.class,
+            RuntimeException.class,
+            NullPointerException.class
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-common/test-common.iml	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test-helpers.iml	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="test-common" scope="TEST" />
+  </component>
+</module>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/ArrayListTestHelper.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,43 @@
+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.
+ */
+
+import java.util.ArrayList;
+
+/**
+ * InterpreterTestHelper2
+ *
+ * @author Brian Goetz
+ */
+class ArrayListTestHelper {
+    static Object main() {
+        ArrayList<String> list = new ArrayList<>();
+        list.add("a");
+        list.add("b");
+        list.add("c");
+        if (!list.contains("a"))
+            throw new AssertionError();
+        return list;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper1.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,122 @@
+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.
+ */
+
+import java.util.List;
+
+/**
+ * InterpreterTestHelper1
+ *
+ * @author Brian Goetz
+ */
+class InterpreterTestHelper1 {
+    public int i;
+    public long l;
+    public char c;
+    public float f;
+    public double d;
+    public short s;
+    public byte b;
+    public boolean z;
+    public Object o;
+
+    public InterpreterTestHelper1(int i, long l, char c, float f, double d, short s, byte b, boolean z, Object o) {
+        this.i = i;
+        this.l = l;
+        this.c = c;
+        this.f = f;
+        this.d = d;
+        this.s = s;
+        this.b = b;
+        this.z = z;
+        this.o = o;
+    }
+
+    public int instanceMethod() {
+        return 1;
+    }
+
+    public static void assertInstance(InterpreterTestHelper1 instance) {
+        if (instance.i != 3) throw new AssertionError();
+        if (instance.l != Long.MAX_VALUE) throw new AssertionError();
+        if (instance.c != 'a') throw new AssertionError();
+        if (instance.f != 3.14f) throw new AssertionError();
+        if (instance.d != 9.9999999999999D) throw new AssertionError();
+        if (instance.s != 9999) throw new AssertionError();
+        if (instance.b != 99) throw new AssertionError();
+        if (!instance.z) throw new AssertionError();
+        if (!instance.o.equals("blarg")) throw new AssertionError();
+        if (instance.instanceMethod() != 1) throw new AssertionError();
+        if (!(instance instanceof InterpreterTestHelper1)) throw new AssertionError();
+        if (!(instance instanceof Object)) throw new AssertionError();
+        if ((instance instanceof List)) throw new AssertionError();
+    }
+
+    public static int one() {
+        return 1;
+    }
+
+    public static int thirtyOne() {
+        return 31;
+    }
+
+    public static int loop() {
+        int sum = 0;
+        for (int i=0; i<5; i++)
+            sum += i;
+        return sum;
+    }
+
+    public static Class thisClass() {
+        return InterpreterTestHelper1.class;
+    }
+
+    public static int x2() {
+        return one() + one();
+    }
+
+    public static int x2(int i) {
+        return i * 2;
+    }
+
+    public static int x4(int i) {
+        return x2(i) + x2(i);
+    }
+
+    public static int x4(String i) {
+        return x4(Integer.valueOf(i));
+    }
+
+    public static int hc(String s) {
+        int h = 0;
+
+        for (int i = 0; i < s.length(); i++)
+            h = 31 * h + s.charAt(i);
+        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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper3.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,95 @@
+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.
+ */
+
+import java.util.function.Supplier;
+
+/**
+ * InterpreterTestHelper3
+ *
+ * @author Brian Goetz
+ */
+class InterpreterTestHelper3 {
+    static String testSaysHello() {
+        Supplier<String> ss = () -> "hello";
+        return ss.get();
+    }
+
+    static String testSaysHelloThere(String a) {
+        Supplier<String> ss = () -> "hello" + a;
+        return ss.get();
+    }
+
+    static int[] intArray(int n) {
+        int[] arr = new int[n];
+        for (int i=0; i<n; i++)
+            arr[i] = i;
+        return arr;
+    }
+
+    static int[][] intIntArray(int n, int m) {
+        int[][] arr = new int[n][m];
+        for (int i=0; i<n; i++)
+            for (int j=0; j<m; j++)
+                arr[i][j] = i+j;
+        return arr;
+    }
+
+    static String[] stringArray(int n) {
+        String[] arr = new String[n];
+        for (int i=0; i<n; i++)
+            arr[i] = Integer.toString(i);
+        return arr;
+    }
+
+    static String testTableSwitch(int i) {
+        switch (i) {
+            case -1: return "minus one";
+            case 0: return "zero";
+            case 1: return "one";
+            case 2: return "two";
+            case 3: return "three";
+            case 4: return "four";
+            case 5: return "five";
+        }
+        return "something funny";
+    }
+
+    static String testLookupSwitch(int i) {
+        switch (i) {
+            case 0: return "minus infinity";
+            case 1: return "zero";
+            case 10: return "one";
+            case 100: return "two";
+            case 1000: return "three";
+            case 10000: return "four";
+            case 100000: return "five";
+        }
+        return "something funny";
+    }
+
+    public static void main(String[] args) {
+        System.out.println("Foo");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper4.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,72 @@
+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.
+ */
+
+import valhalla.interpreter.XRunnable;
+
+/**
+ * InterpreterTestHelper4
+ *
+ * @author Brian Goetz
+ */
+class InterpreterTestHelper4 {
+    static int testnpe(Integer i) { return i; }
+    static int testcce(Object i) { return (Integer)i; }
+    static int testarc(char[] a, int i) { return a[i]; }
+    static void testasc(Object[] a) { a[0] = a; }
+    static int testidiv(int a, int b) { return a / b; }
+    static int testalen(float[] a) { return a.length; }
+
+    static String testCatch(Object r, Object r2) {
+        Throwable throwable = null;
+
+        try {
+            try {
+                ((XRunnable)r).runOrThrow();
+            }
+            catch (IndexOutOfBoundsException ex) {
+                return "Index";
+            }
+            ((XRunnable)r2).runOrThrow();
+        }
+        catch (NullPointerException ex) {
+            return "Null";
+        }
+        catch (RuntimeException ex) {
+            return "Runtime";
+        }
+        catch (InternalError ex) {
+            return "Internal";
+        }
+        catch (Throwable ex) {
+            throwable = ex;  // caught something bad
+            ex.printStackTrace();
+        }
+        finally {
+            if (throwable != null)
+                return "Random error: " + throwable;
+        }
+        return "Normal";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test-helpers/test/valhalla/interpreter/InterpreterTestHelper5.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,35 @@
+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/valhalla/interpreter/InterpretBootclassTest.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,96 @@
+/*
+ * 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.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
+ *
+ * @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 void testArrayList() throws Throwable {
+        interpreter.addBootclass(ArrayList.class);
+
+        Object o = interpreter.invokestatic("valhalla/interpreter/ArrayListTestHelper", "main", "()Ljava/lang/Object;");
+        assertTrue(o instanceof ArrayList);
+        assertEquals("[a, b, c]", o.toString());
+
+        // Assertions against log stream
+
+        List<String> interpretedMethods
+                = interpreter.log.stream()
+                                 .filter(e -> e.kind == InterpreterEvent.Kind.INTERPRET)
+                                 .filter(e -> e.clazz.equals("java.util.ArrayList"))
+                                 .map(e -> e.name)
+                                 .collect(toList());
+        assertTrue(interpretedMethods.contains("<init>"));
+        assertTrue(interpretedMethods.contains("add"));
+        assertTrue(interpretedMethods.contains("contains"));
+
+        interpreter.log.stream()
+                       .filter(e -> e.kind == InterpreterEvent.Kind.EXECUTE)
+                       .filter(e -> e.clazz.equals("java.util.AbstractList"))
+                       .map(e -> e.name)
+                       .findAny()
+                       .orElseThrow(() -> new AssertionError("Didn't find EXECUTE AbstractList.<init>"));
+
+        for (InterpreterEvent event : interpreter.log) {
+        }
+    }
+}
--- a/interpreter/test/valhalla/interpreter/InterpreterTest.java	Thu May 19 11:05:45 2016 +0200
+++ b/interpreter/test/valhalla/interpreter/InterpreterTest.java	Thu Jun 09 13:08:16 2016 +0200
@@ -24,24 +24,17 @@
  */
 package valhalla.interpreter;
 
-import java.io.InputStream;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandleInfo;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
 import java.lang.reflect.Constructor;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
+import java.lang.reflect.Field;
+import java.util.EnumSet;
 
-import jdk.internal.org.objectweb.asm.Opcodes;
-import jdk.nashorn.internal.ir.RuntimeNode;
-import org.testng.annotations.BeforeTest;
+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
@@ -50,174 +43,161 @@
  */
 @Test
 public class InterpreterTest {
+    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";
+
     Interpreter interpreter;
 
-    private InputStream asStream(Class c) {
-        ClassLoader loader = c.getClassLoader();
-        if (loader == null)
-            loader = ClassLoader.getSystemClassLoader();
-        return loader.getResourceAsStream(c.getName().replace('.', '/') + ".class");
+    @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));
     }
 
-    @BeforeTest
-    public void setUp() {
-        interpreter = new Interpreter();
+    @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 {
-        Class c = InterpreterTestHelper1.class;
-        interpreter.addClassfile(asStream(c));
-        Object o = interpreter.invokestatic(c, "one", "()I");
+        Object o = interpreter.invokestatic(HELPER_1, "one", "()I");
         assertEquals((int) (Integer) o, 1);
 
-        o = interpreter.invokestatic(c, "thirtyOne", "()I");
+        o = interpreter.invokestatic(HELPER_1, "thirtyOne", "()I");
         assertEquals((int) (Integer) o, 31);
 
-        o = interpreter.invokestatic(c, "loop", "()I");
+        o = interpreter.invokestatic(HELPER_1, "loop", "()I");
         assertEquals((int) (Integer) o, 10);
 
-        o = interpreter.invokestatic(c, "x2", "()I");
+        o = interpreter.invokestatic(HELPER_1, "x2", "()I");
         assertEquals((int) (Integer) o, 2);
 
-        o = interpreter.invokestatic(c, "x2", "(I)I", 1);
+        o = interpreter.invokestatic(HELPER_1, "x2", "(I)I", 1);
         assertEquals((int) (Integer) o, 2);
 
-        o = interpreter.invokestatic(c, "x4", "(I)I", 2);
+        o = interpreter.invokestatic(HELPER_1, "x4", "(I)I", 2);
         assertEquals((int) (Integer) o, 8);
 
-        o = interpreter.invokestatic(c, "x4", "(Ljava/lang/String;)I", "2");
+        o = interpreter.invokestatic(HELPER_1, "x4", "(Ljava/lang/String;)I", "2");
         assertEquals((int) (Integer) o, 8);
 
-        o = interpreter.invokestatic(c, "hc", "(Ljava/lang/String;)I", "arglebargle");
+        o = interpreter.invokestatic(HELPER_1, "hc", "(Ljava/lang/String;)I", "arglebargle");
         assertEquals((int) (Integer) o, "arglebargle".hashCode());
 
-        o = interpreter.invokestatic(c, "thisClass", "()Ljava/lang/Class;");
-        assertEquals(o, InterpreterTestHelper1.class);
+        o = interpreter.invokestatic(HELPER_1, "thisClass", "()Ljava/lang/Class;");
+        assertEquals(o, interpreter.resolveClass(HELPER_1).getRepresentationClass());
 
-        InterpreterTestHelper1 instance = (InterpreterTestHelper1)
-                interpreter.invokestatic(c, "make", "(IJCFDSBZLjava/lang/Object;)Lvalhalla/interpreter/InterpreterTestHelper1;",
+        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");
-        assertEquals(instance.i, 3);
-        assertEquals(instance.l, Long.MAX_VALUE);
-        assertEquals(instance.c, 'a');
-        assertEquals(instance.f, 3.14f);
-        assertEquals(instance.d, 9.9999999999999D);
-        assertEquals(instance.s, 9999);
-        assertEquals(instance.b, 99);
-        assertEquals(instance.z, true);
-        assertEquals(instance.o, "blarg");
+        Class<?> ic = instance.getClass();
+        Field f;
+        f = ic.getDeclaredField("i"); f.setAccessible(true);
+        assertEquals(f.get(instance), 3);
+        f = ic.getDeclaredField("l"); f.setAccessible(true);
+        assertEquals(f.get(instance), Long.MAX_VALUE);
+        f = ic.getDeclaredField("c"); f.setAccessible(true);
+        assertEquals(f.get(instance), 'a');
+        f = ic.getDeclaredField("f"); f.setAccessible(true);
+        assertEquals(f.get(instance), 3.14f);
+        f = ic.getDeclaredField("d"); f.setAccessible(true);
+        assertEquals(f.get(instance), 9.9999999999999D);
+        f = ic.getDeclaredField("s"); f.setAccessible(true);
+        assertEquals(f.get(instance), (short) 9999);
+        f = ic.getDeclaredField("b"); f.setAccessible(true);
+        assertEquals(f.get(instance), (byte) 99);
+        f = ic.getDeclaredField("z"); f.setAccessible(true);
+        assertEquals(f.get(instance), true);
+        f = ic.getDeclaredField("o"); f.setAccessible(true);
+        assertEquals(f.get(instance), "blarg");
 
-        interpreter.invokestatic(c, "assertInstance", "(Lvalhalla/interpreter/InterpreterTestHelper1;)V", instance);
-    }
-
-    public void testArrayList() throws Throwable {
-        interpreter.addClassfile(asStream(InterpreterTestHelper2.class));
-        interpreter.addClassfile(asStream(ArrayList.class));
-
-        interpreter.invokestatic(InterpreterTestHelper2.class, "main", "()V");
+        interpreter.invokestatic(HELPER_1, "assertInstance", "(L" + HELPER_1 + ";)V", instance);
     }
 
     public void testLambda() throws Throwable {
-        interpreter.addClassfile(asStream(InterpreterTestHelper3.class));
-
-        assertEquals(interpreter.invokestatic(InterpreterTestHelper3.class, "testSaysHello", "()Ljava/lang/String;"), "hello");
-        assertEquals(interpreter.invokestatic(InterpreterTestHelper3.class, "testSaysHelloThere", "(Ljava/lang/String;)Ljava/lang/String;", "there"), "hellothere");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testSaysHello", "()Ljava/lang/String;"), "hello");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testSaysHelloThere", "(Ljava/lang/String;)Ljava/lang/String;", "there"), "hellothere");
     }
 
     public void testSwitch() throws Throwable {
-        interpreter.addClassfile(asStream(InterpreterTestHelper3.class));
-
-        assertEquals(interpreter.invokestatic(InterpreterTestHelper3.class, "testTableSwitch", "(I)Ljava/lang/String;", 1), "one");
-        assertEquals(interpreter.invokestatic(InterpreterTestHelper3.class, "testTableSwitch", "(I)Ljava/lang/String;", 5), "five");
-        assertEquals(interpreter.invokestatic(InterpreterTestHelper3.class, "testLookupSwitch", "(I)Ljava/lang/String;", 1), "zero");
-        assertEquals(interpreter.invokestatic(InterpreterTestHelper3.class, "testLookupSwitch", "(I)Ljava/lang/String;", 1000), "three");
+        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, "testLookupSwitch", "(I)Ljava/lang/String;", 1), "zero");
+        assertEquals(interpreter.invokestatic(HELPER_3, "testLookupSwitch", "(I)Ljava/lang/String;", 1000), "three");
     }
 
     public void testArray() throws Throwable {
-        interpreter.addClassfile(asStream(InterpreterTestHelper3.class));
-
-        int[] ia = (int[]) interpreter.invokestatic(InterpreterTestHelper3.class, "intArray", "(I)[I", 10);
+        int[] ia = (int[]) interpreter.invokestatic(HELPER_3, "intArray", "(I)[I", 10);
         assertEquals(ia.length, 10);
         for (int i=0; i<10; i++)
             assertEquals(ia[i], i);
 
-        String[] sa = (String[]) interpreter.invokestatic(InterpreterTestHelper3.class, "stringArray", "(I)[Ljava/lang/String;", 10);
+        String[] sa = (String[]) interpreter.invokestatic(HELPER_3, "stringArray", "(I)[Ljava/lang/String;", 10);
         assertEquals(ia.length, 10);
         for (int i=0; i<10; i++)
             assertEquals(Integer.parseInt(sa[i]), i);
 
-        int[][] iia = (int[][]) interpreter.invokestatic(InterpreterTestHelper3.class, "intIntArray", "(II)[[I", 10, 10);
+        int[][] iia = (int[][]) interpreter.invokestatic(HELPER_3, "intIntArray", "(II)[[I", 10, 10);
         assertEquals(iia.length, 10);
         for (int i=0; i<10; i++)
             for (int j=0; j<10; j++)
                 assertEquals(iia[i][j], i+j);
     }
 
-    private void assertDeclaredIn(MethodHandle mh, Class<?> clazz) {
-        MethodHandleInfo mhi = Interpreter.Internal.crackMethodHandle(mh);
-        assertEquals(mhi.getDeclaringClass(), clazz);
-    }
-
-    public void testResolve() throws Throwable {
-        MethodHandles.Lookup lookup = MethodHandles.lookup();
-        // ArrayList.size declared in and resolves to ArrayList.size
-        MethodHandle arrayListSize = interpreter.linkMemberAccess(lookup, Opcodes.INVOKEVIRTUAL, ArrayList.class, "size", MethodType.methodType(int.class));
-        assertDeclaredIn(arrayListSize, ArrayList.class);
-        assertDeclaredIn(interpreter.doMethodSelection(arrayListSize, ArrayList.class), ArrayList.class);
-
-        // ArrayList.hashCode declared in and resolves to AbstractList.hashCode
-        MethodHandle arrayListHashCode = interpreter.linkMemberAccess(lookup, Opcodes.INVOKEVIRTUAL, ArrayList.class, "hashCode", MethodType.methodType(int.class));
-        assertDeclaredIn(arrayListHashCode, AbstractList.class);
-        assertDeclaredIn(interpreter.doMethodSelection(arrayListHashCode, ArrayList.class), AbstractList.class);
-
-        // List.hashCode declared in Object; when invoked on ArrayList, resolves to AbstractList.hashCode
-        MethodHandle listHashCode = interpreter.linkMemberAccess(lookup, Opcodes.INVOKEVIRTUAL, List.class, "hashCode", MethodType.methodType(int.class));
-        assertDeclaredIn(listHashCode, Object.class);
-        assertDeclaredIn(interpreter.doMethodSelection(listHashCode, ArrayList.class), AbstractList.class);
-    }
-
     public void testImplicits() throws Throwable {
-        final Class<InterpreterTestHelper4> cls = InterpreterTestHelper4.class;
-        interpreter.addClassfile(asStream(cls));
-
         // null pointer exception
         Object nullo = null;
-        assertEquals(interpreter.invokestatic(cls, "testnpe", "(Ljava/lang/Integer;)I", 3), 3);
+        assertEquals(interpreter.invokestatic(HELPER_4, "testnpe", "(Ljava/lang/Integer;)I", 3), 3);
         assertThrows(NullPointerException.class, () ->
-                interpreter.invokestatic(cls, "testnpe", "(Ljava/lang/Integer;)I", nullo));
-        assertEquals(interpreter.invokestatic(cls, "testalen", "([F)I", new float[7]), 7);
+                interpreter.invokestatic(HELPER_4, "testnpe", "(Ljava/lang/Integer;)I", nullo));
+        assertEquals(interpreter.invokestatic(HELPER_4, "testalen", "([F)I", new float[7]), 7);
         assertThrows(NullPointerException.class, () ->
-                interpreter.invokestatic(cls, "testalen", "([F)I", nullo));
+                interpreter.invokestatic(HELPER_4, "testalen", "([F)I", nullo));
 
         // class cast exception
-        assertEquals(interpreter.invokestatic(cls, "testcce", "(Ljava/lang/Object;)I", 3), 3);
+        assertEquals(interpreter.invokestatic(HELPER_4, "testcce", "(Ljava/lang/Object;)I", 3), 3);
         assertThrows(ClassCastException.class, () ->
-                interpreter.invokestatic(cls, "testcce", "(Ljava/lang/Object;)I", "foo"));
+                interpreter.invokestatic(HELPER_4, "testcce", "(Ljava/lang/Object;)I", "foo"));
         assertThrows(NullPointerException.class, () ->
-                interpreter.invokestatic(cls, "testcce", "(Ljava/lang/Object;)I", nullo));
+                interpreter.invokestatic(HELPER_4, "testcce", "(Ljava/lang/Object;)I", nullo));
 
         // array range check
-        assertEquals(interpreter.invokestatic(cls, "testarc", "([CI)I", "abc".toCharArray(), 2), (int) 'c');
+        assertEquals(interpreter.invokestatic(HELPER_4, "testarc", "([CI)I", "abc".toCharArray(), 2), (int) 'c');
         assertThrows(ArrayIndexOutOfBoundsException.class, () ->
-            interpreter.invokestatic(cls, "testarc", "([CI)I", "abc".toCharArray(), -2));
+            interpreter.invokestatic(HELPER_4, "testarc", "([CI)I", "abc".toCharArray(), -2));
         assertThrows(ArrayIndexOutOfBoundsException.class, () ->
-                interpreter.invokestatic(cls, "testarc", "([CI)I", "abc".toCharArray(), 22));
+                interpreter.invokestatic(HELPER_4, "testarc", "([CI)I", "abc".toCharArray(), 22));
         assertThrows(NullPointerException.class, () ->
-                interpreter.invokestatic(cls, "testarc", "([CI)I", null, -2));
+                interpreter.invokestatic(HELPER_4, "testarc", "([CI)I", null, -2));
 
         // array store check
         Object obja = new Object[1], stra = new String[1];
-        assertEquals(interpreter.invokestatic(cls, "testasc", "([Ljava/lang/Object;)V", obja), null);
+        assertEquals(interpreter.invokestatic(HELPER_4, "testasc", "([Ljava/lang/Object;)V", obja), null);
         assertThrows(ArrayStoreException.class, () ->
-                interpreter.invokestatic(cls, "testasc", "([Ljava/lang/Object;)V", stra));
+                interpreter.invokestatic(HELPER_4, "testasc", "([Ljava/lang/Object;)V", stra));
         assertThrows(NullPointerException.class, () ->
-                interpreter.invokestatic(cls, "testasc", "([Ljava/lang/Object;)V", nullo));
+                interpreter.invokestatic(HELPER_4, "testasc", "([Ljava/lang/Object;)V", nullo));
 
         // divide by zero
-        assertEquals(interpreter.invokestatic(cls, "testidiv", "(II)I", 8, 2), 4);
-        assertEquals(interpreter.invokestatic(cls, "testidiv", "(II)I", 0, 3), 0);
+        assertEquals(interpreter.invokestatic(HELPER_4, "testidiv", "(II)I", 8, 2), 4);
+        assertEquals(interpreter.invokestatic(HELPER_4, "testidiv", "(II)I", 0, 3), 0);
         assertThrows(ArithmeticException.class, () ->
-            interpreter.invokestatic(cls, "testidiv", "(II)I", 4, 0));
+            interpreter.invokestatic(HELPER_4, "testidiv", "(II)I", 4, 0));
     }
 
     private void assertThrows(Class<? extends Throwable> exc, XRunnable test) throws Throwable {
@@ -231,12 +211,10 @@
     }
 
     public void testCatch() throws Throwable {
-        final Class<InterpreterTestHelper4> cls = InterpreterTestHelper4.class;
-        interpreter.addClassfile(asStream(cls));
         XRunnable donothing = () -> { };
         final String testCatchSig = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;";
         {
-            String res = (String) interpreter.invokestatic(cls, "testCatch", testCatchSig, donothing, donothing);
+            String res = (String) interpreter.invokestatic(HELPER_4, "testCatch", testCatchSig, donothing, donothing);
             assertEquals(res, "Normal");
         }
         for (Class<?> exc : XRunnable.INTERESTING_EXCEPTIONS) {
@@ -251,226 +229,15 @@
                 throw ex;
             };
             String res;
-            res = (String) interpreter.invokestatic(cls, "testCatch", testCatchSig, dothrow, dothrow);
+            res = (String) interpreter.invokestatic(HELPER_4, "testCatch", testCatchSig, dothrow, dothrow);
             assertTrue(exc.getSimpleName().startsWith(res), res);
-            res = (String) interpreter.invokestatic(cls, "testCatch", testCatchSig, donothing, dothrow);
+            res = (String) interpreter.invokestatic(HELPER_4, "testCatch", testCatchSig, donothing, dothrow);
             assertTrue(exc.getSimpleName().startsWith(res), res);
-            res = (String) interpreter.invokestatic(cls, "testCatch", testCatchSig, dothrow, donothing);
+            res = (String) interpreter.invokestatic(HELPER_4, "testCatch", testCatchSig, dothrow, donothing);
             assertTrue(exc.getSimpleName().startsWith(res), res);
         }
     }
 
 }
 
-class 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) {
-        this.i = i;
-        this.l = l;
-        this.c = c;
-        this.f = f;
-        this.d = d;
-        this.s = s;
-        this.b = b;
-        this.z = z;
-        this.o = o;
-    }
-
-    public int instanceMethod() {
-        return 1;
-    }
-
-    public static void assertInstance(InterpreterTestHelper1 instance) {
-        if (instance.i != 3) throw new AssertionError();
-        if (instance.l != Long.MAX_VALUE) throw new AssertionError();
-        if (instance.c != 'a') throw new AssertionError();
-        if (instance.f != 3.14f) throw new AssertionError();
-        if (instance.d != 9.9999999999999D) throw new AssertionError();
-        if (instance.s != 9999) throw new AssertionError();
-        if (instance.b != 99) throw new AssertionError();
-        if (!instance.z) throw new AssertionError();
-        if (!instance.o.equals("blarg")) throw new AssertionError();
-        if (instance.instanceMethod() != 1) throw new AssertionError();
-        if (!(instance instanceof InterpreterTestHelper1)) throw new AssertionError();
-        if (!(instance instanceof Object)) throw new AssertionError();
-        if ((instance instanceof List)) throw new AssertionError();
-    }
-
-    public static int one() {
-        return 1;
-    }
-
-    public static int thirtyOne() {
-        return 31;
-    }
-
-    public static int loop() {
-        int sum = 0;
-        for (int i=0; i<5; i++)
-            sum += i;
-        return sum;
-    }
-
-    public static Class thisClass() {
-        return InterpreterTestHelper1.class;
-    }
-
-    public static int x2() {
-        return one() + one();
-    }
-
-    public static int x2(int i) {
-        return i * 2;
-    }
-
-    public static int x4(int i) {
-        return x2(i) + x2(i);
-    }
-
-    public static int x4(String i) {
-        return x4(Integer.valueOf(i));
-    }
-
-    public static int hc(String s) {
-        int h = 0;
-
-        for (int i = 0; i < s.length(); i++)
-            h = 31 * h + s.charAt(i);
-        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);
-    }
-}
-
-interface XRunnable {
-    void runOrThrow() throws Throwable;
-    Class<?>[] INTERESTING_EXCEPTIONS = {
-            InternalError.class,
-            RuntimeException.class,
-            NullPointerException.class
-    };
-}
-
-interface XSupplier<T> {
-    T getOrThrow() throws Throwable;
-}
-
-class InterpreterTestHelper2 {
-    static void main() {
-        ArrayList<String> list = new ArrayList<>();
-        list.add("a");
-        list.add("b");
-        list.add("c");
-        if (!list.contains("a"))
-            throw new AssertionError();
-    }
-}
-
-class InterpreterTestHelper3 {
-    static String testSaysHello() {
-        Supplier<String> ss = () -> "hello";
-        return ss.get();
-    }
-
-    static String testSaysHelloThere(String a) {
-        Supplier<String> ss = () -> "hello" + a;
-        return ss.get();
-    }
-
-    static int[] intArray(int n) {
-        int[] arr = new int[n];
-        for (int i=0; i<n; i++)
-            arr[i] = i;
-        return arr;
-    }
-
-    static int[][] intIntArray(int n, int m) {
-        int[][] arr = new int[n][m];
-        for (int i=0; i<n; i++)
-            for (int j=0; j<m; j++)
-                arr[i][j] = i+j;
-        return arr;
-    }
-
-    static String[] stringArray(int n) {
-        String[] arr = new String[n];
-        for (int i=0; i<n; i++)
-            arr[i] = Integer.toString(i);
-        return arr;
-    }
-
-    static String testTableSwitch(int i) {
-        switch (i) {
-            case -1: return "minus one";
-            case 0: return "zero";
-            case 1: return "one";
-            case 2: return "two";
-            case 3: return "three";
-            case 4: return "four";
-            case 5: return "five";
-        }
-        return "something funny";
-    }
-
-    static String testLookupSwitch(int i) {
-        switch (i) {
-            case 0: return "minus infinity";
-            case 1: return "zero";
-            case 10: return "one";
-            case 100: return "two";
-            case 1000: return "three";
-            case 10000: return "four";
-            case 100000: return "five";
-        }
-        return "something funny";
-    }
-}
-
-
-class InterpreterTestHelper4 {
-    static int testnpe(Integer i) { return i; }
-    static int testcce(Object i) { return (Integer)i; }
-    static int testarc(char[] a, int i) { return a[i]; }
-    static void testasc(Object[] a) { a[0] = a; }
-    static int testidiv(int a, int b) { return a / b; }
-    static int testalen(float[] a) { return a.length; }
-    static String testCatch(Object r, Object r2) {
-        boolean ok = true;
-        try {
-            try {
-                ((XRunnable)r).runOrThrow();
-            }
-            catch (IndexOutOfBoundsException ex) {
-                return "Index";
-            }
-            ((XRunnable)r2).runOrThrow();
-        }
-        catch (NullPointerException ex) {
-            return "Null";
-        }
-        catch (RuntimeException ex) {
-            return "Runtime";
-        }
-        catch (InternalError ex) {
-            return "Internal";
-        }
-        catch (Throwable ex) {
-            ok = false;  // caught something bad
-        }
-        finally {
-            if (!ok)  return "Random??";
-        }
-        return ok ? "Normal" : "Not OK";
-    }
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interpreter/test/valhalla/interpreter/ResolveTest.java	Thu Jun 09 13:08:16 2016 +0200
@@ -0,0 +1,71 @@
+/*
+ * 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.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+import jdk.internal.org.objectweb.asm.Opcodes;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * ResolveTest
+ *
+ * @author Brian Goetz
+ */
+@Test
+public class ResolveTest {
+    public void testResolve() throws Throwable {
+        Interpreter interpreter = new Interpreter();
+
+        MethodHandles.Lookup lookup = MethodHandles.lookup();
+        // ArrayList.size declared in and resolves to ArrayList.size
+        MethodHandle arrayListSize = interpreter.linkMemberAccess(lookup, Opcodes.INVOKEVIRTUAL, ArrayList.class, "size", MethodType.methodType(int.class));
+        assertDeclaredIn(arrayListSize, ArrayList.class);
+        assertDeclaredIn(interpreter.doMethodSelection(arrayListSize, ArrayList.class), ArrayList.class);
+
+        // ArrayList.hashCode declared in and resolves to AbstractList.hashCode
+        MethodHandle arrayListHashCode = interpreter.linkMemberAccess(lookup, Opcodes.INVOKEVIRTUAL, ArrayList.class, "hashCode", MethodType.methodType(int.class));
+        assertDeclaredIn(arrayListHashCode, AbstractList.class);
+        assertDeclaredIn(interpreter.doMethodSelection(arrayListHashCode, ArrayList.class), AbstractList.class);
+
+        // List.hashCode declared in Object; when invoked on ArrayList, resolves to AbstractList.hashCode
+        MethodHandle listHashCode = interpreter.linkMemberAccess(lookup, Opcodes.INVOKEVIRTUAL, List.class, "hashCode", MethodType.methodType(int.class));
+        assertDeclaredIn(listHashCode, Object.class);
+        assertDeclaredIn(interpreter.doMethodSelection(listHashCode, ArrayList.class), AbstractList.class);
+    }
+
+    private void assertDeclaredIn(MethodHandle mh, Class<?> clazz) {
+        MethodHandleInfo mhi = Interpreter.Internal.crackMethodHandle(mh);
+        assertEquals(mhi.getDeclaringClass(), clazz);
+    }
+}
--- a/src/java.base/share/classes/java/lang/Arrayish.java	Thu May 19 11:05:45 2016 +0200
+++ b/src/java.base/share/classes/java/lang/Arrayish.java	Thu Jun 09 13:08:16 2016 +0200
@@ -30,10 +30,9 @@
  *
  * @author Brian Goetz
  */
+@SuppressWarnings("serial")
 public interface Arrayish<any T> extends Cloneable, java.io.Serializable {
 
-    static final long serialVersionUID = 292L + 4711L;
-
     default int arraySize() {
         @SuppressWarnings("unchecked")
         T[] array = (T[]) (Object) this;
--- a/src/java.base/share/classes/java/lang/invoke/GenericStaticDispatch.java	Thu May 19 11:05:45 2016 +0200
+++ b/src/java.base/share/classes/java/lang/invoke/GenericStaticDispatch.java	Thu Jun 09 13:08:16 2016 +0200
@@ -12,7 +12,6 @@
                                        Object... args) throws ReflectiveOperationException {
         DispatchContext dispatchContext = new DispatchContext(invokedName, invokedType, caller, args);
         Class<?> inner = Class.forName(dispatchContext.innerName(dispatchContext.receiverClass), false, dispatchContext.getLoader());
-        Object receiver = inner.newInstance();
-        return new ConstantCallSite(caller.findVirtual(inner, invokedName, invokedType).bindTo(receiver));
+        return new ConstantCallSite(caller.findStatic(inner, invokedName, invokedType));
     }
 }
--- a/src/java.base/share/classes/valhalla/model3/Model3Converter.java	Thu May 19 11:05:45 2016 +0200
+++ b/src/java.base/share/classes/valhalla/model3/Model3Converter.java	Thu Jun 09 13:08:16 2016 +0200
@@ -275,9 +275,14 @@
                     .toArray(Method[]::new);
             Field[] fields = Stream.of(cf.fields)
                     .filter(f -> needsField(erased, f))
+                    .map(m -> mangleField(newCp, m))
                     .toArray(Field[]::new);
+            AccessFlags access = cf.access_flags;
+            if (access.is(AccessFlags.ACC_SPECIES)) {
+                access = access.ignore(AccessFlags.ACC_SPECIES).or(AccessFlags.ACC_STATIC);
+            }
             ClassFile outClass = new ClassFile(cf.magic, 0, cf.major_version,
-                                               newCp, cf.access_flags, cf.this_class, cf.super_class, cf.interfaces,
+                                               newCp, access, cf.this_class, cf.super_class, cf.interfaces,
                                                fields, mangledMethods, cf.attributes);
 
             ClassWriter cw = new ClassWriter();
@@ -295,9 +300,10 @@
     }
 
     boolean needsMethod(boolean erased, String[] bindings, ConstantPool cp, Method m) {
-        if (!erased && m.access_flags.is(AccessFlags.ACC_STATIC)) {
-            //check for static specializable methods (species statics)
-            return m.attributes.get(Attribute.SpecializerSignature) != null;
+        if (erased && getUTF8(m.name_index, cp).equals("<sclinit>")) {
+            return false;
+        } else if (!erased && m.access_flags.is(AccessFlags.ACC_STATIC)) {
+            return false;
         } else {
             Where_attribute where_attribute = (Where_attribute)m.attributes.get(Attribute.Where);
             if (where_attribute != null) {
@@ -412,6 +418,14 @@
         }
     }
 
+    private Field mangleField(ConstantPool cp, Field f) {
+        AccessFlags access = f.access_flags;
+        if (access.is(AccessFlags.ACC_SPECIES)) {
+            access = access.ignore(AccessFlags.ACC_SPECIES).or(AccessFlags.ACC_STATIC);
+        }
+        return new Field(access, f.name_index, f.descriptor, f.attributes);
+    }
+
     private Method mangleMethod(ConstantPool cp, Method m) {
         Attribute[] attrs = m.attributes.attrs.clone();
         boolean changed = false;
@@ -425,7 +439,20 @@
         if (!changed)
             return m;
 
-        return new Method(m.access_flags, m.name_index, m.descriptor, new Attributes(cp, attrs));
+        int name_index = m.name_index;
+        if (getUTF8(name_index, cp).equals("<sclinit>")) {
+            try {
+                name_index = cp.getUTF8Index("<clinit>");
+            } catch (Throwable ex) { }
+        }
+
+        AccessFlags access = m.access_flags;
+        if (access.is(AccessFlags.ACC_SPECIES)) {
+            access = access.ignore(AccessFlags.ACC_SPECIES).or(AccessFlags.ACC_STATIC);
+        }
+
+
+        return new Method(access, name_index, m.descriptor, new Attributes(cp, attrs));
     }
 
     private Code_attribute mangleCode(Code_attribute old, ConstantPool cp) {
--- a/src/java.base/share/classes/valhalla/model3/classfile/AccessFlags.java	Thu May 19 11:05:45 2016 +0200
+++ b/src/java.base/share/classes/valhalla/model3/classfile/AccessFlags.java	Thu Jun 09 13:08:16 2016 +0200
@@ -60,7 +60,8 @@
     public static final int ACC_ANNOTATION    = 0x2000; // class, inner
     public static final int ACC_VALUEFACTORY  = 0x2000; //                      method
     public static final int ACC_ENUM          = 0x4000; // class, inner, field
-    public static final int ACC_MANDATED      = 0x8000; // class, inner, field, method
+    public static final int ACC_MANDATED      = 0x8000; // method parameters
+    public static final int ACC_SPECIES       = 0x8000; // class, inner, field, method
 
     public static enum Kind { Class, InnerClass, Field, Method}
 
@@ -72,8 +73,12 @@
         this.flags = flags;
     }
 
-    public valhalla.model3.classfile.AccessFlags ignore(int mask) {
-        return new valhalla.model3.classfile.AccessFlags(flags & ~mask);
+    public AccessFlags ignore(int mask) {
+        return new AccessFlags(flags & ~mask);
+    }
+
+    public AccessFlags or(int mask) {
+        return new AccessFlags(flags | mask);
     }
 
     public boolean is(int mask) {
@@ -104,12 +109,12 @@
 
     private static final int[] innerClassModifiers = {
         ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,
-        ACC_ABSTRACT, ACC_VALUE
+        ACC_ABSTRACT, ACC_VALUE, ACC_SPECIES
     };
 
     private static final int[] innerClassFlags = {
         ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SUPER,
-        ACC_INTERFACE, ACC_ABSTRACT, ACC_SYNTHETIC, ACC_ANNOTATION, ACC_ENUM, ACC_VALUE
+        ACC_INTERFACE, ACC_ABSTRACT, ACC_SYNTHETIC, ACC_ANNOTATION, ACC_ENUM, ACC_VALUE, ACC_SPECIES
     };
 
     public Set<String> getInnerClassModifiers() {
@@ -123,12 +128,12 @@
 
     private static final int[] fieldModifiers = {
         ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,
-        ACC_VOLATILE, ACC_TRANSIENT
+        ACC_VOLATILE, ACC_TRANSIENT, ACC_SPECIES
     };
 
     private static final int[] fieldFlags = {
         ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,
-        ACC_VOLATILE, ACC_TRANSIENT, ACC_SYNTHETIC, ACC_ENUM
+        ACC_VOLATILE, ACC_TRANSIENT, ACC_SYNTHETIC, ACC_ENUM, ACC_SPECIES
     };
 
     public Set<String> getFieldModifiers() {
@@ -141,13 +146,13 @@
 
     private static final int[] methodModifiers = {
         ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,
-        ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT, ACC_STRICT
+        ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT, ACC_STRICT, ACC_SPECIES
     };
 
     private static final int[] methodFlags = {
         ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,
         ACC_SYNCHRONIZED, ACC_BRIDGE, ACC_VARARGS, ACC_NATIVE, ACC_ABSTRACT,
-        ACC_STRICT, ACC_SYNTHETIC, ACC_VALUEFACTORY
+        ACC_STRICT, ACC_SYNTHETIC, ACC_VALUEFACTORY, ACC_SPECIES
     };
 
     public Set<String> getMethodModifiers() {
@@ -212,8 +217,8 @@
                 return "abstract";
             case ACC_STRICT:
                 return "strictfp";
-            case ACC_MANDATED:
-                return "mandated";
+            case ACC_SPECIES:
+                return "species";
             default:
                 return null;
         }
@@ -251,8 +256,8 @@
             return (t == Kind.Method ? "ACC_VALUEFACTORY" : "ACC_ANNOTATION");
         case ACC_ENUM:
             return "ACC_ENUM";
-        case ACC_MANDATED:
-            return "ACC_MANDATED";
+        case ACC_SPECIES:
+            return "ACC_SPECIES";
         default:
             return null;
         }
--- a/test/java/lang/Class/ArrayMethods.java	Thu May 19 11:05:45 2016 +0200
+++ b/test/java/lang/Class/ArrayMethods.java	Thu Jun 09 13:08:16 2016 +0200
@@ -31,6 +31,9 @@
 import java.util.Arrays;
 
 public class ArrayMethods {
+
+    static final boolean USE_ARRAYISH = true;
+
     public int failed = 0;
 
     public static void main(String[] args) throws Exception {
@@ -89,8 +92,9 @@
     public void testGetInterfaces() {
         Class<?>[] is = new Integer[0].getClass().getInterfaces();
         boolean thisFailed = false;
+        int expectedLen = USE_ARRAYISH ? 3 : 2;
 
-        if (is.length != 2)
+        if (is.length != expectedLen)
             thisFailed = true;
 
         if (!is[0].getCanonicalName().equals("java.lang.Cloneable"))
@@ -99,10 +103,21 @@
         if (!is[1].getCanonicalName().equals("java.io.Serializable"))
             thisFailed = true;
 
+        if (USE_ARRAYISH) {
+            if (!is[2].getCanonicalName().equals("java.lang.Arrayish")) {
+                thisFailed = true;
+            }
+        }
+
         if (thisFailed) {
             failed++;
             System.out.println(Arrays.asList(is));
-            System.out.println("Should contain exactly Cloneable, Serializable in that order.");
+            if (USE_ARRAYISH) {
+                System.out.println("Should contain exactly Cloneable, Serializable, Arrayish in that order.");
+            }
+            else {
+                System.out.println("Should contain exactly Cloneable, Serializable in that order.");
+            }
         }
     }
 }
--- a/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java	Thu May 19 11:05:45 2016 +0200
+++ b/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java	Thu Jun 09 13:08:16 2016 +0200
@@ -223,8 +223,8 @@
             return roots.stream().flatMap(this::toStream)
                     .filter(x -> x.getNameCount() > 2)
                     .map( x-> x.subpath(2, x.getNameCount()))
-                    .map( x -> x.toString())
-                    .filter(s -> s.endsWith(".class"));
+                    .map( x -> x.toString()) // Skip "__Value" (temp proto class, only partial VM suport)
+                    .filter(s -> s.endsWith(".class") && (!s.endsWith("__Value.class")));
         }
 
         @Override