changeset 14078:780c8eba356a

Prototype of ConstantDynamic
author briangoetz
date Fri, 26 Aug 2016 17:49:17 -0400
parents 95fc5ae64549
children 1915d0702dbf
files src/java.base/share/classes/java/lang/invoke/ConstantDynamic.java src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassReader.java src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassWriter.java src/java.base/share/classes/jdk/internal/org/objectweb/asm/DynConstant.java src/java.base/share/classes/jdk/internal/org/objectweb/asm/Item.java src/java.base/share/classes/jdk/internal/org/objectweb/asm/util/TraceClassVisitor.java src/java.base/share/classes/valhalla/constantdyn/ConstantDynRewriter.java
diffstat 7 files changed, 344 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/ConstantDynamic.java	Fri Aug 26 17:49:17 2016 -0400
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.invoke;
+
+/**
+ * ConstantDynamic
+ *
+ * @author Brian Goetz
+ */
+public class ConstantDynamic {
+    public static Class<?> primitiveClass(MethodHandles.Lookup lookup, String name, MethodType desc, String arg) {
+        switch (arg) {
+            case "I": return int.class;
+            case "J": return long.class;
+            case "S": return short.class;
+            case "B": return byte.class;
+            case "C": return char.class;
+            case "F": return float.class;
+            case "D": return double.class;
+            case "Z": return boolean.class;
+            default:
+                throw new IllegalArgumentException(arg);
+        }
+    }
+}
--- a/src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassReader.java	Mon Jul 04 18:15:56 2016 +0100
+++ b/src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassReader.java	Fri Aug 26 17:49:17 2016 -0400
@@ -222,6 +222,7 @@
             case ClassWriter.FLOAT:
             case ClassWriter.NAME_TYPE:
             case ClassWriter.INDY:
+            case ClassWriter.CONSTANT_DYNAMIC:
                 size = 5;
                 break;
             case ClassWriter.LONG:
@@ -382,6 +383,11 @@
                 item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
                         readUnsignedShort(index));
                 break;
+
+            case ClassWriter.CONSTANT_DYNAMIC:
+                item.set(readUnsignedShort(index), readUTF8(index+2, buf));
+                break;
+
             // case ClassWriter.STR:
             // case ClassWriter.CLASS:
             // case ClassWriter.MTYPE
@@ -429,9 +435,9 @@
         int boostrapMethodCount = readUnsignedShort(u + 8);
         for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
             int position = v - u - 10;
-            int hashCode = readConst(readUnsignedShort(v), c).hashCode();
+            int hashCode = readConst(readUnsignedShort(v), null, c).hashCode();
             for (int k = readUnsignedShort(v + 2); k > 0; --k) {
-                hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
+                hashCode ^= readConst(readUnsignedShort(v + 4), null, c).hashCode();
                 v += 2;
             }
             v += 4;
@@ -772,7 +778,7 @@
             // (based on frequencies observed on typical classes)
             if ("ConstantValue".equals(attrName)) {
                 int item = readUnsignedShort(u + 8);
-                value = item == 0 ? null : readConst(item, c);
+                value = item == 0 ? null : readConst(item, null, c);
             } else if (SIGNATURES && "Signature".equals(attrName)) {
                 signature = readUTF8(u + 8, c);
             } else if ("Deprecated".equals(attrName)) {
@@ -1491,11 +1497,11 @@
                 u += 3;
                 break;
             case ClassWriter.LDC_INSN:
-                mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c));
+                mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, context, c));
                 u += 2;
                 break;
             case ClassWriter.LDCW_INSN:
-                mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c));
+                mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), context, c));
                 u += 3;
                 break;
             case ClassWriter.FIELDORMETH_INSN:
@@ -1521,12 +1527,12 @@
             case ClassWriter.INDYMETH_INSN: {
                 int cpIndex = items[readUnsignedShort(u + 1)];
                 int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
-                Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
+                Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), context, c);
                 int bsmArgCount = readUnsignedShort(bsmIndex + 2);
                 Object[] bsmArgs = new Object[bsmArgCount];
                 bsmIndex += 4;
                 for (int i = 0; i < bsmArgCount; i++) {
-                    bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
+                    bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), context, c);
                     bsmIndex += 2;
                 }
                 cpIndex = items[readUnsignedShort(cpIndex + 2)];
@@ -1920,7 +1926,7 @@
         case 'J': // pointer to CONSTANT_Long
         case 'F': // pointer to CONSTANT_Float
         case 'D': // pointer to CONSTANT_Double
-            av.visit(name, readConst(readUnsignedShort(v), buf));
+            av.visit(name, readConst(readUnsignedShort(v), null, buf));
             v += 2;
             break;
         case 'B': // pointer to CONSTANT_Byte
@@ -2561,7 +2567,7 @@
      *         {@link String}, {@link Type} or {@link Handle} corresponding to
      *         the given constant pool item.
      */
-    public Object readConst(final int item, final char[] buf) {
+    public Object readConst(final int item, Context context, final char[] buf) {
         int index = items[item];
         switch (b[index - 1]) {
         case ClassWriter.INT:
@@ -2578,6 +2584,14 @@
             return readUTF8(index, buf);
         case ClassWriter.MTYPE:
             return Type.getMethodType(readUTF8(index, buf));
+        case ClassWriter.CONSTANT_DYNAMIC:
+            int bsmIndex = context.bootstrapMethods[readUnsignedShort(index)];
+            Handle handle = (Handle) readConst(readUnsignedShort(bsmIndex), context, buf);
+            int bsmArgCount = readUnsignedShort(bsmIndex + 2);
+            Object[] bsmArgs = new Object[bsmArgCount];
+            for (int i=0; i<bsmArgCount; i++)
+                bsmArgs[i] = readConst(readUnsignedShort(bsmIndex + 4 + i*2), context, buf);
+            return new DynConstant(handle, bsmArgs, readUTF8(index+2, buf));
         default: // case ClassWriter.HANDLE_BASE + [1..9]:
             int tag = readByte(index);
             int[] items = this.items;
--- a/src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassWriter.java	Mon Jul 04 18:15:56 2016 +0100
+++ b/src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassWriter.java	Fri Aug 26 17:49:17 2016 -0400
@@ -266,6 +266,8 @@
      */
     static final int HANDLE = 15;
 
+    static final int CONSTANT_DYNAMIC = 17;
+
     /**
      * The type of CONSTANT_InvokeDynamic constant pool items.
      */
@@ -1082,6 +1084,9 @@
         } else if (cst instanceof Handle) {
             Handle h = (Handle) cst;
             return newHandleItem(h.tag, h.owner, h.name, h.desc);
+        } else if (cst instanceof DynConstant) {
+            DynConstant dc = (DynConstant) cst;
+            return newDynConstantItem(dc.bootstrap, dc.staticArgs, dc.desc);
         } else {
             throw new IllegalArgumentException("value " + cst);
         }
@@ -1146,6 +1151,18 @@
         return result;
     }
 
+    Item newDynConstantItem(Handle bootstrap, Object[] staticArgs, String desc) {
+        Item i = newInvokeDynamicItem("const", "()" + desc, bootstrap, staticArgs);
+        key3.set(i.index, desc);
+        Item result = get(key3);
+        if (result == null) {
+            put122(CONSTANT_DYNAMIC, i.index, newUTF8(desc));
+            result = new Item(index++, key3);
+            put(result);
+        }
+        return result;
+    }
+
     /**
      * Adds a class reference to the constant pool of the class being build.
      * Does nothing if the constant pool already contains a similar item.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/org/objectweb/asm/DynConstant.java	Fri Aug 26 17:49:17 2016 -0400
@@ -0,0 +1,57 @@
+package jdk.internal.org.objectweb.asm;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class DynConstant {
+    final Handle bootstrap;
+    final Object[] staticArgs;
+    final String desc;
+
+    public DynConstant(Handle bootstrap, Object[] staticArgs, String desc) {
+        this.bootstrap = bootstrap;
+        this.desc = desc;
+        this.staticArgs = staticArgs.clone();
+    }
+
+    public Handle getBootstrap() {
+        return bootstrap;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public Object[] getStaticArgs() {
+        return staticArgs.clone();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        DynConstant that = (DynConstant) o;
+
+        if (bootstrap != null ? !bootstrap.equals(that.bootstrap) : that.bootstrap != null) return false;
+        if (!Arrays.equals(staticArgs, that.staticArgs)) return false;
+        return desc != null ? desc.equals(that.desc) : that.desc == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = bootstrap != null ? bootstrap.hashCode() : 0;
+        result = 31 * result + Arrays.hashCode(staticArgs);
+        result = 31 * result + (desc != null ? desc.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ConstantDynamic[%s(%s):%s]", bootstrap,
+                Stream.of(staticArgs).map(Object::toString).collect(Collectors.joining(",")),
+                desc);
+    }
+}
--- a/src/java.base/share/classes/jdk/internal/org/objectweb/asm/Item.java	Mon Jul 04 18:15:56 2016 +0100
+++ b/src/java.base/share/classes/jdk/internal/org/objectweb/asm/Item.java	Fri Aug 26 17:49:17 2016 -0400
@@ -296,6 +296,13 @@
         this.hashCode = hashCode;
     }
 
+    // ConstantDynamic item
+    void set(int bsm, String type) {
+        this.type = ClassWriter.CONSTANT_DYNAMIC;
+        this.intVal = bsm;
+        this.strVal1 = type;
+    }
+
     /**
      * Indicates if the given item is equal to this one. <i>This method assumes
      * that the two items have the same {@link #type}</i>.
--- a/src/java.base/share/classes/jdk/internal/org/objectweb/asm/util/TraceClassVisitor.java	Mon Jul 04 18:15:56 2016 +0100
+++ b/src/java.base/share/classes/jdk/internal/org/objectweb/asm/util/TraceClassVisitor.java	Fri Aug 26 17:49:17 2016 -0400
@@ -58,10 +58,14 @@
  */
 package jdk.internal.org.objectweb.asm.util;
 
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.PrintWriter;
 
 import jdk.internal.org.objectweb.asm.AnnotationVisitor;
 import jdk.internal.org.objectweb.asm.Attribute;
+import jdk.internal.org.objectweb.asm.ClassReader;
 import jdk.internal.org.objectweb.asm.ClassVisitor;
 import jdk.internal.org.objectweb.asm.FieldVisitor;
 import jdk.internal.org.objectweb.asm.MethodVisitor;
@@ -244,4 +248,9 @@
         }
         super.visitEnd();
     }
+
+    public static void main(String[] args) throws IOException {
+        ClassReader cr = new ClassReader(new FileInputStream(args[0]));
+        cr.accept(new TraceClassVisitor(new PrintWriter(System.out)), ClassReader.SKIP_DEBUG);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/valhalla/constantdyn/ConstantDynRewriter.java	Fri Aug 26 17:49:17 2016 -0400
@@ -0,0 +1,184 @@
+package valhalla.constantdyn;
+
+import jdk.internal.org.objectweb.asm.*;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+public class ConstantDynRewriter {
+
+    private static String descFor(Object cst) {
+        if (cst instanceof Integer) {
+            return "I";
+        } else if (cst instanceof Byte) {
+            return "B";
+        } else if (cst instanceof Character) {
+            return "C";
+        } else if (cst instanceof Short) {
+            return "S";
+        } else if (cst instanceof Boolean) {
+            return "Z";
+        } else if (cst instanceof Float) {
+            return "F";
+        } else if (cst instanceof Long) {
+            return "J";
+        } else if (cst instanceof Double) {
+            return "D";
+        } else if (cst instanceof String) {
+            return "Ljava/lang/String;";
+        } else if (cst instanceof Type) {
+            Type t = (Type) cst;
+            int s = t.getSort();
+            if (s == Type.OBJECT) {
+                return "Ljava/lang/Class;";
+            } else if (s == Type.METHOD) {
+                return "Ljava/lang/invoke/MethodType;";
+            } else { // s == primitive type or array
+                throw new IllegalArgumentException("value[type] " + cst);
+            }
+        } else if (cst instanceof Handle) {
+            return "Ljava/lang/invoke/MethodHandle;";
+        } else {
+            throw new IllegalArgumentException("value " + cst);
+        }
+    }
+
+    public byte[] transform(InputStream input) throws IOException {
+        ClassReader cr = new ClassReader(input);
+        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+
+        List<Runnable> tasks = new ArrayList<>();
+        cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
+            String className;
+            int indys = 0;
+            int ldcs = 0;
+            Map<DynConstant, String> ldcMethod = new HashMap<>();
+
+            @Override
+            public void visit(int version, int access, String className, String desc, String superClass, String[] interfaces) {
+                this.className = className;
+                int newVersion =  version & 0xFFFDFFFF;
+                super.visit(newVersion, access, className, desc, superClass, interfaces);
+            }
+
+            @Override
+            public MethodVisitor visitMethod(int access, String methName, String methDesc, String methSig, String[] exceptions) {
+                MethodVisitor parentMV = super.visitMethod(access, methName, methDesc, methSig, exceptions);
+                return new MethodVisitor(Opcodes.ASM5, parentMV) {
+                    @Override
+                    public void visitLdcInsn(Object cst) {
+                        if (cst instanceof DynConstant) {
+                            DynConstant dc = (DynConstant) cst;
+                            if (ldcMethod.containsKey(dc)) {
+                                parentMV.visitMethodInsn(INVOKESTATIC, className, ldcMethod.get(dc), "()" + dc.getDesc(), false);
+                            }
+                            else {
+                                int index = indys++;
+                                String fieldName = "ldc$" + index;
+                                String fieldType = dc.getDesc();
+                                String methodType = "()" + fieldType;
+                                ldcMethod.put(dc, fieldName);
+                                tasks.add(() -> {
+                                    cw.visitField(ACC_PRIVATE|ACC_STATIC|ACC_VOLATILE, fieldName, fieldType, null, null).visitEnd();
+                                    MethodVisitor mv = visitMethod(ACC_PRIVATE | ACC_STATIC, fieldName, methodType, null, null);
+                                    mv.visitCode();
+                                    mv.visitFieldInsn(GETSTATIC, className, fieldName, fieldType);
+                                    Label l0 = new Label();
+                                    mv.visitJumpInsn(IFNONNULL, l0);
+                                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false);
+                                    mv.visitLdcInsn("const");
+                                    mv.visitLdcInsn(Type.getMethodType(methodType));
+                                    for (Object o : dc.getStaticArgs())
+                                        mv.visitLdcInsn(o);
+                                    mv.visitMethodInsn(INVOKESTATIC, dc.getBootstrap().getOwner(), dc.getBootstrap().getName(), dc.getBootstrap().getDesc(), false);
+                                    mv.visitFieldInsn(PUTSTATIC, className, fieldName, fieldType);
+
+                                    mv.visitLabel(l0);
+                                    mv.visitFieldInsn(GETSTATIC, className, fieldName, fieldType);
+                                    mv.visitInsn(ARETURN);
+                                    mv.visitMaxs(-1, -1);
+                                    mv.visitEnd();
+                                });
+                                parentMV.visitMethodInsn(INVOKESTATIC, className, fieldName, methodType, false);
+                            }
+                        }
+                        else
+                            super.visitLdcInsn(cst);
+                    }
+
+                    @Override
+                    public void visitInvokeDynamicInsn(String invName, String invDesc, Handle handle, Object... staticArgs) {
+                        // @@@ Only do this if there are any CDYNs in the static args
+
+                        int index = indys++;
+                        String bsmOwner = handle.getOwner();
+                        String bsmName = handle.getName();
+                        String bsmDesc = handle.getDesc();
+                        String bsmInvocationDesc =
+                                Stream.of(staticArgs)
+                                        .map(ConstantDynRewriter::descFor)
+                                        .collect(Collectors.joining("",
+                                                "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;",
+                                                ")Ljava/lang/invoke/CallSite;"));
+                        String MH_DESC = "Ljava/lang/invoke/MethodHandle;";
+                        String fieldName = "indy$" + index;
+                        String methodDesc = "()" + MH_DESC;
+                        tasks.add(() -> {
+                            cw.visitField(ACC_PRIVATE|ACC_STATIC|ACC_VOLATILE, fieldName, MH_DESC, null, null).visitEnd();
+
+                            MethodVisitor mv = visitMethod(ACC_PRIVATE|ACC_STATIC, fieldName, methodDesc, null, null);
+                            mv.visitCode();
+                            mv.visitFieldInsn(GETSTATIC, className, fieldName, MH_DESC);
+                            Label l0 = new Label();
+                            mv.visitJumpInsn(IFNONNULL, l0);
+                            mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false);
+                            mv.visitVarInsn(ASTORE, 3);
+                            mv.visitVarInsn(ALOAD, 3);
+                            mv.visitLdcInsn(Type.getType("L" + bsmOwner + ";"));
+                            mv.visitLdcInsn(bsmName);
+                            mv.visitLdcInsn(Type.getMethodType(bsmDesc));
+                            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
+                            mv.visitVarInsn(ALOAD, 3);
+                            mv.visitLdcInsn(invName);
+                            mv.visitLdcInsn(Type.getMethodType(invDesc));
+                            for (Object o : staticArgs)
+                                mv.visitLdcInsn(o);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invoke", bsmInvocationDesc, false);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/CallSite", "dynamicInvoker", "()Ljava/lang/invoke/MethodHandle;", false);
+                            mv.visitFieldInsn(PUTSTATIC, className, fieldName, MH_DESC);
+                            mv.visitLabel(l0);
+                            mv.visitFieldInsn(GETSTATIC, className, fieldName, MH_DESC);
+                            mv.visitInsn(ARETURN);
+                            mv.visitMaxs(-1, -1);
+                            mv.visitEnd();
+                        });
+                        parentMV.visitMethodInsn(INVOKESTATIC, className, fieldName, methodDesc, false);
+                        parentMV.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invoke", invDesc, false);
+                    }
+                };
+            }
+        }, 0);
+        tasks.forEach(Runnable::run);
+        return cw.toByteArray();
+    }
+
+    public static void main(String[] args) throws IOException {
+        String inFile = args[0];
+        String outFile = args[1];
+        byte[] bytes = new ConstantDynRewriter().transform(new FileInputStream(inFile));
+        try (FileOutputStream fos = new FileOutputStream(outFile)) {
+            fos.write(bytes);
+        }
+    }
+}