8187742: Minimal set of bootstrap methods for constant dynamic
authorpsandoz
Wed, 31 Jan 2018 11:20:36 -0800
changeset 48827 8772acd913e5
parent 48826 c4d9d1b08e2e
child 48828 5fcc602d36b6
8187742: Minimal set of bootstrap methods for constant dynamic Reviewed-by: jrose, forax Contributed-by: brian.goetz@oracle.com, paul.sandoz@oracle.com
src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java
src/java.base/share/classes/java/lang/invoke/DynamicConstant.java
src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java
src/java.base/share/classes/sun/invoke/util/Wrapper.java
test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java
test/jdk/java/lang/invoke/condy/ConstantBootstrapsTest.java
test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/BytePoolHelper.java
test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/IsolatedMethodBuilder.java
test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/PoolHelper.java
test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/TypedCodeBuilder.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java	Wed Jan 31 11:20:36 2018 -0800
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2017, 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 java.lang.invoke;
+
+import sun.invoke.util.Wrapper;
+
+import static java.lang.invoke.MethodHandleNatives.mapLookupExceptionToError;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Bootstrap methods for dynamically-computed constants.
+ *
+ * <p>The bootstrap methods in this class will throw a
+ * {@code NullPointerException} for any reference argument that is {@code null},
+ * unless the argument is specified to be unused or specified to accept a
+ * {@code null} value.
+ *
+ * @since 10
+ */
+public final class ConstantBootstraps {
+    // implements the upcall from the JVM, MethodHandleNatives.linkDynamicConstant:
+    /*non-public*/
+    static Object makeConstant(MethodHandle bootstrapMethod,
+                               // Callee information:
+                               String name, Class<?> type,
+                               // Extra arguments for BSM, if any:
+                               Object info,
+                               // Caller information:
+                               Class<?> callerClass) {
+        // BSMI.invoke handles all type checking and exception translation.
+        // If type is not a reference type, the JVM is expecting a boxed
+        // version, and will manage unboxing on the other side.
+        return BootstrapMethodInvoker.invoke(
+                type, bootstrapMethod, name, type, info, callerClass);
+    }
+
+    /**
+     * Returns a {@code null} object reference for the reference type specified
+     * by {@code type}.
+     *
+     * @param lookup unused
+     * @param name unused
+     * @param type a reference type
+     * @return a {@code null} value
+     * @throws IllegalArgumentException if {@code type} is not a reference type
+     */
+    public static Object nullConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+        if (requireNonNull(type).isPrimitive()) {
+            throw new IllegalArgumentException(String.format("not reference: %s", type));
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a {@link Class} mirror for the primitive type whose type
+     * descriptor is specified by {@code name}.
+     *
+     * @param lookup unused
+     * @param name the descriptor (JVMS 4.3) of the desired primitive type
+     * @param type the required result type (must be {@code Class.class})
+     * @return the {@link Class} mirror
+     * @throws IllegalArgumentException if the name is not a descriptor for a
+     * primitive type or the type is not {@code Class.class}
+     */
+    public static Class<?> primitiveClass(MethodHandles.Lookup lookup, String name, Class<?> type) {
+        requireNonNull(name);
+        requireNonNull(type);
+        if (type != Class.class) {
+            throw new IllegalArgumentException();
+        }
+        if (name.length() == 0 || name.length() > 1) {
+            throw new IllegalArgumentException(String.format("not primitive: %s", name));
+        }
+
+        return Wrapper.forPrimitiveType(name.charAt(0)).primitiveType();
+    }
+
+    /**
+     * Returns an {@code enum} constant of the type specified by {@code type}
+     * with the name specified by {@code name}.
+     *
+     * @param lookup the lookup context describing the class performing the
+     * operation (normally stacked by the JVM)
+     * @param type the {@code Class} object describing the enum type for which
+     * a constant is to be returned
+     * @param name the name of the constant to return, which must exactly match
+     * an enum constant in the specified type.
+     * @param <E> The enum type for which a constant value is to be returned
+     * @return the enum constant of the specified enum type with the
+     * specified name
+     * @throws IllegalAccessError if the declaring class or the field is not
+     * accessible to the class performing the operation
+     * @throws IllegalArgumentException if the specified enum type has
+     * no constant with the specified name, or the specified
+     * class object does not represent an enum type
+     * @see Enum#valueOf(Class, String)
+     */
+    public static <E extends Enum<E>> E enumConstant(MethodHandles.Lookup lookup, String name, Class<E> type) {
+        requireNonNull(lookup);
+        requireNonNull(name);
+        requireNonNull(type);
+        validateClassAccess(lookup, type);
+
+        return Enum.valueOf(type, name);
+    }
+
+    /**
+     * Returns the value of a static final field.
+     *
+     * @param lookup the lookup context describing the class performing the
+     * operation (normally stacked by the JVM)
+     * @param name the name of the field
+     * @param type the type of the field
+     * @param declaringClass the class in which the field is declared
+     * @return the value of the field
+     * @throws IllegalAccessError if the declaring class or the field is not
+     * accessible to the class performing the operation
+     * @throws NoSuchFieldError if the specified field does not exist
+     * @throws IncompatibleClassChangeError if the specified field is not
+     * {@code final}
+     */
+    public static Object getStaticFinal(MethodHandles.Lookup lookup, String name, Class<?> type,
+                                        Class<?> declaringClass) {
+        requireNonNull(lookup);
+        requireNonNull(name);
+        requireNonNull(type);
+        requireNonNull(declaringClass);
+
+        MethodHandle mh;
+        try {
+            mh = lookup.findStaticGetter(declaringClass, name, type);
+            MemberName member = mh.internalMemberName();
+            if (!member.isFinal()) {
+                throw new IncompatibleClassChangeError("not a final field: " + name);
+            }
+        }
+        catch (ReflectiveOperationException ex) {
+            throw mapLookupExceptionToError(ex);
+        }
+
+        // Since mh is a handle to a static field only instances of
+        // VirtualMachineError are anticipated to be thrown, such as a
+        // StackOverflowError or an InternalError from the j.l.invoke code
+        try {
+            return mh.invoke();
+        }
+        catch (RuntimeException | Error e) {
+            throw e;
+        }
+        catch (Throwable e) {
+            throw new LinkageError("Unexpected throwable", e);
+        }
+    }
+
+    /**
+     * Returns the value of a static final field declared in the class which
+     * is the same as the field's type (or, for primitive-valued fields,
+     * declared in the wrapper class.)  This is a simplified form of
+     * {@link #getStaticFinal(MethodHandles.Lookup, String, Class, Class)}
+     * for the case where a class declares distinguished constant instances of
+     * itself.
+     *
+     * @param lookup the lookup context describing the class performing the
+     * operation (normally stacked by the JVM)
+     * @param name the name of the field
+     * @param type the type of the field
+     * @return the value of the field
+     * @throws IllegalAccessError if the declaring class or the field is not
+     * accessible to the class performing the operation
+     * @throws NoSuchFieldError if the specified field does not exist
+     * @throws IncompatibleClassChangeError if the specified field is not
+     * {@code final}
+     * @see #getStaticFinal(MethodHandles.Lookup, String, Class, Class)
+     */
+    public static Object getStaticFinal(MethodHandles.Lookup lookup, String name, Class<?> type) {
+        requireNonNull(type);
+
+        Class<?> declaring = type.isPrimitive()
+                             ? Wrapper.forPrimitiveType(type).wrapperType()
+                             : type;
+        return getStaticFinal(lookup, name, type, declaring);
+    }
+
+
+    /**
+     * Returns the result of invoking a method handle with the provided
+     * arguments.
+     *
+     * @param lookup the lookup context describing the class performing the
+     * operation (normally stacked by the JVM)
+     * @param name unused
+     * @param type the type of the value to be returned, which must be
+     * compatible with the return type of the method handle
+     * @param handle the method handle to be invoked
+     * @param args the arguments to pass to the method handle, as if with
+     * {@link MethodHandle#invokeWithArguments}.  Each argument may be
+     * {@code null}.
+     * @return the result of invoking the method handle
+     * @throws WrongMethodTypeException if the handle's return type cannot be
+     * adjusted to the desired type
+     * @throws ClassCastException if an argument cannot be converted by
+     * reference casting
+     * @throws Throwable anything thrown by the method handle invocation
+     */
+    public static Object invoke(MethodHandles.Lookup lookup, String name, Class<?> type,
+                                MethodHandle handle, Object... args) throws Throwable {
+        requireNonNull(type);
+        requireNonNull(handle);
+        requireNonNull(args);
+
+        if (type != handle.type().returnType()) {
+            handle = handle.asType(handle.type().changeReturnType(type));
+        }
+
+        return handle.invokeWithArguments(args);
+    }
+
+    /**
+     * Finds a {@link VarHandle} for an instance field.
+     *
+     * @param lookup the lookup context describing the class performing the
+     * operation (normally stacked by the JVM)
+     * @param name the name of the field
+     * @param type the required result type (must be {@code Class<VarHandle>})
+     * @param declaringClass the class in which the field is declared
+     * @param fieldType the type of the field
+     * @return the {@link VarHandle}
+     * @throws IllegalAccessError if the declaring class or the field is not
+     * accessible to the class performing the operation
+     * @throws NoSuchFieldError if the specified field does not exist
+     * @throws IllegalArgumentException if the type is not {@code VarHandle}
+     */
+    public static VarHandle fieldVarHandle(MethodHandles.Lookup lookup, String name, Class<VarHandle> type,
+                                           Class<?> declaringClass, Class<?> fieldType) {
+        requireNonNull(lookup);
+        requireNonNull(name);
+        requireNonNull(type);
+        requireNonNull(declaringClass);
+        requireNonNull(fieldType);
+        if (type != VarHandle.class) {
+            throw new IllegalArgumentException();
+        }
+
+        try {
+            return lookup.findVarHandle(declaringClass, name, fieldType);
+        }
+        catch (ReflectiveOperationException e) {
+            throw mapLookupExceptionToError(e);
+        }
+    }
+
+    /**
+     * Finds a {@link VarHandle} for a static field.
+     *
+     * @param lookup the lookup context describing the class performing the
+     * operation (normally stacked by the JVM)
+     * @param name the name of the field
+     * @param type the required result type (must be {@code Class<VarHandle>})
+     * @param declaringClass the class in which the field is declared
+     * @param fieldType the type of the field
+     * @return the {@link VarHandle}
+     * @throws IllegalAccessError if the declaring class or the field is not
+     * accessible to the class performing the operation
+     * @throws NoSuchFieldError if the specified field does not exist
+     * @throws IllegalArgumentException if the type is not {@code VarHandle}
+     */
+    public static VarHandle staticFieldVarHandle(MethodHandles.Lookup lookup, String name, Class<VarHandle> type,
+                                                 Class<?> declaringClass, Class<?> fieldType) {
+        requireNonNull(lookup);
+        requireNonNull(name);
+        requireNonNull(type);
+        requireNonNull(declaringClass);
+        requireNonNull(fieldType);
+        if (type != VarHandle.class) {
+            throw new IllegalArgumentException();
+        }
+
+        try {
+            return lookup.findStaticVarHandle(declaringClass, name, fieldType);
+        }
+        catch (ReflectiveOperationException e) {
+            throw mapLookupExceptionToError(e);
+        }
+    }
+
+    /**
+     * Finds a {@link VarHandle} for an array type.
+     *
+     * @param lookup the lookup context describing the class performing the
+     * operation (normally stacked by the JVM)
+     * @param name unused
+     * @param type the required result type (must be {@code Class<VarHandle>})
+     * @param arrayClass the type of the array
+     * @return the {@link VarHandle}
+     * @throws IllegalAccessError if the component type of the array is not
+     * accessible to the class performing the operation
+     * @throws IllegalArgumentException if the type is not {@code VarHandle}
+     */
+    public static VarHandle arrayVarHandle(MethodHandles.Lookup lookup, String name, Class<VarHandle> type,
+                                           Class<?> arrayClass) {
+        requireNonNull(lookup);
+        requireNonNull(type);
+        requireNonNull(arrayClass);
+        if (type != VarHandle.class) {
+            throw new IllegalArgumentException();
+        }
+
+        return MethodHandles.arrayElementVarHandle(validateClassAccess(lookup, arrayClass));
+    }
+
+    private static <T> Class<T> validateClassAccess(MethodHandles.Lookup lookup, Class<T> type) {
+        try {
+            lookup.accessClass(type);
+            return type;
+        }
+        catch (ReflectiveOperationException ex) {
+            throw mapLookupExceptionToError(ex);
+        }
+    }
+}
--- a/src/java.base/share/classes/java/lang/invoke/DynamicConstant.java	Fri Sep 08 10:46:46 2017 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2017, 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 java.lang.invoke;
-
-/**
- * Bootstrap methods for dynamically-computed constant.
- */
-final class DynamicConstant {
-    // implements the upcall from the JVM, MethodHandleNatives.linkDynamicConstant:
-    /*non-public*/
-    static Object makeConstant(MethodHandle bootstrapMethod,
-                               // Callee information:
-                               String name, Class<?> type,
-                               // Extra arguments for BSM, if any:
-                               Object info,
-                               // Caller information:
-                               Class<?> callerClass) {
-        // BSMI.invoke handles all type checking and exception translation.
-        // If type is not a reference type, the JVM is expecting a boxed
-        // version, and will manage unboxing on the other side.
-        return BootstrapMethodInvoker.invoke(
-                type, bootstrapMethod, name, type, info, callerClass);
-    }
-}
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java	Fri Sep 08 10:46:46 2017 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java	Wed Jan 31 11:20:36 2018 -0800
@@ -310,7 +310,7 @@
                                           MethodHandle bootstrapMethod,
                                           String name, Class<?> type,
                                           Object staticArguments) {
-        return DynamicConstant.makeConstant(bootstrapMethod, name, type, staticArguments, caller);
+        return ConstantBootstraps.makeConstant(bootstrapMethod, name, type, staticArguments, caller);
     }
 
     private static String staticArglistForTrace(Object staticArguments) {
--- a/src/java.base/share/classes/sun/invoke/util/Wrapper.java	Fri Sep 08 10:46:46 2017 -0700
+++ b/src/java.base/share/classes/sun/invoke/util/Wrapper.java	Wed Jan 31 11:20:36 2018 -0800
@@ -273,6 +273,25 @@
         throw newIllegalArgumentException("not primitive: "+type);
     }
 
+    /** Return the wrapper that corresponds to the provided basic type char.
+     *  The basic type char must be for one of the eight primitive types, or void.
+     *  @throws IllegalArgumentException for unexpected types
+     */
+    public static Wrapper forPrimitiveType(char basicTypeChar) {
+        switch (basicTypeChar) {
+            case 'I': return INT;
+            case 'J': return LONG;
+            case 'S': return SHORT;
+            case 'B': return BYTE;
+            case 'C': return CHAR;
+            case 'F': return FLOAT;
+            case 'D': return DOUBLE;
+            case 'Z': return BOOLEAN;
+            case 'V': return VOID;
+            default: throw newIllegalArgumentException("not primitive: " + basicTypeChar);
+        }
+    }
+
     static Wrapper findPrimitiveType(Class<?> type) {
         Wrapper w = FROM_PRIM[hashPrim(type)];
         if (w != null && w.primitiveType == type) {
--- a/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java	Fri Sep 08 10:46:46 2017 -0700
+++ b/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java	Wed Jan 31 11:20:36 2018 -0800
@@ -90,16 +90,30 @@
                                                   String name, Class<?> type,
                                                   String bsmMethodName, MethodType bsmType,
                                                   Consumer<PoolHelper.StaticArgListBuilder<String, String, byte[]>> staticArgs) throws Exception {
-        return ldcDynamicConstant(l, name, cref(type), bsmMethodName, bsmType.toMethodDescriptorString(), staticArgs);
+        return ldcDynamicConstant(l, name, type, l.lookupClass(), bsmMethodName, bsmType, staticArgs);
+    }
+
+    public static MethodHandle ldcDynamicConstant(MethodHandles.Lookup l,
+                                                  String name, Class<?> type,
+                                                  Class<?> bsmClass, String bsmMethodName, MethodType bsmType,
+                                                  Consumer<PoolHelper.StaticArgListBuilder<String, String, byte[]>> staticArgs) throws Exception {
+        return ldcDynamicConstant(l, name, cref(type), csym(bsmClass), bsmMethodName, bsmType.toMethodDescriptorString(), staticArgs);
     }
 
     public static MethodHandle ldcDynamicConstant(MethodHandles.Lookup l,
                                                   String name, String type,
                                                   String bsmMethodName, String bsmType,
                                                   Consumer<PoolHelper.StaticArgListBuilder<String, String, byte[]>> staticArgs) throws Exception {
+        return ldcDynamicConstant(l, name, type, csym(l.lookupClass()), bsmMethodName, bsmType, staticArgs);
+    }
+
+    public static MethodHandle ldcDynamicConstant(MethodHandles.Lookup l,
+                                                  String name, String type,
+                                                  String bsmClass, String bsmMethodName, String bsmType,
+                                                  Consumer<PoolHelper.StaticArgListBuilder<String, String, byte[]>> staticArgs) throws Exception {
         return ldc(l, type,
                    P -> P.putDynamicConstant(name, type,
-                                             csym(l.lookupClass()), bsmMethodName, bsmType,
+                                             bsmClass, bsmMethodName, bsmType,
                                              staticArgs));
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/condy/ConstantBootstrapsTest.java	Wed Jan 31 11:20:36 2018 -0800
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2017, 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.
+ */
+
+/*
+ * @test
+ * @bug 8186046
+ * @summary Test dynamic constant bootstraps
+ * @library /lib/testlibrary/bytecode /java/lang/invoke/common
+ * @build jdk.experimental.bytecode.BasicClassBuilder test.java.lang.invoke.lib.InstructionHelper
+ * @run testng ConstantBootstrapsTest
+ * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:UseBootstrapCallInfo=3 ConstantBootstrapsTest
+ */
+
+import jdk.experimental.bytecode.PoolHelper;
+import org.testng.annotations.Test;
+import test.java.lang.invoke.lib.InstructionHelper;
+
+import java.lang.invoke.ConstantBootstraps;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleInfo;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.lang.invoke.WrongMethodTypeException;
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Map;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+@Test
+public class ConstantBootstrapsTest {
+    static final MethodHandles.Lookup L = MethodHandles.lookup();
+
+    static MethodType lookupMT(Class<?> ret, Class<?>... params) {
+        return MethodType.methodType(ret, MethodHandles.Lookup.class, String.class, Class.class).
+                appendParameterTypes(params);
+    }
+
+    public void testNullConstant() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(L, "_", Object.class,
+                                                          ConstantBootstraps.class, "nullConstant", lookupMT(Object.class),
+                                                          S -> {});
+        assertNull(handle.invoke());
+
+        handle = InstructionHelper.ldcDynamicConstant(L, "_", MethodType.class,
+                                                      ConstantBootstraps.class, "nullConstant", lookupMT(Object.class),
+                                                      S -> {});
+        assertNull(handle.invoke());
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testNullConstantPrimitiveClass() {
+        ConstantBootstraps.nullConstant(MethodHandles.lookup(), null, int.class);
+    }
+
+
+    public void testPrimitiveClass() throws Throwable {
+        var pm = Map.of(
+                "I", int.class,
+                "J", long.class,
+                "S", short.class,
+                "B", byte.class,
+                "C", char.class,
+                "F", float.class,
+                "D", double.class,
+                "Z", boolean.class,
+                "V", void.class
+        );
+
+        for (var desc : pm.keySet()) {
+            var handle = InstructionHelper.ldcDynamicConstant(L, desc, Class.class,
+                                                              ConstantBootstraps.class, "primitiveClass", lookupMT(Class.class),
+                                                              S -> {});
+            assertEquals(handle.invoke(), pm.get(desc));
+        }
+    }
+
+    @Test(expectedExceptions = NullPointerException.class)
+    public void testPrimitiveClassNullName() {
+        ConstantBootstraps.primitiveClass(MethodHandles.lookup(), null, Class.class);
+    }
+
+    @Test(expectedExceptions = NullPointerException.class)
+    public void testPrimitiveClassNullType() {
+        ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "I", null);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testPrimitiveClassEmptyName() {
+        ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "", Class.class);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testPrimitiveClassWrongNameChar() {
+        ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "L", Class.class);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testPrimitiveClassWrongNameString() {
+        ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "Ljava/lang/Object;", Class.class);
+    }
+
+
+    public void testEnumConstant() throws Throwable {
+        for (var v : StackWalker.Option.values()) {
+            var handle = InstructionHelper.ldcDynamicConstant(L, v.name(), StackWalker.Option.class,
+                                                              ConstantBootstraps.class, "enumConstant", lookupMT(Enum.class),
+                                                              S -> { });
+            assertEquals(handle.invoke(), v);
+        }
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testEnumConstantUnknown() {
+        ConstantBootstraps.enumConstant(MethodHandles.lookup(), "DOES_NOT_EXIST", StackWalker.Option.class);
+    }
+
+
+    public void testGetStaticDecl() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(L, "TYPE", Class.class,
+                                                          ConstantBootstraps.class, "getStaticFinal", lookupMT(Object.class, Class.class),
+                                                          S -> { S.add("java/lang/Integer", PoolHelper::putClass); });
+        assertEquals(handle.invoke(), int.class);
+    }
+
+    public void testGetStaticSelf() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(L, "MAX_VALUE", int.class,
+                                                          ConstantBootstraps.class, "getStaticFinal", lookupMT(Object.class),
+                                                          S -> { });
+        assertEquals(handle.invoke(), Integer.MAX_VALUE);
+
+
+        handle = InstructionHelper.ldcDynamicConstant(L, "ZERO", BigInteger.class,
+                                                      ConstantBootstraps.class, "getStaticFinal", lookupMT(Object.class),
+                                                      S -> { });
+        assertEquals(handle.invoke(), BigInteger.ZERO);
+    }
+
+
+    public void testInvoke() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(
+                L, "_", List.class,
+                ConstantBootstraps.class, "invoke", lookupMT(Object.class, MethodHandle.class, Object[].class),
+                S -> {
+                    S.add("", (P, Z) -> {
+                        return P.putHandle(MethodHandleInfo.REF_invokeStatic, "java/util/List", "of",
+                                           MethodType.methodType(List.class, Object[].class).toMethodDescriptorString(),
+                                           true);
+                    });
+                    S.add(1).add(2).add(3).add(4);
+                });
+        assertEquals(handle.invoke(), List.of(1, 2, 3, 4));
+    }
+
+    public void testInvokeAsType() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(
+                L, "_", int.class,
+                ConstantBootstraps.class, "invoke", lookupMT(Object.class, MethodHandle.class, Object[].class),
+                S -> {
+                    S.add("", (P, Z) -> {
+                        return P.putHandle(MethodHandleInfo.REF_invokeStatic, "java/lang/Integer", "valueOf",
+                                           MethodType.methodType(Integer.class, String.class).toMethodDescriptorString(),
+                                           false);
+                    });
+                    S.add("42");
+                });
+        assertEquals(handle.invoke(), 42);
+    }
+
+    @Test(expectedExceptions = ClassCastException.class)
+    public void testInvokeAsTypeClassCast() throws Throwable {
+        ConstantBootstraps.invoke(MethodHandles.lookup(), "_", String.class,
+                                  MethodHandles.lookup().findStatic(Integer.class, "valueOf", MethodType.methodType(Integer.class, String.class)),
+                                  "42");
+    }
+
+    @Test(expectedExceptions = WrongMethodTypeException.class)
+    public void testInvokeAsTypeWrongReturnType() throws Throwable {
+        ConstantBootstraps.invoke(MethodHandles.lookup(), "_", short.class,
+                                  MethodHandles.lookup().findStatic(Integer.class, "parseInt", MethodType.methodType(int.class, String.class)),
+                                  "42");
+    }
+
+
+    static class X {
+        public String f;
+        public static String sf;
+    }
+
+    public void testVarHandleField() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(
+                L, "f", VarHandle.class,
+                ConstantBootstraps.class, "fieldVarHandle", lookupMT(VarHandle.class, Class.class, Class.class),
+                S -> {
+                    S.add(X.class.getName().replace('.', '/'), PoolHelper::putClass).
+                            add("java/lang/String", PoolHelper::putClass);
+                });
+
+        var vhandle = (VarHandle) handle.invoke();
+        assertEquals(vhandle.varType(), String.class);
+        assertEquals(vhandle.coordinateTypes(), List.of(X.class));
+    }
+
+    public void testVarHandleStaticField() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(
+                L, "sf", VarHandle.class,
+                ConstantBootstraps.class, "staticFieldVarHandle", lookupMT(VarHandle.class, Class.class, Class.class),
+                S -> {
+                    S.add(X.class.getName().replace('.', '/'), PoolHelper::putClass).
+                            add("java/lang/String", PoolHelper::putClass);
+                });
+
+        var vhandle = (VarHandle) handle.invoke();
+        assertEquals(vhandle.varType(), String.class);
+        assertEquals(vhandle.coordinateTypes(), List.of());
+    }
+
+    public void testVarHandleArray() throws Throwable {
+        var handle = InstructionHelper.ldcDynamicConstant(
+                L, "_", VarHandle.class,
+                ConstantBootstraps.class, "arrayVarHandle", lookupMT(VarHandle.class, Class.class),
+                S -> {
+                    S.add(String[].class.getName().replace('.', '/'), PoolHelper::putClass);
+                });
+
+        var vhandle = (VarHandle) handle.invoke();
+        assertEquals(vhandle.varType(), String.class);
+        assertEquals(vhandle.coordinateTypes(), List.of(String[].class, int.class));
+    }
+}
--- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/BytePoolHelper.java	Fri Sep 08 10:46:46 2017 -0700
+++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/BytePoolHelper.java	Wed Jan 31 11:20:36 2018 -0800
@@ -490,7 +490,8 @@
         BsmKey poolKey = bootstraps.lookup(bsmKey);
         if (poolKey == null) {
             poolKey = bsmKey.dup();
-            int bsm_ref = putHandleInternal(MethodHandleInfo.REF_invokeStatic, bsmClass, bsmName, bsmType);
+            // TODO the BSM could be a static method on an interface
+            int bsm_ref = putHandleInternal(MethodHandleInfo.REF_invokeStatic, bsmClass, bsmName, bsmType, false);
             poolKey.at(currentBsmIndex++);
             bootstraps.enter(poolKey);
             bsm_attr.writeChar(bsm_ref);
@@ -562,15 +563,20 @@
 
     @Override
     public int putHandle(int refKind, S owner, CharSequence name, T type) {
-        return putHandleInternal(refKind, symbolToString.apply(owner), name, typeToString.apply(type));
+        return putHandleInternal(refKind, symbolToString.apply(owner), name, typeToString.apply(type), false);
     }
 
-    private int putHandleInternal(int refKind, String owner, CharSequence name, String type) {
+    @Override
+    public int putHandle(int refKind, S owner, CharSequence name, T type, boolean isInterface) {
+        return putHandleInternal(refKind, symbolToString.apply(owner), name, typeToString.apply(type), isInterface);
+    }
+
+    private int putHandleInternal(int refKind, String owner, CharSequence name, String type, boolean isInterface) {
         key.setMethodHandle(refKind, owner, name, type);
         PoolKey poolKey = entries.lookup(key);
         if (poolKey == null) {
             poolKey = key.dup();
-            int ref_idx = putMemberRefInternal(fromKind(refKind), owner, name, type);
+            int ref_idx = putMemberRefInternal(fromKind(refKind, isInterface), owner, name, type);
             poolKey.at(currentIndex++);
             entries.enter(poolKey);
             pool.writeByte(PoolTag.CONSTANT_METHODHANDLE.tag);
@@ -580,7 +586,7 @@
         return poolKey.index;
     }
 
-    PoolTag fromKind(int bsmKind) {
+    PoolTag fromKind(int bsmKind, boolean isInterface) {
         switch (bsmKind) {
             case 1: // REF_getField
             case 2: // REF_getStatic
@@ -591,9 +597,8 @@
             case 6: // REF_invokeStatic
             case 7: // REF_invokeSpecial
             case 8: // REF_newInvokeSpecial
-                return PoolTag.CONSTANT_METHODREF;
             case 9: // REF_invokeInterface
-                return PoolTag.CONSTANT_INTERFACEMETHODREF;
+                return isInterface ? PoolTag.CONSTANT_INTERFACEMETHODREF : PoolTag.CONSTANT_METHODREF;
             default:
                 throw new IllegalStateException();
         }
--- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/IsolatedMethodBuilder.java	Fri Sep 08 10:46:46 2017 -0700
+++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/IsolatedMethodBuilder.java	Wed Jan 31 11:20:36 2018 -0800
@@ -117,6 +117,11 @@
         }
 
         @Override
+        public int putHandle(int refKind, Class<?> owner, CharSequence name, String type, boolean isInterface) {
+            return 0; //???
+        }
+
+        @Override
         public int putMethodType(String s) {
             return 0; //???
         }
--- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/PoolHelper.java	Fri Sep 08 10:46:46 2017 -0700
+++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/PoolHelper.java	Wed Jan 31 11:20:36 2018 -0800
@@ -58,6 +58,8 @@
 
     int putHandle(int refKind, S owner, CharSequence name, T type);
 
+    int putHandle(int refKind, S owner, CharSequence name, T type, boolean isInterface);
+
     int putInvokeDynamic(CharSequence invokedName, T invokedType, S bsmClass, CharSequence bsmName, T bsmType, Consumer<StaticArgListBuilder<S, T, E>> staticArgs);
 
     int putDynamicConstant(CharSequence constName, T constType, S bsmClass, CharSequence bsmName, T bsmType, Consumer<StaticArgListBuilder<S, T, E>> staticArgs);
--- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/TypedCodeBuilder.java	Fri Sep 08 10:46:46 2017 -0700
+++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/TypedCodeBuilder.java	Wed Jan 31 11:20:36 2018 -0800
@@ -1033,6 +1033,12 @@
             }
 
             @Override
+            public int putHandle(int refKind, S owner, CharSequence name, T t, boolean isInterface) {
+                type = typeHelper.type(typeHelper.symbolFrom("java/lang/invoke/MethodHandle"));
+                return poolHelper.putHandle(refKind, owner, name, t, isInterface);
+            }
+
+            @Override
             public int putInvokeDynamic(CharSequence invokedName, T invokedType, S bsmClass, CharSequence bsmName, T bsmType, Consumer<StaticArgListBuilder<S, T, E>> staticArgs) {
                 throw new IllegalStateException();
             }