/* * 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 jdk.experimental.bytecode.*; import jdk.experimental.bytecode.MacroCodeBuilder.FieldAccessKind; import jdk.experimental.bytecode.MacroCodeBuilder.InvocationKind; import jdk.experimental.value.MethodHandleBuilder; import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; 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; import static java.lang.invoke.MethodHandleNatives.Constants.REF_getStatic; import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeInterface; 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.EQ; import static jdk.experimental.bytecode.MacroCodeBuilder.CondKind.NE; /** * Utility class for spinning classfiles associated with lambda forms. */ class LambdaFormBuilder extends MethodHandleBuilder { 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 LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V"; 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; String className = overrideNames ? CLASS_PREFIX + invokerName.substring(0, p) : CLASS_PREFIX + DEFAULT_CLASS; return MethodHandleBuilder.loadCode(Lookup.IMPL_LOOKUP.in(LambdaForm.class), className, methodName, invokerType.toMethodDescriptorString(), M -> new LambdaFormCodeBuilder(form, invokerType, M), clazz -> InvokerBytecodeGenerator.resolveInvokerMember(clazz, methodName, invokerType), C -> new LambdaFormBuilder(C, form).generateLambdaFormBody()); } LambdaFormCodeBuilder builder; LambdaForm lambdaForm; LambdaFormBuilder(LambdaFormCodeBuilder builder, LambdaForm lambdaForm) { this.builder = builder; this.lambdaForm = lambdaForm; } /** Generates code to check that actual receiver and LambdaForm matches */ private boolean checkActualReceiver() { // Expects MethodHandle on the stack and actual receiver MethodHandle in slot #0 builder.dup().load(0).invokestatic(MethodHandleImpl.class, "assertSame", LLV_SIG, false); return true; } void generateLambdaFormBody() { if (lambdaForm.customized != null && MethodHandleBuilder.ENABLE_POOL_PATCHES) { // Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute // receiver MethodHandle (at slot #0) with an embedded constant and use it instead. // It enables more efficient code generation in some situations, since embedded constants // are compile-time constants for JIT compiler. builder.ldc(lambdaForm.customized) .checkcast(MethodHandle.class); assert(checkActualReceiver()); // generates runtime check builder.store(0); } // iterate over the form's names, generating bytecode instructions for each // start iterating at the first name following the arguments Name onStack = null; for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { Name name = lambdaForm.names[i]; if (onStack != null && onStack.type != V_TYPE) { // non-void: actually assign builder.store(fromBasicType(onStack.type), 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; } 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); builder.pushArguments(name, 0); continue; } case ZERO: { assert (name.arguments.length == 0); builder.ldc(name.type.basicTypeWrapper().zero()); continue; } // TODO: case GUARD_WITH_CATCH: // TODO: case TRY_FINALLY: // TODO: case NEW_ARRAY: // TODO: case ARRAY_LOAD: // TODO: case ARRAY_STORE: // TODO: case ARRAY_LENGTH: } if (InvokerBytecodeGenerator.isStaticallyInvocable(member)) { builder.invokeStaticName(member, name); } else { builder.invokeName(name); } } builder.return_(onStack); } 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 = builder.extendLocalsMap(localTypes); final int firstLoopStateIndex = clauseDataIndex + 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: builder.pushArgument(MethodHandleImpl.LoopClauses.class, invoker.arguments[1]) .getfield(MethodHandleImpl.LoopClauses.class, "clauses", MHARY2) .store(TypeTag.A, clauseDataIndex); // INIT: for (int c = 0, state = 0; c < nClauses; ++c) { MethodType cInitType = loopType.changeReturnType(loopClauseTypes[c].basicTypeClass()); builder.invokeLoopHandle(inits, c, args, false, cInitType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); if (cInitType.returnType() != void.class) { builder.store(fromClass(cInitType.returnType()), firstLoopStateIndex + state); ++state; } } // LOOP: String loopLabel = builder.label(); String doneLabel = builder.label(); builder.label(loopLabel); 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 builder.invokeLoopHandle(steps, c, args, true, stepType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); if (!isVoid) { builder.store(fromClass(stepType.returnType()), firstLoopStateIndex + s); ++s; } String nextLabel = builder.label(); // invoke loop predicate builder.invokeLoopHandle(preds, c, args, true, predType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex) .emitCondJump(Opcode.IFEQ, NE, nextLabel) .invokeLoopHandle(finis, c, args, true, finiType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex) .goto_(doneLabel) .label(nextLabel); } builder.goto_(loopLabel) .label(doneLabel); return result; } /** * Emit bytecode for the selectAlternative idiom. * * The pattern looks like (Cf. MethodHandleImpl.makeGuardWithTest): *
*/ private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) { assert InvokerBytecodeGenerator.isStaticallyInvocable(invokeBasicName); Name receiver = (Name) invokeBasicName.arguments[0]; String fallbackLabel = builder.label(); String doneLabel = builder.label(); builder.pushArgument(selectAlternativeName, 0) // load test result .emitCondJump(Opcode.IFEQ, EQ, fallbackLabel) // if_icmp L_fallback .pushArgument(selectAlternativeName, 1) // get 2nd argument of selectAlternative .store(TypeTag.A, receiver.index()) // store the MH in the receiver slot .invokeStaticName(invokeBasicName) // invoke selectAlternativeName.arguments[1] .goto_(doneLabel) .label(fallbackLabel) .pushArgument(selectAlternativeName, 2) // get 3rd argument of selectAlternative .store(TypeTag.A, receiver.index()) // store the MH in the receiver slot .invokeStaticName(invokeBasicName) // invoke selectAlternativeName.arguments[2] .label(doneLabel); return invokeBasicName; // return what's on stack } static class LambdaFormCodeBuilder extends MethodHandleCodeBuilder{@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} * }