blob: 64c67d71a5b7baf7e183f292c761c85c49c32e46 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.classgen.asm.indy;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.EmptyExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.tools.WideningCategories;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.classgen.asm.CompileStack;
import org.codehaus.groovy.classgen.asm.InvocationWriter;
import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter;
import org.codehaus.groovy.classgen.asm.OperandStack;
import org.codehaus.groovy.classgen.asm.WriterController;
import org.codehaus.groovy.runtime.wrappers.Wrapper;
import org.codehaus.groovy.vmplugin.v7.IndyInterface;
import org.objectweb.asm.Handle;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getTypeDescription;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CallType.CAST;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CallType.GET;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CallType.INIT;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CallType.METHOD;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.GROOVY_OBJECT;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.IMPLICIT_THIS;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.SAFE_NAVIGATION;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.SPREAD_CALL;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.THIS_CALL;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
/**
* This Writer is used to generate the call invocation byte codes
* for usage by invokedynamic.
*/
public class InvokeDynamicWriter extends InvocationWriter {
private static final String INDY_INTERFACE_NAME = IndyInterface.class.getName().replace('.', '/');
private static final String BSM_METHOD_TYPE_DESCRIPTOR =
MethodType.methodType(
CallSite.class, Lookup.class, String.class, MethodType.class,
String.class, int.class
).toMethodDescriptorString();
private static final Handle BSM =
new Handle(
H_INVOKESTATIC,
INDY_INTERFACE_NAME,
"bootstrap",
BSM_METHOD_TYPE_DESCRIPTOR);
private final WriterController controller;
public InvokeDynamicWriter(WriterController wc) {
super(wc);
this.controller = wc;
}
@Override
protected boolean makeCachedCall(Expression origin, ClassExpression sender,
Expression receiver, Expression message, Expression arguments,
MethodCallerMultiAdapter adapter, boolean safe, boolean spreadSafe,
boolean implicitThis, boolean containsSpreadExpression
) {
// fixed number of arguments && name is a real String and no GString
if ((adapter == null || adapter == invokeMethod || adapter == invokeMethodOnCurrent || adapter == invokeStaticMethod) && !spreadSafe) {
String methodName = getMethodName(message);
if (methodName != null) {
makeIndyCall(adapter, receiver, implicitThis, safe, methodName, arguments);
return true;
}
}
return false;
}
private String prepareIndyCall(Expression receiver, boolean implicitThis) {
CompileStack compileStack = controller.getCompileStack();
OperandStack operandStack = controller.getOperandStack();
compileStack.pushLHS(false);
// load normal receiver as first argument
compileStack.pushImplicitThis(implicitThis);
receiver.visit(controller.getAcg());
compileStack.popImplicitThis();
return "("+getTypeDescription(operandStack.getTopOperand());
}
private void finishIndyCall(Handle bsmHandle, String methodName, String sig, int numberOfArguments, Object... bsmArgs) {
CompileStack compileStack = controller.getCompileStack();
OperandStack operandStack = controller.getOperandStack();
controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, sig, bsmHandle, bsmArgs);
operandStack.replace(ClassHelper.OBJECT_TYPE, numberOfArguments);
compileStack.popLHS();
}
private void makeIndyCall(MethodCallerMultiAdapter adapter, Expression receiver, boolean implicitThis, boolean safe, String methodName, Expression arguments) {
OperandStack operandStack = controller.getOperandStack();
StringBuilder sig = new StringBuilder(prepareIndyCall(receiver, implicitThis));
// load arguments
int numberOfArguments = 1;
ArgumentListExpression ae = makeArgumentList(arguments);
boolean containsSpreadExpression = AsmClassGenerator.containsSpreadExpression(arguments);
AsmClassGenerator acg = controller.getAcg();
if (containsSpreadExpression) {
acg.despreadList(ae.getExpressions(), true);
sig.append(getTypeDescription(Object[].class));
} else {
for (Expression arg : ae.getExpressions()) {
arg.visit(acg);
if (arg instanceof CastExpression) {
operandStack.box();
acg.loadWrapper(arg);
sig.append(getTypeDescription(Wrapper.class));
} else {
sig.append(getTypeDescription(operandStack.getTopOperand()));
}
numberOfArguments++;
}
}
sig.append(")Ljava/lang/Object;");
String callSiteName = METHOD.getCallSiteName();
if (adapter == null) callSiteName = INIT.getCallSiteName();
int flags = getMethodCallFlags(adapter, safe, containsSpreadExpression);
finishIndyCall(BSM, callSiteName, sig.toString(), numberOfArguments, methodName, flags);
}
private static int getMethodCallFlags(MethodCallerMultiAdapter adapter, boolean safe, boolean spread) {
int ret = 0;
if (safe) ret |= SAFE_NAVIGATION;
if (adapter==invokeMethodOnCurrent) ret |= THIS_CALL;
if (spread) ret |= SPREAD_CALL;
return ret;
}
@Override
public void makeSingleArgumentCall(Expression receiver, String message, Expression arguments, boolean safe) {
makeIndyCall(invokeMethod, receiver, false, safe, message, arguments);
}
private static int getPropertyFlags(boolean safe, boolean implicitThis, boolean groovyObject) {
int flags = 0;
if (implicitThis) flags |= IMPLICIT_THIS;
if (groovyObject) flags |= GROOVY_OBJECT;
if (safe) flags |= SAFE_NAVIGATION;
return flags;
}
protected void writeGetProperty(Expression receiver, String propertyName, boolean safe, boolean implicitThis, boolean groovyObject) {
String sig = prepareIndyCall(receiver, implicitThis);
sig += ")Ljava/lang/Object;";
int flags = getPropertyFlags(safe,implicitThis,groovyObject);
finishIndyCall(BSM, GET.getCallSiteName(), sig, 1, propertyName, flags);
}
@Override
protected void writeNormalConstructorCall(ConstructorCallExpression call) {
makeCall(call, new ClassExpression(call.getType()), new ConstantExpression("<init>"), call.getArguments(), null, false, false, false);
}
@Override
public void coerce(ClassNode from, ClassNode target) {
ClassNode wrapper = ClassHelper.getWrapper(target);
makeIndyCall(invokeMethod, EmptyExpression.INSTANCE, false, false, "asType", new ClassExpression(wrapper));
if (ClassHelper.boolean_TYPE.equals(target) || ClassHelper.Boolean_TYPE.equals(target)) {
writeIndyCast(ClassHelper.OBJECT_TYPE,target);
} else {
BytecodeHelper.doCast(controller.getMethodVisitor(), wrapper);
controller.getOperandStack().replace(wrapper);
controller.getOperandStack().doGroovyCast(target);
}
}
@Override
public void castToNonPrimitiveIfNecessary(ClassNode sourceType, ClassNode targetType) {
ClassNode boxedType = ClassHelper.getWrapper(sourceType);
if (WideningCategories.implementsInterfaceOrSubclassOf(boxedType, targetType)) {
controller.getOperandStack().box();
return;
}
writeIndyCast(sourceType, targetType);
}
private void writeIndyCast(ClassNode sourceType, ClassNode targetType) {
String sig = "(" +
getTypeDescription(sourceType) +
')' +
getTypeDescription(targetType);
controller.getMethodVisitor().visitInvokeDynamicInsn(
//TODO: maybe use a different bootstrap method since no arguments are needed here
CAST.getCallSiteName(), sig, BSM, "()", 0);
controller.getOperandStack().replace(targetType);
}
@Override
public void castNonPrimitiveToBool(ClassNode sourceType) {
writeIndyCast(sourceType, ClassHelper.boolean_TYPE);
}
}