http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java new file mode 100644 index 0000000..716f97e --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java @@ -0,0 +1,387 @@ +/* + * 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.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 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; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java new file mode 100644 index 0000000..af305ba --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java @@ -0,0 +1,392 @@ +/* + * 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.GroovyBugError; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.VariableScope; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.FieldExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.Verifier; +import org.objectweb.asm.MethodVisitor; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +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.ALOAD; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.GETFIELD; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.NEW; + +public class ClosureWriter { + + protected interface UseExistingReference {} + + private final Map<Expression,ClassNode> closureClassMap; + private final WriterController controller; + private final WriterControllerFactory factory; + + public ClosureWriter(WriterController wc) { + this.controller = wc; + closureClassMap = new HashMap<Expression,ClassNode>(); + factory = new WriterControllerFactory() { + public WriterController makeController(final WriterController normalController) { + return controller; + } + }; + } + + public void writeClosure(ClosureExpression expression) { + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + ClassNode classNode = controller.getClassNode(); + AsmClassGenerator acg = controller.getAcg(); + + // generate closure as public class to make sure it can be properly invoked by classes of the + // Groovy runtime without circumventing JVM access checks (see CachedMethod for example). + int mods = ACC_PUBLIC; + if (classNode.isInterface()) { + mods |= ACC_STATIC; + } + ClassNode closureClass = getOrAddClosureClass(expression, mods); + String closureClassinternalName = BytecodeHelper.getClassInternalName(closureClass); + List constructors = closureClass.getDeclaredConstructors(); + ConstructorNode node = (ConstructorNode) constructors.get(0); + + Parameter[] localVariableParams = node.getParameters(); + + mv.visitTypeInsn(NEW, closureClassinternalName); + mv.visitInsn(DUP); + if (controller.isStaticMethod() || compileStack.isInSpecialConstructorCall()) { + (new ClassExpression(classNode)).visit(acg); + (new ClassExpression(controller.getOutermostClass())).visit(acg); + } else { + mv.visitVarInsn(ALOAD, 0); + controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); + loadThis(); + } + + // now let's load the various parameters we're passing + // we start at index 2 because the first variable we pass + // is the owner instance and at this point it is already + // on the stack + for (int i = 2; i < localVariableParams.length; i++) { + Parameter param = localVariableParams[i]; + String name = param.getName(); + loadReference(name, controller); + if (param.getNodeMetaData(ClosureWriter.UseExistingReference.class)==null) { + param.setNodeMetaData(ClosureWriter.UseExistingReference.class,Boolean.TRUE); + } + } + + // we may need to pass in some other constructors + //cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "<init>", prototype + ")V"); + mv.visitMethodInsn(INVOKESPECIAL, closureClassinternalName, "<init>", BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, localVariableParams), false); + controller.getOperandStack().replace(ClassHelper.CLOSURE_TYPE, localVariableParams.length); + } + + public static void loadReference(String name, WriterController controller) { + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + ClassNode classNode = controller.getClassNode(); + AsmClassGenerator acg = controller.getAcg(); + + // compileStack.containsVariable(name) means to ask if the variable is already declared + // compileStack.getScope().isReferencedClassVariable(name) means to ask if the variable is a field + // If it is no field and is not yet declared, then it is either a closure shared variable or + // an already declared variable. + if (!compileStack.containsVariable(name) && compileStack.getScope().isReferencedClassVariable(name)) { + acg.visitFieldExpression(new FieldExpression(classNode.getDeclaredField(name))); + } else { + BytecodeVariable v = compileStack.getVariable(name, !classNodeUsesReferences(controller.getClassNode())); + if (v == null) { + // variable is not on stack because we are + // inside a nested Closure and this variable + // was not used before + // then load it from the Closure field + FieldNode field = classNode.getDeclaredField(name); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, controller.getInternalClassName(), name, BytecodeHelper.getTypeDescription(field.getType())); + } else { + mv.visitVarInsn(ALOAD, v.getIndex()); + } + controller.getOperandStack().push(ClassHelper.REFERENCE_TYPE); + } + } + + public ClassNode getOrAddClosureClass(ClosureExpression expression, int mods) { + ClassNode closureClass = closureClassMap.get(expression); + if (closureClass == null) { + closureClass = createClosureClass(expression, mods); + closureClassMap.put(expression, closureClass); + controller.getAcg().addInnerClass(closureClass); + closureClass.addInterface(ClassHelper.GENERATED_CLOSURE_Type); + closureClass.putNodeMetaData(WriterControllerFactory.class, factory); + } + return closureClass; + } + + private static boolean classNodeUsesReferences(ClassNode classNode) { + boolean ret = classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE; + if (ret) return ret; + if (classNode instanceof InnerClassNode) { + InnerClassNode inner = (InnerClassNode) classNode; + return inner.isAnonymous(); + } + return false; + } + + protected ClassNode createClosureClass(ClosureExpression expression, int mods) { + ClassNode classNode = controller.getClassNode(); + ClassNode outerClass = controller.getOutermostClass(); + MethodNode methodNode = controller.getMethodNode(); + String name = classNode.getName() + "$" + + controller.getContext().getNextClosureInnerName(outerClass, classNode, methodNode); // add a more informative name + boolean staticMethodOrInStaticClass = controller.isStaticMethod() || classNode.isStaticClass(); + + Parameter[] parameters = expression.getParameters(); + if (parameters == null) { + parameters = Parameter.EMPTY_ARRAY; + } else if (parameters.length == 0) { + // let's create a default 'it' parameter + Parameter it = new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL); + parameters = new Parameter[]{it}; + Variable ref = expression.getVariableScope().getDeclaredVariable("it"); + if (ref!=null) it.setClosureSharedVariable(ref.isClosureSharedVariable()); + } + + Parameter[] localVariableParams = getClosureSharedVariables(expression); + removeInitialValues(localVariableParams); + + InnerClassNode answer = new InnerClassNode(classNode, name, mods, ClassHelper.CLOSURE_TYPE.getPlainNodeReference()); + answer.setEnclosingMethod(controller.getMethodNode()); + answer.setSynthetic(true); + answer.setUsingGenerics(outerClass.isUsingGenerics()); + answer.setSourcePosition(expression); + + if (staticMethodOrInStaticClass) { + answer.setStaticClass(true); + } + if (controller.isInScriptBody()) { + answer.setScriptBody(true); + } + MethodNode method = + answer.addMethod("doCall", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, parameters, ClassNode.EMPTY_ARRAY, expression.getCode()); + method.setSourcePosition(expression); + + VariableScope varScope = expression.getVariableScope(); + if (varScope == null) { + throw new RuntimeException( + "Must have a VariableScope by now! for expression: " + expression + " class: " + name); + } else { + method.setVariableScope(varScope.copy()); + } + if (parameters.length > 1 + || (parameters.length == 1 + && parameters[0].getType() != null + && parameters[0].getType() != ClassHelper.OBJECT_TYPE + && !ClassHelper.OBJECT_TYPE.equals(parameters[0].getType().getComponentType()))) + { + + // let's add a typesafe call method + MethodNode call = answer.addMethod( + "call", + ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + new ReturnStatement( + new MethodCallExpression( + VariableExpression.THIS_EXPRESSION, + "doCall", + new ArgumentListExpression(parameters)))); + call.setSourcePosition(expression); + } + + // let's make the constructor + BlockStatement block = new BlockStatement(); + // this block does not get a source position, because we don't + // want this synthetic constructor to show up in corbertura reports + VariableExpression outer = new VariableExpression("_outerInstance"); + outer.setSourcePosition(expression); + block.getVariableScope().putReferencedLocalVariable(outer); + VariableExpression thisObject = new VariableExpression("_thisObject"); + thisObject.setSourcePosition(expression); + block.getVariableScope().putReferencedLocalVariable(thisObject); + TupleExpression conArgs = new TupleExpression(outer, thisObject); + block.addStatement( + new ExpressionStatement( + new ConstructorCallExpression( + ClassNode.SUPER, + conArgs))); + + // let's assign all the parameter fields from the outer context + for (Parameter param : localVariableParams) { + String paramName = param.getName(); + ClassNode type = param.getType(); + if (true) { + VariableExpression initialValue = new VariableExpression(paramName); + initialValue.setAccessedVariable(param); + initialValue.setUseReferenceDirectly(true); + ClassNode realType = type; + type = ClassHelper.makeReference(); + param.setType(ClassHelper.makeReference()); + FieldNode paramField = answer.addField(paramName, ACC_PRIVATE | ACC_SYNTHETIC, type, initialValue); + paramField.setOriginType(ClassHelper.getWrapper(param.getOriginType())); + paramField.setHolder(true); + String methodName = Verifier.capitalize(paramName); + + // let's add a getter & setter + Expression fieldExp = new FieldExpression(paramField); + answer.addMethod( + "get" + methodName, + ACC_PUBLIC, + realType.getPlainNodeReference(), + Parameter.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, + new ReturnStatement(fieldExp)); + } + } + + Parameter[] params = new Parameter[2 + localVariableParams.length]; + params[0] = new Parameter(ClassHelper.OBJECT_TYPE, "_outerInstance"); + params[1] = new Parameter(ClassHelper.OBJECT_TYPE, "_thisObject"); + System.arraycopy(localVariableParams, 0, params, 2, localVariableParams.length); + + ASTNode sn = answer.addConstructor(ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, block); + sn.setSourcePosition(expression); + + correctAccessedVariable(answer,expression); + + return answer; + } + + private static void correctAccessedVariable(final InnerClassNode closureClass, ClosureExpression ce) { + CodeVisitorSupport visitor = new CodeVisitorSupport() { + @Override + public void visitVariableExpression(VariableExpression expression) { + Variable v = expression.getAccessedVariable(); + if (v==null) return; + if (!(v instanceof FieldNode)) return; + String name = expression.getName(); + FieldNode fn = closureClass.getDeclaredField(name); + if (fn != null) { // only overwrite if we find something more specific + expression.setAccessedVariable(fn); + } + } + }; + visitor.visitClosureExpression(ce); + } + + /* + * this method is called for local variables shared between scopes. + * These variables must not have init values because these would + * then in later steps be used to create multiple versions of the + * same method, in this case the constructor. A closure should not + * have more than one constructor! + */ + private static void removeInitialValues(Parameter[] params) { + for (int i = 0; i < params.length; i++) { + if (params[i].hasInitialExpression()) { + Parameter p = new Parameter(params[i].getType(), params[i].getName()); + p.setOriginType(p.getOriginType()); + params[i] = p; + } + } + } + + public boolean addGeneratedClosureConstructorCall(ConstructorCallExpression call) { + ClassNode classNode = controller.getClassNode(); + if (!classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) return false; + + AsmClassGenerator acg = controller.getAcg(); + OperandStack operandStack = controller.getOperandStack(); + + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitVarInsn(ALOAD, 0); + ClassNode callNode = classNode.getSuperClass(); + TupleExpression arguments = (TupleExpression) call.getArguments(); + if (arguments.getExpressions().size()!=2) throw new GroovyBugError("expected 2 arguments for closure constructor super call, but got"+arguments.getExpressions().size()); + arguments.getExpression(0).visit(acg); + operandStack.box(); + arguments.getExpression(1).visit(acg); + operandStack.box(); + //TODO: replace with normal String, p not needed + Parameter p = new Parameter(ClassHelper.OBJECT_TYPE,"_p"); + String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, new Parameter[]{p,p}); + mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "<init>", descriptor, false); + operandStack.remove(2); + return true; + } + + protected Parameter[] getClosureSharedVariables(ClosureExpression ce) { + VariableScope scope = ce.getVariableScope(); + Parameter[] ret = new Parameter[scope.getReferencedLocalVariablesCount()]; + int index = 0; + for (Iterator iter = scope.getReferencedLocalVariablesIterator(); iter.hasNext();) { + Variable element = (org.codehaus.groovy.ast.Variable) iter.next(); + Parameter p = new Parameter(element.getType(), element.getName()); + p.setOriginType(element.getOriginType()); + p.setClosureSharedVariable(element.isClosureSharedVariable()); + ret[index] = p; + index++; + } + return ret; + } + + private void loadThis() { + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitVarInsn(ALOAD, 0); + if (controller.isInClosure()) { + mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Closure", "getThisObject", "()Ljava/lang/Object;", false); + controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); + } else { + controller.getOperandStack().push(controller.getClassNode()); + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java b/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java new file mode 100644 index 0000000..35133bb --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java @@ -0,0 +1,872 @@ +/* + * 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.GroovyBugError; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.VariableScope; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +/** + * This class is a helper for AsmClassGenerator. It manages + * different aspects of the code of a code block like + * handling labels, defining variables, and scopes. + * After a MethodNode is visited clear should be called, for + * initialization the method init should be used. + * <p> + * Some Notes: + * <ul> + * <li> every push method will require a later pop call + * <li> method parameters may define a category 2 variable, so + * don't ignore the type stored in the variable object + * <li> the index of the variable may not be as assumed when + * the variable is a parameter of a method because the + * parameter may be used in a closure, so don't ignore + * the stored variable index + * <li> the names of temporary variables can be ignored. The names + * are only used for debugging and do not conflict with each + * other or normal variables. For accessing, the index of the + * variable must be used. + * <li> never mix temporary and normal variables by changes to this class. + * While the name is very important for a normal variable, it is only a + * helper construct for temporary variables. That means for example a + * name for a temporary variable can be used multiple times without + * conflict. So mixing them both may lead to the problem that a normal + * or temporary variable is hidden or even removed. That must not happen! + * </ul> + * + * + * @see org.codehaus.groovy.classgen.AsmClassGenerator + * @author Jochen Theodorou + */ +public class CompileStack implements Opcodes { + /** + * TODO: remove optimization of this.foo -> this.@foo + * + */ + + // state flag + private boolean clear=true; + // current scope + private VariableScope scope; + // current label for continue + private Label continueLabel; + // current label for break + private Label breakLabel; + // available variables on stack + private Map stackVariables = new HashMap(); + // index of the last variable on stack + private int currentVariableIndex = 1; + // index for the next variable on stack + private int nextVariableIndex = 1; + // currently temporary variables in use + private final LinkedList temporaryVariables = new LinkedList(); + // overall used variables for a method/constructor + private final LinkedList usedVariables = new LinkedList(); + // map containing named labels of parenting blocks + private Map superBlockNamedLabels = new HashMap(); + // map containing named labels of current block + private Map currentBlockNamedLabels = new HashMap(); + // list containing finally blocks + // such a block is created by synchronized or finally and + // must be called for break/continue/return + private LinkedList<BlockRecorder> finallyBlocks = new LinkedList<BlockRecorder>(); + private final LinkedList<BlockRecorder> visitedBlocks = new LinkedList<BlockRecorder>(); + + private Label thisStartLabel, thisEndLabel; + +// private MethodVisitor mv; + + // helper to handle different stack based variables + private final LinkedList stateStack = new LinkedList(); + + // handle different states for the implicit "this" + private final LinkedList<Boolean> implicitThisStack = new LinkedList<Boolean>(); + // handle different states for being on the left hand side + private final LinkedList<Boolean> lhsStack = new LinkedList<Boolean>(); + { + implicitThisStack.add(false); + lhsStack.add(false); + } + + // defines the first variable index usable after + // all parameters of a method + private int localVariableOffset; + // this is used to store the goals for a "break foo" call + // in a loop where foo is a label. + private final Map namedLoopBreakLabel = new HashMap(); + // this is used to store the goals for a "continue foo" call + // in a loop where foo is a label. + private final Map namedLoopContinueLabel = new HashMap(); + private String className; + private final LinkedList<ExceptionTableEntry> typedExceptions = new LinkedList<ExceptionTableEntry>(); + private final LinkedList<ExceptionTableEntry> untypedExceptions = new LinkedList<ExceptionTableEntry>(); + // stores if on left-hand-side during compilation + private boolean lhs; + // stores if implicit or explicit this is used. + private boolean implicitThis; + private final WriterController controller; + private boolean inSpecialConstructorCall; + + protected static class LabelRange { + public Label start; + public Label end; + } + + public static class BlockRecorder { + private boolean isEmpty = true; + public Runnable excludedStatement; + public final LinkedList<LabelRange> ranges; + public BlockRecorder() { + ranges = new LinkedList<LabelRange>(); + } + public BlockRecorder(Runnable excludedStatement) { + this(); + this.excludedStatement = excludedStatement; + } + public void startRange(Label start) { + LabelRange range = new LabelRange(); + range.start = start; + ranges.add(range); + isEmpty = false; + } + public void closeRange(Label end) { + ranges.getLast().end = end; + } + } + + private static class ExceptionTableEntry { + Label start,end,goal; + String sig; + } + + private class StateStackElement { + final VariableScope scope; + final Label continueLabel; + final Label breakLabel; + final Map stackVariables; + final Map currentBlockNamedLabels; + final LinkedList<BlockRecorder> finallyBlocks; + final boolean inSpecialConstructorCall; + + StateStackElement() { + scope = CompileStack.this.scope; + continueLabel = CompileStack.this.continueLabel; + breakLabel = CompileStack.this.breakLabel; + stackVariables = CompileStack.this.stackVariables; + currentBlockNamedLabels = CompileStack.this.currentBlockNamedLabels; + finallyBlocks = CompileStack.this.finallyBlocks; + inSpecialConstructorCall = CompileStack.this.inSpecialConstructorCall; + } + } + + public CompileStack(WriterController wc) { + this.controller = wc; + } + + public void pushState() { + stateStack.add(new StateStackElement()); + stackVariables = new HashMap(stackVariables); + finallyBlocks = new LinkedList(finallyBlocks); + } + + private void popState() { + if (stateStack.isEmpty()) { + throw new GroovyBugError("Tried to do a pop on the compile stack without push."); + } + StateStackElement element = (StateStackElement) stateStack.removeLast(); + scope = element.scope; + continueLabel = element.continueLabel; + breakLabel = element.breakLabel; + stackVariables = element.stackVariables; + finallyBlocks = element.finallyBlocks; + inSpecialConstructorCall = element.inSpecialConstructorCall; + } + + public Label getContinueLabel() { + return continueLabel; + } + + public Label getBreakLabel() { + return breakLabel; + } + + public void removeVar(int tempIndex) { + final BytecodeVariable head = (BytecodeVariable) temporaryVariables.removeFirst(); + if (head.getIndex() != tempIndex) { + temporaryVariables.addFirst(head); + MethodNode methodNode = controller.getMethodNode(); + if (methodNode==null) { + methodNode = controller.getConstructorNode(); + } + throw new GroovyBugError( + "In method "+ (methodNode!=null?methodNode.getText():"<unknown>") + ", " + + "CompileStack#removeVar: tried to remove a temporary " + + "variable with index "+ tempIndex + " in wrong order. " + + "Current temporary variables=" + temporaryVariables); + } + } + + private void setEndLabels(){ + Label endLabel = new Label(); + controller.getMethodVisitor().visitLabel(endLabel); + for (Iterator iter = stackVariables.values().iterator(); iter.hasNext();) { + BytecodeVariable var = (BytecodeVariable) iter.next(); + var.setEndLabel(endLabel); + } + thisEndLabel = endLabel; + } + + public void pop() { + setEndLabels(); + popState(); + } + + public VariableScope getScope() { + return scope; + } + + /** + * creates a temporary variable. + * + * @param var defines type and name + * @param store defines if the toplevel argument of the stack should be stored + * @return the index used for this temporary variable + */ + public int defineTemporaryVariable(Variable var, boolean store) { + return defineTemporaryVariable(var.getName(), var.getType(),store); + } + + public BytecodeVariable getVariable(String variableName ) { + return getVariable(variableName, true); + } + + /** + * Returns a normal variable. + * <p> + * If <code>mustExist</code> is true and the normal variable doesn't exist, + * then this method will throw a GroovyBugError. It is not the intention of + * this method to let this happen! And the exception should not be used for + * flow control - it is just acting as an assertion. If the exception is thrown + * then it indicates a bug in the class using CompileStack. + * This method can also not be used to return a temporary variable. + * Temporary variables are not normal variables. + * + * @param variableName name of the variable + * @param mustExist throw exception if variable does not exist + * @return the normal variable or null if not found (and <code>mustExist</code> not true) + */ + public BytecodeVariable getVariable(String variableName, boolean mustExist) { + if (variableName.equals("this")) return BytecodeVariable.THIS_VARIABLE; + if (variableName.equals("super")) return BytecodeVariable.SUPER_VARIABLE; + BytecodeVariable v = (BytecodeVariable) stackVariables.get(variableName); + if (v == null && mustExist) + throw new GroovyBugError("tried to get a variable with the name " + variableName + " as stack variable, but a variable with this name was not created"); + return v; + } + + /** + * creates a temporary variable. + * + * @param name defines type and name + * @param store defines if the top-level argument of the stack should be stored + * @return the index used for this temporary variable + */ + public int defineTemporaryVariable(String name,boolean store) { + return defineTemporaryVariable(name, ClassHelper.DYNAMIC_TYPE,store); + } + + /** + * creates a temporary variable. + * + * @param name defines the name + * @param node defines the node + * @param store defines if the top-level argument of the stack should be stored + * @return the index used for this temporary variable + */ + public int defineTemporaryVariable(String name, ClassNode node, boolean store) { + BytecodeVariable answer = defineVar(name, node, false, false); + temporaryVariables.addFirst(answer); // TRICK: we add at the beginning so when we find for remove or get we always have the last one + usedVariables.removeLast(); + + if (store) controller.getOperandStack().storeVar(answer); + + return answer.getIndex(); + } + + private void resetVariableIndex(boolean isStatic) { + temporaryVariables.clear(); + if (!isStatic) { + currentVariableIndex=1; + nextVariableIndex=1; + } else { + currentVariableIndex=0; + nextVariableIndex=0; + } + } + + /** + * Clears the state of the class. This method should be called + * after a MethodNode is visited. Note that a call to init will + * fail if clear is not called before + */ + public void clear() { + if (stateStack.size()>1) { + int size = stateStack.size()-1; + throw new GroovyBugError("the compile stack contains "+size+" more push instruction"+(size==1?"":"s")+" than pops."); + } + if (lhsStack.size()>1) { + int size = lhsStack.size()-1; + throw new GroovyBugError("lhs stack is supposed to be empty, but has " + + size + " elements left."); + } + if (implicitThisStack.size()>1) { + int size = implicitThisStack.size()-1; + throw new GroovyBugError("implicit 'this' stack is supposed to be empty, but has " + + size + " elements left."); + } + clear = true; + MethodVisitor mv = controller.getMethodVisitor(); + // br experiment with local var table so debuggers can retrieve variable names + if (true) {//AsmClassGenerator.CREATE_DEBUG_INFO) { + if (thisEndLabel==null) setEndLabels(); + + if (!scope.isInStaticContext()) { + // write "this" + mv.visitLocalVariable("this", className, null, thisStartLabel, thisEndLabel, 0); + } + + for (Iterator iterator = usedVariables.iterator(); iterator.hasNext();) { + BytecodeVariable v = (BytecodeVariable) iterator.next(); + ClassNode t = v.getType(); + if (v.isHolder()) t = ClassHelper.REFERENCE_TYPE; + String type = BytecodeHelper.getTypeDescription(t); + Label start = v.getStartLabel(); + Label end = v.getEndLabel(); + mv.visitLocalVariable(v.getName(), type, null, start, end, v.getIndex()); + } + } + + //exception table writing + for (ExceptionTableEntry ep : typedExceptions) { + mv.visitTryCatchBlock(ep.start, ep.end, ep.goal, ep.sig); + } + //exception table writing + for (ExceptionTableEntry ep : untypedExceptions) { + mv.visitTryCatchBlock(ep.start, ep.end, ep.goal, ep.sig); + } + + + pop(); + typedExceptions.clear(); + untypedExceptions.clear(); + stackVariables.clear(); + usedVariables.clear(); + scope = null; + finallyBlocks.clear(); + resetVariableIndex(false); + superBlockNamedLabels.clear(); + currentBlockNamedLabels.clear(); + namedLoopBreakLabel.clear(); + namedLoopContinueLabel.clear(); + continueLabel=null; + breakLabel=null; + thisStartLabel=null; + thisEndLabel=null; + mv = null; + } + + public void addExceptionBlock (Label start, Label end, Label goal, + String sig) + { + // this code is in an extra method to avoid + // lazy initialization issues + ExceptionTableEntry ep = new ExceptionTableEntry(); + ep.start = start; + ep.end = end; + ep.sig = sig; + ep.goal = goal; + if (sig==null) { + untypedExceptions.add(ep); + } else { + typedExceptions.add(ep); + } + } + + /** + * initializes this class for a MethodNode. This method will + * automatically define variables for the method parameters + * and will create references if needed. The created variables + * can be accessed by calling getVariable(). + * + */ + public void init(VariableScope el, Parameter[] parameters) { + if (!clear) throw new GroovyBugError("CompileStack#init called without calling clear before"); + clear=false; + pushVariableScope(el); + defineMethodVariables(parameters,el.isInStaticContext()); + this.className = BytecodeHelper.getTypeDescription(controller.getClassNode()); + } + + /** + * Causes the state-stack to add an element and sets + * the given scope as new current variable scope. Creates + * a element for the state stack so pop has to be called later + */ + public void pushVariableScope(VariableScope el) { + pushState(); + scope = el; + superBlockNamedLabels = new HashMap(superBlockNamedLabels); + superBlockNamedLabels.putAll(currentBlockNamedLabels); + currentBlockNamedLabels = new HashMap(); + } + + /** + * Should be called when descending into a loop that defines + * also a scope. Calls pushVariableScope and prepares labels + * for a loop structure. Creates a element for the state stack + * so pop has to be called later, TODO: @Deprecate + */ + public void pushLoop(VariableScope el, String labelName) { + pushVariableScope(el); + continueLabel = new Label(); + breakLabel = new Label(); + if (labelName != null) { + initLoopLabels(labelName); + } + } + + /** + * Should be called when descending into a loop that defines + * also a scope. Calls pushVariableScope and prepares labels + * for a loop structure. Creates a element for the state stack + * so pop has to be called later + */ + public void pushLoop(VariableScope el, List<String> labelNames) { + pushVariableScope(el); + continueLabel = new Label(); + breakLabel = new Label(); + if (labelNames != null) { + for (String labelName : labelNames) { + initLoopLabels(labelName); + } + } + } + + private void initLoopLabels(String labelName) { + namedLoopBreakLabel.put(labelName,breakLabel); + namedLoopContinueLabel.put(labelName,continueLabel); + } + + /** + * Should be called when descending into a loop that does + * not define a scope. Creates a element for the state stack + * so pop has to be called later, TODO: @Deprecate + */ + public void pushLoop(String labelName) { + pushState(); + continueLabel = new Label(); + breakLabel = new Label(); + initLoopLabels(labelName); + } + + /** + * Should be called when descending into a loop that does + * not define a scope. Creates a element for the state stack + * so pop has to be called later + */ + public void pushLoop(List<String> labelNames) { + pushState(); + continueLabel = new Label(); + breakLabel = new Label(); + if (labelNames != null) { + for (String labelName : labelNames) { + initLoopLabels(labelName); + } + } + } + + /** + * Used for <code>break foo</code> inside a loop to end the + * execution of the marked loop. This method will return the + * break label of the loop if there is one found for the name. + * If not, the current break label is returned. + */ + public Label getNamedBreakLabel(String name) { + Label label = getBreakLabel(); + Label endLabel = null; + if (name!=null) endLabel = (Label) namedLoopBreakLabel.get(name); + if (endLabel!=null) label = endLabel; + return label; + } + + /** + * Used for <code>continue foo</code> inside a loop to continue + * the execution of the marked loop. This method will return + * the break label of the loop if there is one found for the + * name. If not, getLabel is used. + */ + public Label getNamedContinueLabel(String name) { + Label label = getLabel(name); + Label endLabel = null; + if (name!=null) endLabel = (Label) namedLoopContinueLabel.get(name); + if (endLabel!=null) label = endLabel; + return label; + } + + /** + * Creates a new break label and a element for the state stack + * so pop has to be called later + */ + public Label pushSwitch(){ + pushState(); + breakLabel = new Label(); + return breakLabel; + } + + /** + * because a boolean Expression may not be evaluated completely + * it is important to keep the registers clean + */ + public void pushBooleanExpression(){ + pushState(); + } + + private BytecodeVariable defineVar(String name, ClassNode type, boolean holder, boolean useReferenceDirectly) { + int prevCurrent = currentVariableIndex; + makeNextVariableID(type,useReferenceDirectly); + int index = currentVariableIndex; + if (holder && !useReferenceDirectly) index = localVariableOffset++; + BytecodeVariable answer = new BytecodeVariable(index, type, name, prevCurrent); + usedVariables.add(answer); + answer.setHolder(holder); + return answer; + } + + private void makeLocalVariablesOffset(Parameter[] paras, boolean isInStaticContext) { + resetVariableIndex(isInStaticContext); + + for (Parameter para : paras) { + makeNextVariableID(para.getType(), false); + } + localVariableOffset = nextVariableIndex; + + resetVariableIndex(isInStaticContext); + } + + private void defineMethodVariables(Parameter[] paras, boolean isInStaticContext) { + Label startLabel = new Label(); + thisStartLabel = startLabel; + controller.getMethodVisitor().visitLabel(startLabel); + + makeLocalVariablesOffset(paras,isInStaticContext); + + for (Parameter para : paras) { + String name = para.getName(); + BytecodeVariable answer; + ClassNode type = para.getType(); + if (para.isClosureSharedVariable()) { + boolean useExistingReference = para.getNodeMetaData(ClosureWriter.UseExistingReference.class) != null; + answer = defineVar(name, para.getOriginType(), true, useExistingReference); + answer.setStartLabel(startLabel); + if (!useExistingReference) { + controller.getOperandStack().load(type, currentVariableIndex); + controller.getOperandStack().box(); + + // GROOVY-4237, the original variable should always appear + // in the variable index, otherwise some programs get into + // trouble. So we define a dummy variable for the packaging + // phase and let it end right away before the normal + // reference will be used + Label newStart = new Label(); + controller.getMethodVisitor().visitLabel(newStart); + BytecodeVariable var = new BytecodeVariable(currentVariableIndex, para.getOriginType(), name, currentVariableIndex); + var.setStartLabel(startLabel); + var.setEndLabel(newStart); + usedVariables.add(var); + answer.setStartLabel(newStart); + + createReference(answer); + } + } else { + answer = defineVar(name, type, false, false); + answer.setStartLabel(startLabel); + } + stackVariables.put(name, answer); + } + + nextVariableIndex = localVariableOffset; + } + + private void createReference(BytecodeVariable reference) { + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitTypeInsn(NEW, "groovy/lang/Reference"); + mv.visitInsn(DUP_X1); + mv.visitInsn(SWAP); + mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Reference", "<init>", "(Ljava/lang/Object;)V", false); + mv.visitVarInsn(ASTORE, reference.getIndex()); + } + + private static void pushInitValue(ClassNode type, MethodVisitor mv) { + if (ClassHelper.isPrimitiveType(type)) { + if (type== ClassHelper.long_TYPE) { + mv.visitInsn(LCONST_0); + } else if (type== ClassHelper.double_TYPE) { + mv.visitInsn(DCONST_0); + } else if (type== ClassHelper.float_TYPE) { + mv.visitInsn(FCONST_0); + } else { + mv.visitLdcInsn(0); + } + } else { + mv.visitInsn(ACONST_NULL); + } + } + + /** + * Defines a new Variable using an AST variable. + * @param initFromStack if true the last element of the + * stack will be used to initialize + * the new variable. If false null + * will be used. + */ + public BytecodeVariable defineVariable(Variable v, boolean initFromStack) { + return defineVariable(v, v.getOriginType(), initFromStack); + } + + public BytecodeVariable defineVariable(Variable v, ClassNode variableType, boolean initFromStack) { + String name = v.getName(); + BytecodeVariable answer = defineVar(name, variableType, v.isClosureSharedVariable(), v.isClosureSharedVariable()); + stackVariables.put(name, answer); + + MethodVisitor mv = controller.getMethodVisitor(); + Label startLabel = new Label(); + answer.setStartLabel(startLabel); + ClassNode type = answer.getType().redirect(); + OperandStack operandStack = controller.getOperandStack(); + + if (!initFromStack) { + if (ClassHelper.isPrimitiveType(v.getOriginType()) && ClassHelper.getWrapper(v.getOriginType()) == variableType) { + pushInitValue(v.getOriginType(), mv); + operandStack.push(v.getOriginType()); + operandStack.box(); + operandStack.remove(1); + } else { + pushInitValue(type, mv); + } + } + operandStack.push(answer.getType()); + if (answer.isHolder()) { + operandStack.box(); + operandStack.remove(1); + createReference(answer); + } else { + operandStack.storeVar(answer); + } + + mv.visitLabel(startLabel); + return answer; + } + + /** + * @param name the name of the variable of interest + * @return true if a variable is already defined + */ + public boolean containsVariable(String name) { + return stackVariables.containsKey(name); + } + + /** + * Calculates the index of the next free register stores it + * and sets the current variable index to the old value + */ + private void makeNextVariableID(ClassNode type, boolean useReferenceDirectly) { + currentVariableIndex = nextVariableIndex; + if ((type== ClassHelper.long_TYPE || type== ClassHelper.double_TYPE) && !useReferenceDirectly) { + nextVariableIndex++; + } + nextVariableIndex++; + } + + /** + * Returns the label for the given name + */ + public Label getLabel(String name) { + if (name==null) return null; + Label l = (Label) superBlockNamedLabels.get(name); + if (l==null) l = createLocalLabel(name); + return l; + } + + /** + * creates a new named label + */ + public Label createLocalLabel(String name) { + Label l = (Label) currentBlockNamedLabels.get(name); + if (l==null) { + l = new Label(); + currentBlockNamedLabels.put(name,l); + } + return l; + } + + public void applyFinallyBlocks(Label label, boolean isBreakLabel) { + // first find the state defining the label. That is the state + // directly after the state not knowing this label. If no state + // in the list knows that label, then the defining state is the + // current state. + StateStackElement result = null; + for (ListIterator iter = stateStack.listIterator(stateStack.size()); iter.hasPrevious();) { + StateStackElement element = (StateStackElement) iter.previous(); + if (!element.currentBlockNamedLabels.values().contains(label)) { + if (isBreakLabel && element.breakLabel != label) { + result = element; + break; + } + if (!isBreakLabel && element.continueLabel != label) { + result = element; + break; + } + } + } + + List<BlockRecorder> blocksToRemove; + if (result==null) { + // all Blocks do know the label, so use all finally blocks + blocksToRemove = (List<BlockRecorder>) Collections.EMPTY_LIST; + } else { + blocksToRemove = result.finallyBlocks; + } + + List<BlockRecorder> blocks = new LinkedList<BlockRecorder>(finallyBlocks); + blocks.removeAll(blocksToRemove); + applyBlockRecorder(blocks); + } + + + private void applyBlockRecorder(List<BlockRecorder> blocks) { + if (blocks.isEmpty() || blocks.size() == visitedBlocks.size()) return; + + MethodVisitor mv = controller.getMethodVisitor(); + Label newStart = new Label(); + + for (BlockRecorder fb : blocks) { + if (visitedBlocks.contains(fb)) continue; + + Label end = new Label(); + mv.visitInsn(NOP); + mv.visitLabel(end); + + fb.closeRange(end); + + // we exclude the finally block from the exception table + // here to avoid double visiting of finally statements + fb.excludedStatement.run(); + + fb.startRange(newStart); + } + + mv.visitInsn(NOP); + mv.visitLabel(newStart); + } + + public void applyBlockRecorder() { + applyBlockRecorder(finallyBlocks); + } + + public boolean hasBlockRecorder() { + return !finallyBlocks.isEmpty(); + } + + public void pushBlockRecorder(BlockRecorder recorder) { + pushState(); + finallyBlocks.addFirst(recorder); + } + + public void pushBlockRecorderVisit(BlockRecorder finallyBlock) { + visitedBlocks.add(finallyBlock); + } + + public void popBlockRecorderVisit(BlockRecorder finallyBlock) { + visitedBlocks.remove(finallyBlock); + } + + public void writeExceptionTable(BlockRecorder block, Label goal, String sig) { + if (block.isEmpty) return; + MethodVisitor mv = controller.getMethodVisitor(); + for (LabelRange range : block.ranges) { + mv.visitTryCatchBlock(range.start, range.end, goal, sig); + } + } + +// public MethodVisitor getMethodVisitor() { +// return mv; +// } + + public boolean isLHS() { + return lhs; + } + + public void pushLHS(boolean lhs) { + lhsStack.add(lhs); + this.lhs = lhs; + } + + public void popLHS() { + lhsStack.removeLast(); + this.lhs = lhsStack.getLast(); + } + + public void pushImplicitThis(boolean implicitThis) { + implicitThisStack.add(implicitThis); + this.implicitThis = implicitThis; + } + + public boolean isImplicitThis() { + return implicitThis; + } + + public void popImplicitThis() { + implicitThisStack.removeLast(); + this.implicitThis = implicitThisStack.getLast(); + } + + public boolean isInSpecialConstructorCall() { + return inSpecialConstructorCall; + } + + public void pushInSpecialConstructorCall() { + pushState(); + inSpecialConstructorCall = true; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java b/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java new file mode 100644 index 0000000..22acbaa --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java @@ -0,0 +1,276 @@ +/* + * 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.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.InterfaceHelperClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.control.SourceUnit; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** + * This class will delegate all calls to a WriterController given in the constructor. + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class DelegatingController extends WriterController { + private final WriterController delegationController; + + public DelegatingController(WriterController normalController) { + this.delegationController = normalController; + } + + @Override + public void init(final AsmClassGenerator asmClassGenerator, final GeneratorContext gcon, final ClassVisitor cv, final ClassNode cn) { + delegationController.init(asmClassGenerator, gcon, cv, cn); + } + + @Override + public void setMethodNode(final MethodNode mn) { + delegationController.setMethodNode(mn); + } + + @Override + public void setConstructorNode(final ConstructorNode cn) { + delegationController.setConstructorNode(cn); + } + + @Override + public boolean isFastPath() { + return delegationController.isFastPath(); + } + + @Override + public CallSiteWriter getCallSiteWriter() { + return delegationController.getCallSiteWriter(); + } + + @Override + public StatementWriter getStatementWriter() { + return delegationController.getStatementWriter(); + } + + @Override + public TypeChooser getTypeChooser() { + return delegationController.getTypeChooser(); + } + + @Override + public AsmClassGenerator getAcg() { + return delegationController.getAcg(); + } + + @Override + public AssertionWriter getAssertionWriter() { + return delegationController.getAssertionWriter(); + } + + @Override + public BinaryExpressionHelper getBinaryExpressionHelper() { + return delegationController.getBinaryExpressionHelper(); + } + + @Override + public UnaryExpressionHelper getUnaryExpressionHelper() { + return delegationController.getUnaryExpressionHelper(); + } + + @Override + public String getClassName() { + return delegationController.getClassName(); + } + + @Override + public ClassNode getClassNode() { + return delegationController.getClassNode(); + } + + @Override + public ClassVisitor getClassVisitor() { + return delegationController.getClassVisitor(); + } + + @Override + public ClosureWriter getClosureWriter() { + return delegationController.getClosureWriter(); + } + + @Override + public CompileStack getCompileStack() { + return delegationController.getCompileStack(); + } + + @Override + public ConstructorNode getConstructorNode() { + return delegationController.getConstructorNode(); + } + + @Override + public GeneratorContext getContext() { + return delegationController.getContext(); + } + + @Override + public ClassVisitor getCv() { + return delegationController.getCv(); + } + + @Override + public InterfaceHelperClassNode getInterfaceClassLoadingClass() { + return delegationController.getInterfaceClassLoadingClass(); + } + + @Override + public String getInternalBaseClassName() { + return delegationController.getInternalBaseClassName(); + } + + @Override + public String getInternalClassName() { + return delegationController.getInternalClassName(); + } + + @Override + public InvocationWriter getInvocationWriter() { + return delegationController.getInvocationWriter(); + } + + @Override + public MethodNode getMethodNode() { + return delegationController.getMethodNode(); + } + + @Override + public MethodVisitor getMethodVisitor() { + return delegationController.getMethodVisitor(); + } + + @Override + public OperandStack getOperandStack() { + return delegationController.getOperandStack(); + } + + @Override + public ClassNode getOutermostClass() { + return delegationController.getOutermostClass(); + } + + @Override + public ClassNode getReturnType() { + return delegationController.getReturnType(); + } + + @Override + public SourceUnit getSourceUnit() { + return delegationController.getSourceUnit(); + } + + @Override + public boolean isConstructor() { + return delegationController.isConstructor(); + } + + @Override + public boolean isInClosure() { + return delegationController.isInClosure(); + } + + @Override + public boolean isInClosureConstructor() { + return delegationController.isInClosureConstructor(); + } + + @Override + public boolean isNotClinit() { + return delegationController.isNotClinit(); + } + + @Override + public boolean isInScriptBody() { + return delegationController.isInScriptBody(); + } + + @Override + public boolean isNotExplicitThisInClosure(boolean implicitThis) { + return delegationController.isNotExplicitThisInClosure(implicitThis); + } + + @Override + public boolean isStaticConstructor() { + return delegationController.isStaticConstructor(); + } + + @Override + public boolean isStaticContext() { + return delegationController.isStaticContext(); + } + + @Override + public boolean isStaticMethod() { + return delegationController.isStaticMethod(); + } + + @Override + public void setInterfaceClassLoadingClass(InterfaceHelperClassNode ihc) { + delegationController.setInterfaceClassLoadingClass(ihc); + } + + @Override + public void setMethodVisitor(MethodVisitor methodVisitor) { + delegationController.setMethodVisitor(methodVisitor); + } + + @Override + public boolean shouldOptimizeForInt() { + return delegationController.shouldOptimizeForInt(); + } + + @Override + public void switchToFastPath() { + delegationController.switchToFastPath(); + } + + @Override + public void switchToSlowPath() { + delegationController.switchToSlowPath(); + } + + @Override + public int getBytecodeVersion() { + return delegationController.getBytecodeVersion(); + } + + @Override + public void setLineNumber(int n) { + delegationController.setLineNumber(n); + } + + @Override + public int getLineNumber() { + return delegationController.getLineNumber(); + } + + @Override + public void resetLineNumber() { + delegationController.resetLineNumber(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/ExpressionAsVariableSlot.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/ExpressionAsVariableSlot.java b/src/main/java/org/codehaus/groovy/classgen/asm/ExpressionAsVariableSlot.java new file mode 100644 index 0000000..a9e568b --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/ExpressionAsVariableSlot.java @@ -0,0 +1,81 @@ +/* + * 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.GroovyBugError; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.classgen.BytecodeExpression; +import org.objectweb.asm.MethodVisitor; + +/** + * Helper class that takes an Expression and if visited will load it normally, + * storing the result in a helper variable, which then can be requested after + * the visit is completed. A copy of the variable will stay on the stack. + * Subsequent visits will load the stored value instead of visiting the + * expression again + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class ExpressionAsVariableSlot extends BytecodeExpression { + private int index = -1; + private final Expression exp; + private final WriterController controller; + private final String name; + + public ExpressionAsVariableSlot(WriterController controller, Expression expression, String name) { + this.exp = expression; + this.controller = controller; + this.name = name; + } + + public ExpressionAsVariableSlot(WriterController controller, Expression expression) { + this(controller, expression, "ExpressionAsVariableSlot_TEMP"); + } + + @Override + public void visit(MethodVisitor mv) { + OperandStack os = controller.getOperandStack(); + if (index == -1) { // first visit + // visit expression + exp.visit(controller.getAcg()); + // make copy & set type + os.dup(); + this.setType(os.getTopOperand()); + // store copy in temporary variable + CompileStack compileStack = controller.getCompileStack(); + index = compileStack.defineTemporaryVariable(name, getType(), true); + } else { + os.load(getType(), index); + } + // since the calling code will push the type again, we better remove it here + os.remove(1); + } + + /** + * returns the index of the bytecode variable + */ + public int getIndex() { + if (index == -1) throw new GroovyBugError("index requested before visit!"); + return index; + } + + @Override + public String getText() { + return exp.getText(); + } +}
