/* * 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; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.InterfaceHelperClassNode; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.classgen.AsmClassGenerator; import org.codehaus.groovy.runtime.callsite.CallSite; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import java.util.*; import static org.objectweb.asm.Opcodes.*; /** * This class represents non public API used by AsmClassGenerator. Don't * use this class in your code * @author Jochen Theodorou */ public class CallSiteWriter { private static final Set<String> NAMES = new HashSet<String>(); private static final Set<String> BASIC = new HashSet<String>(); static { Collections.addAll(NAMES, "plus", "minus", "multiply", "div", "compareTo", "or", "and", "xor", "intdiv", "mod", "leftShift", "rightShift", "rightShiftUnsigned"); Collections.addAll(BASIC, "plus", "minus", "multiply", "div"); } private static String [] sig = new String [255]; private static String getCreateArraySignature(int numberOfArguments) { if (sig[numberOfArguments] == null) { StringBuilder sb = new StringBuilder("("); for (int i = 0; i != numberOfArguments; ++i) { sb.append("Ljava/lang/Object;"); } sb.append(")[Ljava/lang/Object;"); sig[numberOfArguments] = sb.toString(); } return sig[numberOfArguments]; } private static final int MOD_PRIVSS = ACC_PRIVATE+ACC_STATIC+ACC_SYNTHETIC, MOD_PUBSS = ACC_PUBLIC+ACC_STATIC+ACC_SYNTHETIC; private static final ClassNode CALLSITE_ARRAY_NODE = ClassHelper.make(CallSite[].class); private static final String GET_CALLSITE_METHOD = "$getCallSiteArray", CALLSITE_CLASS = "org/codehaus/groovy/runtime/callsite/CallSite", CALLSITE_DESC = "[Lorg/codehaus/groovy/runtime/callsite/CallSite;", GET_CALLSITE_DESC = "()"+CALLSITE_DESC, CALLSITE_ARRAY_CLASS = "org/codehaus/groovy/runtime/callsite/CallSiteArray", GET_CALLSITEARRAY_DESC = "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;", CALLSITE_FIELD = "$callSiteArray", REF_CLASS = "java/lang/ref/SoftReference", REF_DESC = "L"+REF_CLASS+";", METHOD_OO_DESC = "(Ljava/lang/Object;)Ljava/lang/Object;", CREATE_CSA_METHOD = "$createCallSiteArray"; public static final String CONSTRUCTOR = "<$constructor$>"; private final List callSites = new ArrayList(32); private int callSiteArrayVarIndex = -1; private final WriterController controller; public CallSiteWriter(WriterController wc) { this.controller = wc; ClassNode node = controller.getClassNode(); if(node instanceof InterfaceHelperClassNode) { InterfaceHelperClassNode ihcn = (InterfaceHelperClassNode) node; callSites.addAll(ihcn.getCallSites()); } } public void makeSiteEntry() { if (controller.isNotClinit()) { controller.getMethodVisitor().visitMethodInsn(INVOKESTATIC, controller.getInternalClassName(), GET_CALLSITE_METHOD, GET_CALLSITE_DESC, false); controller.getOperandStack().push(CALLSITE_ARRAY_NODE); callSiteArrayVarIndex = controller.getCompileStack().defineTemporaryVariable("$local$callSiteArray", CALLSITE_ARRAY_NODE, true); } } public void generateCallSiteArray() { if (!controller.getClassNode().isInterface()) { controller.getClassVisitor().visitField(MOD_PRIVSS, CALLSITE_FIELD, REF_DESC, null, null); generateCreateCallSiteArray(); generateGetCallSiteArray(); } } private void generateGetCallSiteArray() { int visibility = (controller.getClassNode() instanceof InterfaceHelperClassNode) ? MOD_PUBSS : MOD_PRIVSS; MethodVisitor mv = controller.getClassVisitor().visitMethod(visibility, GET_CALLSITE_METHOD, GET_CALLSITE_DESC, null, null); controller.setMethodVisitor(mv); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, controller.getInternalClassName(), "$callSiteArray", "Ljava/lang/ref/SoftReference;"); Label l0 = new Label(); mv.visitJumpInsn(IFNULL, l0); mv.visitFieldInsn(GETSTATIC, controller.getInternalClassName(), "$callSiteArray", "Ljava/lang/ref/SoftReference;"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ref/SoftReference", "get", "()Ljava/lang/Object;", false); mv.visitTypeInsn(CHECKCAST, "org/codehaus/groovy/runtime/callsite/CallSiteArray"); mv.visitInsn(DUP); mv.visitVarInsn(ASTORE, 0); Label l1 = new Label(); mv.visitJumpInsn(IFNONNULL, l1); mv.visitLabel(l0); mv.visitMethodInsn(INVOKESTATIC, controller.getInternalClassName(), "$createCallSiteArray", "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;", false); mv.visitVarInsn(ASTORE, 0); mv.visitTypeInsn(NEW, "java/lang/ref/SoftReference"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/ref/SoftReference", "<init>", "(Ljava/lang/Object;)V", false); mv.visitFieldInsn(PUTSTATIC, controller.getInternalClassName(), "$callSiteArray", "Ljava/lang/ref/SoftReference;"); mv.visitLabel(l1); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "array", "[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } private void generateCreateCallSiteArray() { List<String> callSiteInitMethods = new LinkedList<String>(); int index = 0; int methodIndex = 0; final int size = callSites.size(); final int maxArrayInit = 5000; // create array initialization methods while (index < size) { methodIndex++; String methodName = "$createCallSiteArray_" + methodIndex; callSiteInitMethods.add(methodName); MethodVisitor mv = controller.getClassVisitor().visitMethod(MOD_PRIVSS, methodName, "([Ljava/lang/String;)V", null, null); controller.setMethodVisitor(mv); mv.visitCode(); int methodLimit = size; // check if the next block is over the max allowed if ((methodLimit - index) > maxArrayInit) { methodLimit = index + maxArrayInit; } for (; index < methodLimit; index++) { mv.visitVarInsn(ALOAD, 0); mv.visitLdcInsn(index); mv.visitLdcInsn(callSites.get(index)); mv.visitInsn(AASTORE); } mv.visitInsn(RETURN); mv.visitMaxs(2,1); mv.visitEnd(); } // create base createCallSiteArray method MethodVisitor mv = controller.getClassVisitor().visitMethod(MOD_PRIVSS, CREATE_CSA_METHOD, GET_CALLSITEARRAY_DESC, null, null); controller.setMethodVisitor(mv); mv.visitCode(); mv.visitLdcInsn(size); mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); mv.visitVarInsn(ASTORE, 0); for (String methodName : callSiteInitMethods) { mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESTATIC, controller.getInternalClassName(), methodName, "([Ljava/lang/String;)V", false); } mv.visitTypeInsn(NEW, CALLSITE_ARRAY_CLASS); mv.visitInsn(DUP); controller.getAcg().visitClassExpression(new ClassExpression(controller.getClassNode())); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, CALLSITE_ARRAY_CLASS, "<init>", "(Ljava/lang/Class;[Ljava/lang/String;)V", false); mv.visitInsn(ARETURN); mv.visitMaxs(0,0); mv.visitEnd(); } private int allocateIndex(String name) { callSites.add(name); return callSites.size()-1; } private void invokeSafe(boolean safe, String unsafeMethod, String safeMethod) { String method = unsafeMethod; if (safe) method = safeMethod; controller.getMethodVisitor().visitMethodInsn(INVOKEINTERFACE, CALLSITE_CLASS, method, METHOD_OO_DESC, true); controller.getOperandStack().replace(ClassHelper.OBJECT_TYPE); } public void prepareCallSite(String message) { MethodVisitor mv = controller.getMethodVisitor(); if (controller.isNotClinit()) { mv.visitVarInsn(ALOAD, callSiteArrayVarIndex); } else { mv.visitMethodInsn(INVOKESTATIC, controller.getClassName(), GET_CALLSITE_METHOD, GET_CALLSITE_DESC, false); } final int index = allocateIndex(message); mv.visitLdcInsn(index); mv.visitInsn(AALOAD); } private void prepareSiteAndReceiver(Expression receiver, String methodName, boolean implicitThis) { prepareSiteAndReceiver(receiver, methodName, implicitThis, false); } protected void prepareSiteAndReceiver(Expression receiver, String methodName, boolean implicitThis, boolean lhs) { //site prepareCallSite(methodName); // receiver CompileStack compileStack = controller.getCompileStack(); compileStack.pushImplicitThis(implicitThis); compileStack.pushLHS(lhs); receiver.visit(controller.getAcg()); controller.getOperandStack().box(); compileStack.popLHS(); compileStack.popImplicitThis(); } protected void visitBoxedArgument(Expression exp) { exp.visit(controller.getAcg()); if (!(exp instanceof TupleExpression)) { // we are not in a tuple, so boxing might be missing for // this single argument call controller.getOperandStack().box(); } } public final void makeSingleArgumentCall(Expression receiver, String message, Expression arguments) { makeSingleArgumentCall(receiver, message, arguments, false); } public void makeSingleArgumentCall(Expression receiver, String message, Expression arguments, boolean safe) { OperandStack operandStack = controller.getOperandStack(); int m1 = operandStack.getStackLength(); //slow Path prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS()); visitBoxedArgument(arguments); int m2 = operandStack.getStackLength(); controller.getMethodVisitor().visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", safe ? "callSafe" : "call", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true); operandStack.replace(ClassHelper.OBJECT_TYPE, m2-m1); } public void makeGroovyObjectGetPropertySite(Expression receiver, String methodName, boolean safe, boolean implicitThis) { prepareSiteAndReceiver(receiver, methodName, implicitThis); invokeSafe(safe, "callGroovyObjectGetProperty", "callGroovyObjectGetPropertySafe"); } public void makeGetPropertySite(Expression receiver, String methodName, boolean safe, boolean implicitThis) { prepareSiteAndReceiver(receiver, methodName, implicitThis); invokeSafe(safe, "callGetProperty", "callGetPropertySafe"); } public void makeCallSite(Expression receiver, String message, Expression arguments, boolean safe, boolean implicitThis, boolean callCurrent, boolean callStatic) { prepareSiteAndReceiver(receiver, message, implicitThis); CompileStack compileStack = controller.getCompileStack(); compileStack.pushImplicitThis(implicitThis); compileStack.pushLHS(false); boolean constructor = message.equals(CONSTRUCTOR); OperandStack operandStack = controller.getOperandStack(); // arguments boolean containsSpreadExpression = AsmClassGenerator.containsSpreadExpression(arguments); int numberOfArguments = containsSpreadExpression ? -1 : AsmClassGenerator.argumentSize(arguments); int operandsToReplace = 1; if (numberOfArguments > MethodCallerMultiAdapter.MAX_ARGS || containsSpreadExpression) { ArgumentListExpression ae; if (arguments instanceof ArgumentListExpression) { ae = (ArgumentListExpression) arguments; } else if (arguments instanceof TupleExpression) { TupleExpression te = (TupleExpression) arguments; ae = new ArgumentListExpression(te.getExpressions()); } else { ae = new ArgumentListExpression(); ae.addExpression(arguments); } controller.getCompileStack().pushImplicitThis(false); if (containsSpreadExpression) { numberOfArguments = -1; controller.getAcg().despreadList(ae.getExpressions(), true); } else { numberOfArguments = ae.getExpressions().size(); for (int i = 0; i < numberOfArguments; i++) { Expression argument = ae.getExpression(i); argument.visit(controller.getAcg()); operandStack.box(); if (argument instanceof CastExpression) controller.getAcg().loadWrapper(argument); } operandsToReplace += numberOfArguments; } controller.getCompileStack().popImplicitThis(); } controller.getCompileStack().popLHS(); controller.getCompileStack().popImplicitThis(); MethodVisitor mv = controller.getMethodVisitor(); if (numberOfArguments > 4) { final String createArraySignature = getCreateArraySignature(numberOfArguments); mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/ArrayUtil", "createArray", createArraySignature, false); //TODO: use pre-generated Object[] operandStack.replace(ClassHelper.OBJECT_TYPE.makeArray(),numberOfArguments); operandsToReplace = operandsToReplace-numberOfArguments+1; } final String desc = getDescForParamNum(numberOfArguments); if (callStatic) { mv.visitMethodInsn(INVOKEINTERFACE, CALLSITE_CLASS, "callStatic", "(Ljava/lang/Class;" + desc, true); } else if (constructor) { mv.visitMethodInsn(INVOKEINTERFACE, CALLSITE_CLASS, "callConstructor", "(Ljava/lang/Object;" + desc, true); } else if (callCurrent) { mv.visitMethodInsn(INVOKEINTERFACE, CALLSITE_CLASS, "callCurrent", "(Lgroovy/lang/GroovyObject;" + desc, true); } else if (safe) { mv.visitMethodInsn(INVOKEINTERFACE, CALLSITE_CLASS, "callSafe", "(Ljava/lang/Object;" + desc, true); } else { mv.visitMethodInsn(INVOKEINTERFACE, CALLSITE_CLASS, "call", "(Ljava/lang/Object;" + desc, true); } operandStack.replace(ClassHelper.OBJECT_TYPE,operandsToReplace); } private static String getDescForParamNum(int numberOfArguments) { switch (numberOfArguments) { case 0: return ")Ljava/lang/Object;"; case 1: return "Ljava/lang/Object;)Ljava/lang/Object;"; case 2: return "Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"; case 3: return "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"; case 4: return "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"; default: return "[Ljava/lang/Object;)Ljava/lang/Object;"; } } public List<String> getCallSites() { return callSites; } public void makeCallSiteArrayInitializer() { final String classInternalName = BytecodeHelper.getClassInternalName(controller.getClassNode()); MethodVisitor mv = controller.getMethodVisitor(); mv.visitInsn(ACONST_NULL); mv.visitFieldInsn(PUTSTATIC, classInternalName, "$callSiteArray", "Ljava/lang/ref/SoftReference;"); } public boolean hasCallSiteUse() { return callSiteArrayVarIndex>=0; } }