src/java.base/share/classes/java/lang/invoke/LambdaFormBuilder.java

Print this page
rev 17257 : imported patch 8183130.patch
rev 17253 : imported patch 8183131.patch
rev 17240 : Dump classes from LambdaFormBuilder is triggered using
MethodHandleStatics.DUMP_CLASS_FILES. Class names are
extended by the LF invoker name (which should be unique)
when debugging (dumping triggers debugging).
rev 17235 : Add support for Q-types to lambda forms
Note: support is optional, and can be enabled using the flag:
-Dvalhalla.enableValueLambdaForms=true

@@ -23,23 +23,23 @@
  * questions.
  */
 
 package java.lang.invoke;
 
-import jdk.experimental.bytecode.AnnotationsBuilder;
+import jdk.experimental.bytecode.*;
 import jdk.experimental.bytecode.MacroCodeBuilder.FieldAccessKind;
 import jdk.experimental.bytecode.MacroCodeBuilder.InvocationKind;
-import jdk.experimental.bytecode.MethodBuilder;
-import jdk.experimental.bytecode.TypeTag;
 import jdk.experimental.value.MethodHandleBuilder;
 import sun.invoke.util.VerifyType;
 import sun.invoke.util.Wrapper;
 import valhalla.shady.MinimalValueTypes_1_0;
 
 import java.lang.invoke.LambdaForm.BasicType;
 import java.lang.invoke.LambdaForm.Name;
 import java.lang.invoke.MethodHandles.Lookup;
+import java.util.Arrays;
+import java.util.stream.Stream;
 
 import static java.lang.invoke.LambdaForm.BasicType.L_TYPE;
 import static java.lang.invoke.LambdaForm.BasicType.V_TYPE;
 import static java.lang.invoke.LambdaForm.BasicType.basicType;
 import static java.lang.invoke.MethodHandleNatives.Constants.REF_getField;

@@ -48,12 +48,14 @@
 import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeSpecial;
 import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeStatic;
 import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeVirtual;
 import static java.lang.invoke.MethodHandleNatives.Constants.REF_putField;
 import static java.lang.invoke.MethodHandleNatives.Constants.REF_putStatic;
+import static java.lang.invoke.MethodHandleStatics.PROFILE_GWT;
 import static java.lang.invoke.MethodHandleStatics.PROFILE_LEVEL;
 import static java.lang.invoke.MethodHandleStatics.newInternalError;
+import static jdk.experimental.bytecode.MacroCodeBuilder.CondKind.NE;
 
 /**
  * Utility class for spinning classfiles associated with lambda forms.
  */
 class LambdaFormBuilder extends MethodHandleBuilder {

@@ -61,10 +63,13 @@
     private static final String OBJ     = "java/lang/Object";
     private static final String CLASS_PREFIX   = "java/lang/invoke/LambdaForm$Value$";
     private static final String DEFAULT_CLASS  = "MH";
     private static final String LL_SIG  = "(L" + OBJ + ";)L" + OBJ + ";";
 
+    private static final String MH           = "java/lang/invoke/MethodHandle";
+    private static final String MHARY2       = "[[L" + MH + ";";
+
     static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType) {
         String invokerName = form.lambdaName();
         int p = invokerName.indexOf('.');
         boolean overrideNames = p != -1;
         String methodName = overrideNames ? invokerName.substring(p + 1) : invokerName;

@@ -118,10 +123,56 @@
                 // non-void: actually assign
                 builder.store(fromBasicType(onStack.type), localsMap[onStack.index()]);
             }
             onStack = name;  // unless otherwise modified below
             MemberName member = name.function.member();
+            MethodHandleImpl.Intrinsic intr = name.function.intrinsicName();
+            switch (intr) {
+                 case SELECT_ALTERNATIVE: {
+                     assert lambdaForm.isSelectAlternative(i);
+                     if (PROFILE_GWT) {
+                         assert(name.arguments[0] instanceof Name &&
+                                 ((Name)name.arguments[0]).refersTo(MethodHandleImpl.class, "profileBoolean"));
+                         builder.method().withAnnotation(AnnotationsBuilder.Kind.RUNTIME_VISIBLE, InvokerBytecodeGenerator.INJECTEDPROFILE_SIG);
+                     }
+                     onStack = emitSelectAlternative(name, lambdaForm.names[i+1]);
+                     i++;  // skip MH.invokeBasic of the selectAlternative result
+                     continue;
+
+                 }
+                // TODO: case GUARD_WITH_CATCH:
+                // TODO: case TRY_FINALLY:
+
+                case LOOP: {
+                    assert lambdaForm.isLoop(i);
+                    onStack = emitLoop(i);
+                    i += 2; // jump to the end of the LOOP idiom
+                    continue;
+                }
+                case IDENTITY: {
+                    assert (name.arguments.length == 1);
+                    emitPushArguments(name, 0);
+                    continue;
+                }
+                case ZERO: {
+                    assert (name.arguments.length == 0);
+                    assert (name.type != BasicType.Q_TYPE);
+                    builder.ldc(name.type.basicTypeWrapper().zero());
+                    continue;
+                }
+                case NONE: {
+                    // no intrinsic associated
+                    break;
+                }
+                // TODO: case NEW_ARRAY:
+                // TODO: case ARRAY_LOAD:
+                // TODO: case ARRAY_STORE:
+                // TODO: case ARRAY_LENGTH:
+                default: {
+                    throw newInternalError("Unknown intrinsic: "+intr);
+                }
+            }
             if (InvokerBytecodeGenerator.isStaticallyInvocable(member)) {
                 emitStaticInvoke(member, name);
             } else {
                 emitInvoke(name);
             }

@@ -258,11 +309,10 @@
 
     /**
      * Emit an invoke for the given name.
      */
     void emitInvoke(Name name) {
-        //assert(!isLinkerMethodInvoke(name));  // should use the static path for these
         if (true) {
             // push receiver
             MethodHandle target = name.function.resolvedHandle();
             assert(target != null) : name.exprString();
             builder.ldc(target);

@@ -323,10 +373,14 @@
             default:
                 throw new InternalError("unknown type: " + type);
         }
     }
 
+    TypeTag fromClass(Class<?> cls) {
+        return fromBasicType(BasicType.basicType(cls));
+    }
+
     InvocationKind invKindFromRefKind(int refKind) {
         switch (refKind) {
             case REF_invokeVirtual:      return InvocationKind.INVOKEVIRTUAL;
             case REF_invokeStatic:       return InvocationKind.INVOKESTATIC;
             case REF_invokeSpecial:      return InvocationKind.INVOKESPECIAL;

@@ -344,10 +398,13 @@
         }
         throw new InternalError("refKind="+refKind);
     }
 
     static class LambdaFormCodeBuilder extends MethodHandleCodeBuilder {
+
+        private MethodBuilder<Class<?>, String, byte[]> methodBuilder;
+
         public LambdaFormCodeBuilder(LambdaForm form, MethodBuilder<Class<?>, String, byte[]> methodBuilder) {
             super(methodBuilder);
             if (form.forceInline) {
                 methodBuilder.withAnnotation(AnnotationsBuilder.Kind.RUNTIME_VISIBLE, "Ljdk/internal/vm/annotation/ForceInline;");
             }

@@ -356,7 +413,188 @@
         }
 
         Class<?> typeOfLocal(int index) {
             return typeHelper.symbol(state.locals.get(index));
         }
+
+        MethodBuilder<Class<?>, String, byte[]> method() {
+            return methodBuilder;
+        }
+    }
+
+    private Name emitLoop(int pos) {
+        Name args    = lambdaForm.names[pos];
+        Name invoker = lambdaForm.names[pos+1];
+        Name result  = lambdaForm.names[pos+2];
+
+        // extract clause and loop-local state types
+        // find the type info in the loop invocation
+        BasicType[] loopClauseTypes = (BasicType[]) invoker.arguments[0];
+        Class<?>[] loopLocalStateTypes = Stream.of(loopClauseTypes).
+                filter(bt -> bt != BasicType.V_TYPE).map(BasicType::basicTypeClass).toArray(Class<?>[]::new);
+
+        Class<?>[] localTypes = new Class<?>[loopLocalStateTypes.length + 1];
+        localTypes[0] = MethodHandleImpl.LoopClauses.class;
+        System.arraycopy(loopLocalStateTypes, 0, localTypes, 1, loopLocalStateTypes.length);
+
+        final int clauseDataIndex     = extendLocalsMap(localTypes);
+        final int firstLoopStateIndex = clauseDataIndex + 1;
+        maxLocals += loopLocalStateTypes.length + 1;
+
+        Class<?> returnType = result.function.resolvedHandle().type().returnType();
+        MethodType loopType = args.function.resolvedHandle().type()
+                .dropParameterTypes(0,1)
+                .changeReturnType(returnType);
+        MethodType loopHandleType = loopType.insertParameterTypes(0, loopLocalStateTypes);
+        MethodType predType = loopHandleType.changeReturnType(boolean.class);
+        MethodType finiType = loopHandleType;
+
+        final int nClauses = loopClauseTypes.length;
+
+        // indices to invoker arguments to load method handle arrays
+        final int inits = 1;
+        final int steps = 2;
+        final int preds = 3;
+        final int finis = 4;
+
+        // PREINIT:
+        emitPushArgument(MethodHandleImpl.LoopClauses.class, invoker.arguments[1]);
+        builder.getfield(MethodHandleImpl.LoopClauses.class, "clauses", MHARY2);
+        emitAstoreInsn(clauseDataIndex);
+
+        // INIT:
+        for (int c = 0, state = 0; c < nClauses; ++c) {
+            MethodType cInitType = loopType.changeReturnType(loopClauseTypes[c].basicTypeClass());
+            emitLoopHandleInvoke(inits, c, args, false, cInitType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex);
+            if (cInitType.returnType() != void.class) {
+                builder.store(fromClass(cInitType.returnType()), localsMap[firstLoopStateIndex + state]);
+                ++state;
+            }
+        }
+
+        // LOOP:
+        builder.label("LOOP");
+
+        String val = null;
+        for (int c = 0, s = 0; c < nClauses; ++c) {
+            MethodType stepType = loopHandleType.changeReturnType(loopClauseTypes[c].basicTypeClass());
+            boolean isVoid = (stepType.returnType() == void.class);
+
+            // invoke loop step
+            emitLoopHandleInvoke(steps, c, args, true, stepType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex);
+            if (!isVoid) {
+                builder.store(fromClass(stepType.returnType()), localsMap[firstLoopStateIndex + s]);
+                ++s;
+            }
+
+            // invoke loop predicate
+            emitLoopHandleInvoke(preds, c, args, true, predType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex);
+            builder.emitCondJump(Opcode.IFEQ, NE, "NEXT_" + c);
+
+            // invoke fini
+            emitLoopHandleInvoke(finis, c, args, true, finiType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex);
+            builder.goto_("DONE");
+
+            // this is the beginning of the next loop clause
+            builder.label("NEXT_" + c);
+        }
+        builder.goto_("LOOP");
+
+
+        builder.label("DONE");
+
+        return result;
+    }
+
+    private void emitLoopHandleInvoke(int handles, int clause, Name args, boolean pushLocalState,
+                                      MethodType type, Class<?>[] loopLocalStateTypes, int clauseDataSlot,
+                                      int firstLoopStateSlot) {
+        // load handle for clause
+        builder.load(TypeTag.A, localsMap[clauseDataSlot])
+               .ldc(handles - 1)
+               .aaload()
+               .ldc(clause)
+               .aaload();
+
+        // load loop state (preceding the other arguments)
+        if (pushLocalState) {
+            for (int s = 0; s < loopLocalStateTypes.length; ++s) {
+                builder.load(fromClass(loopLocalStateTypes[s]), localsMap[firstLoopStateSlot + s]);
+            }
+        }
+        // load loop args (skip 0: method handle)
+        emitPushArguments(args, 1);
+        builder.invokevirtual(MethodHandle.class, "invokeBasic", type.toMethodDescriptorString(), false);
+    }
+
+    /**
+     * Emit bytecode for the selectAlternative idiom.
+     *
+     * The pattern looks like (Cf. MethodHandleImpl.makeGuardWithTest):
+     * <blockquote><pre>{@code
+     *   Lambda(a0:L,a1:I)=>{
+     *     t2:I=foo.test(a1:I);
+     *     t3:L=MethodHandleImpl.selectAlternative(t2:I,(MethodHandle(int)int),(MethodHandle(int)int));
+     *     t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I}
+     * }</pre></blockquote>
+     */
+    private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) {
+        assert InvokerBytecodeGenerator.isStaticallyInvocable(invokeBasicName);
+
+        Name receiver = (Name) invokeBasicName.arguments[0];
+
+        int pos = invokeBasicName.index();
+        String L_fallback = "L_fallback_" + pos;
+        String L_done     = "L_done_"+ pos;
+
+        // load test result
+        emitPushArgument(selectAlternativeName, 0);
+
+        // if_icmpne L_fallback
+        builder.emitCondJump(Opcode.IFEQ, NE, L_fallback);
+
+        // invoke selectAlternativeName.arguments[1]
+        emitPushArgument(selectAlternativeName, 1);  // get 2nd argument of selectAlternative
+        emitAstoreInsn(receiver.index());  // store the MH in the receiver slot
+        emitStaticInvoke(invokeBasicName);
+
+        // goto L_done
+        builder.goto_(L_done);
+
+
+
+        // L_fallback:
+        builder.label(L_fallback);
+
+        // invoke selectAlternativeName.arguments[2]
+        emitPushArgument(selectAlternativeName, 2);  // get 3rd argument of selectAlternative
+        emitAstoreInsn(receiver.index());  // store the MH in the receiver slot
+        emitStaticInvoke(invokeBasicName);
+
+        // L_done:
+        builder.label(L_done);
+
+        return invokeBasicName;  // return what's on stack
+    }
+
+    private void emitAstoreInsn(int index) {
+        builder.store(TypeTag.A, localsMap[index]);
+    }
+
+    void emitStaticInvoke(Name name) {
+        emitStaticInvoke(name.function.member(), name);
+    }
+
+    private int extendLocalsMap(Class<?>[] types) {
+        int firstSlot = localsMap.length - 1;
+        localsMap = Arrays.copyOf(localsMap, localsMap.length + types.length);
+        int index = localsMap[firstSlot - 1] + 1;
+        int lastSlots = 0;
+        for (int i = 0; i < types.length; ++i) {
+            localsMap[firstSlot + i] = index;
+            lastSlots = BasicType.basicType(types[i]).basicTypeSlots();
+            index += lastSlots;
+        }
+        localsMap[localsMap.length - 1] = index - lastSlots;
+        return firstSlot;
     }
 }