Java tutorial
/* * 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 org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import static org.objectweb.asm.Opcodes.AALOAD; import static org.objectweb.asm.Opcodes.AASTORE; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; import static org.objectweb.asm.Opcodes.ACONST_NULL; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ANEWARRAY; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.ASTORE; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DUP; import static org.objectweb.asm.Opcodes.GETFIELD; import static org.objectweb.asm.Opcodes.GETSTATIC; import static org.objectweb.asm.Opcodes.IFNONNULL; import static org.objectweb.asm.Opcodes.IFNULL; import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.PUTSTATIC; import static org.objectweb.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<String> callSites = new ArrayList<String>(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; } 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()); } }