diff --git a/base/org.codehaus.groovy25/.checkstyle b/base/org.codehaus.groovy25/.checkstyle index 935fb52d90..afc32e171d 100644 --- a/base/org.codehaus.groovy25/.checkstyle +++ b/base/org.codehaus.groovy25/.checkstyle @@ -46,6 +46,7 @@ + diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/CallSiteWriter.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/CallSiteWriter.java new file mode 100644 index 0000000000..b733f4cc6c --- /dev/null +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/CallSiteWriter.java @@ -0,0 +1,404 @@ +/* + * 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.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.runtime.callsite.CallSite; +import groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.AASTORE; +import static groovyjarjarasm.asm.Opcodes.ACC_PRIVATE; +import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC; +import static groovyjarjarasm.asm.Opcodes.ACC_STATIC; +import static groovyjarjarasm.asm.Opcodes.ACC_SYNTHETIC; +import static groovyjarjarasm.asm.Opcodes.ACONST_NULL; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ANEWARRAY; +import static groovyjarjarasm.asm.Opcodes.ARETURN; +import static groovyjarjarasm.asm.Opcodes.ASTORE; +import static groovyjarjarasm.asm.Opcodes.CHECKCAST; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.GETFIELD; +import static groovyjarjarasm.asm.Opcodes.GETSTATIC; +import static groovyjarjarasm.asm.Opcodes.IFNONNULL; +import static groovyjarjarasm.asm.Opcodes.IFNULL; +import static groovyjarjarasm.asm.Opcodes.INVOKEINTERFACE; +import static groovyjarjarasm.asm.Opcodes.INVOKESPECIAL; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.INVOKEVIRTUAL; +import static groovyjarjarasm.asm.Opcodes.NEW; +import static groovyjarjarasm.asm.Opcodes.NOP; +import static groovyjarjarasm.asm.Opcodes.PUTSTATIC; +import static groovyjarjarasm.asm.Opcodes.RETURN; + +/** + * This class represents non public API used by AsmClassGenerator. Don't + * use this class in your code + */ +public class CallSiteWriter { + private static final int SIG_ARRAY_LENGTH = 255; + private static String [] sig = new String [SIG_ARRAY_LENGTH]; + private static String getCreateArraySignature(int numberOfArguments) { + if (numberOfArguments >= SIG_ARRAY_LENGTH) { + throw new IllegalArgumentException(String.format( + "The max number of supported arguments is %s, but found %s", + SIG_ARRAY_LENGTH, 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()) { + // GRECLIPSE add -- GROOVY-9076 + controller.getMethodVisitor().visitInsn(NOP); + // GRECLIPSE end + 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", "", "(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 callSiteInitMethods = new LinkedList(); + 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, "", "(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 void makeSingleArgumentCall(Expression receiver, String message, Expression arguments) { + 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", "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 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; + } + + public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expression objectExpression, String name, MethodCallerMultiAdapter adapter) { + if (controller.getCompileStack().isLHS()) controller.getOperandStack().box(); + controller.getInvocationWriter().makeCall( + expression, + objectExpression, // receiver + new CastExpression(ClassHelper.STRING_TYPE, expression.getProperty()), // messageName + MethodCallExpression.NO_ARGUMENTS, adapter, + expression.isSafe(), expression.isSpreadSafe(), expression.isImplicitThis() + ); + } +} diff --git a/base/org.codehaus.groovy30/.checkstyle b/base/org.codehaus.groovy30/.checkstyle index 31f8e85a68..e13eecd0d0 100644 --- a/base/org.codehaus.groovy30/.checkstyle +++ b/base/org.codehaus.groovy30/.checkstyle @@ -45,6 +45,7 @@ + diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/CallSiteWriter.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/CallSiteWriter.java new file mode 100644 index 0000000000..3b8ed8f059 --- /dev/null +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/CallSiteWriter.java @@ -0,0 +1,408 @@ +/* + * 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.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.runtime.callsite.CallSite; +import groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.AASTORE; +import static groovyjarjarasm.asm.Opcodes.ACC_PRIVATE; +import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC; +import static groovyjarjarasm.asm.Opcodes.ACC_STATIC; +import static groovyjarjarasm.asm.Opcodes.ACC_SYNTHETIC; +import static groovyjarjarasm.asm.Opcodes.ACONST_NULL; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ANEWARRAY; +import static groovyjarjarasm.asm.Opcodes.ARETURN; +import static groovyjarjarasm.asm.Opcodes.ASTORE; +import static groovyjarjarasm.asm.Opcodes.CHECKCAST; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.GETFIELD; +import static groovyjarjarasm.asm.Opcodes.GETSTATIC; +import static groovyjarjarasm.asm.Opcodes.IFNONNULL; +import static groovyjarjarasm.asm.Opcodes.IFNULL; +import static groovyjarjarasm.asm.Opcodes.INVOKEINTERFACE; +import static groovyjarjarasm.asm.Opcodes.INVOKESPECIAL; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.INVOKEVIRTUAL; +import static groovyjarjarasm.asm.Opcodes.NEW; +import static groovyjarjarasm.asm.Opcodes.NOP; +import static groovyjarjarasm.asm.Opcodes.PUTSTATIC; +import static groovyjarjarasm.asm.Opcodes.RETURN; + +/** + * This class represents non public API used by AsmClassGenerator. Don't + * use this class in your code + */ +public class CallSiteWriter { + private static final int SIG_ARRAY_LENGTH = 255; + private static String [] sig = new String [SIG_ARRAY_LENGTH]; + private static String getCreateArraySignature(int numberOfArguments) { + if (numberOfArguments >= SIG_ARRAY_LENGTH) { + throw new IllegalArgumentException(String.format( + "The max number of supported arguments is %s, but found %s", + SIG_ARRAY_LENGTH, 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()) { + // GRECLIPSE add -- GROOVY-9076 + controller.getMethodVisitor().visitInsn(NOP); + // GRECLIPSE end + 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", "", "(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 callSiteInitMethods = new LinkedList(); + 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, "", "(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 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; + } + + public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expression objectExpression, String name, MethodCallerMultiAdapter adapter) { + if (controller.getCompileStack().isLHS()) controller.getOperandStack().box(); + controller.getInvocationWriter().makeCall( + expression, + objectExpression, // receiver + new CastExpression(ClassHelper.STRING_TYPE, expression.getProperty()), // messageName + MethodCallExpression.NO_ARGUMENTS, adapter, + expression.isSafe(), expression.isSpreadSafe(), expression.isImplicitThis() + ); + } +}