http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java new file mode 100644 index 0000000..98ad092 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java @@ -0,0 +1,888 @@ +/* + * 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.sc; + +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +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.PropertyNode; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.classgen.BytecodeExpression; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.classgen.asm.CallSiteWriter; +import org.codehaus.groovy.classgen.asm.CompileStack; +import org.codehaus.groovy.classgen.asm.OperandStack; +import org.codehaus.groovy.classgen.asm.TypeChooser; +import org.codehaus.groovy.runtime.InvokerHelper; +import org.codehaus.groovy.runtime.MetaClassHelper; +import org.codehaus.groovy.syntax.SyntaxException; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.Boolean_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type; +import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.GROOVY_OBJECT_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.Integer_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.Iterator_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.LIST_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.Long_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.MAP_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.Number_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper; +import static org.codehaus.groovy.ast.ClassHelper.getWrapper; +import static org.codehaus.groovy.ast.ClassHelper.int_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; +import static org.codehaus.groovy.ast.ClassHelper.make; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.chooseBestMethod; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType; + +/** + * A call site writer which replaces call site caching with static calls. This means that the generated code + * looks more like Java code than dynamic Groovy code. Best effort is made to use JVM instructions instead of + * calls to helper methods. + * + * @author Cedric Champeau + */ +public class StaticTypesCallSiteWriter extends CallSiteWriter implements Opcodes { + + private static final ClassNode INVOKERHELPER_TYPE = ClassHelper.make(InvokerHelper.class); + private static final MethodNode GROOVYOBJECT_GETPROPERTY_METHOD = GROOVY_OBJECT_TYPE.getMethod("getProperty", new Parameter[]{new Parameter(STRING_TYPE, "propertyName")}); + private static final MethodNode INVOKERHELPER_GETPROPERTY_METHOD = INVOKERHELPER_TYPE.getMethod("getProperty", new Parameter[]{new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")}); + private static final MethodNode INVOKERHELPER_GETPROPERTYSAFE_METHOD = INVOKERHELPER_TYPE.getMethod("getPropertySafe", new Parameter[]{new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")}); + private static final MethodNode CLOSURE_GETTHISOBJECT_METHOD = CLOSURE_TYPE.getMethod("getThisObject", new Parameter[0]); + private static final ClassNode COLLECTION_TYPE = make(Collection.class); + private static final MethodNode COLLECTION_SIZE_METHOD = COLLECTION_TYPE.getMethod("size", Parameter.EMPTY_ARRAY); + private static final MethodNode MAP_GET_METHOD = MAP_TYPE.getMethod("get", new Parameter[] { new Parameter(OBJECT_TYPE, "key")}); + + + private final StaticTypesWriterController controller; + + public StaticTypesCallSiteWriter(final StaticTypesWriterController controller) { + super(controller); + this.controller = controller; + } + + @Override + public void generateCallSiteArray() { + CallSiteWriter regularCallSiteWriter = controller.getRegularCallSiteWriter(); + if (regularCallSiteWriter.hasCallSiteUse()) { + regularCallSiteWriter.generateCallSiteArray(); + } + } + + @Override + public void makeCallSite(final Expression receiver, final String message, final Expression arguments, final boolean safe, final boolean implicitThis, final boolean callCurrent, final boolean callStatic) { + } + + @Override + public void makeGetPropertySite(Expression receiver, final String methodName, final boolean safe, final boolean implicitThis) { + Object dynamic = receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY); + if (dynamic !=null) { + makeDynamicGetProperty(receiver, methodName, safe); + return; + } + TypeChooser typeChooser = controller.getTypeChooser(); + ClassNode classNode = controller.getClassNode(); + ClassNode receiverType = receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER); + if (receiverType==null) { + receiverType = typeChooser.resolveType(receiver, classNode); + } + Object type = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + if (type==null && receiver instanceof VariableExpression) { + Variable variable = ((VariableExpression) receiver).getAccessedVariable(); + if (variable instanceof Expression) { + type = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + } + } + if (type!=null) { + // in case a "flow type" is found, it is preferred to use it instead of + // the declaration type + receiverType = (ClassNode) type; + } + boolean isClassReceiver = false; + if (isClassClassNodeWrappingConcreteType(receiverType)) { + isClassReceiver = true; + receiverType = receiverType.getGenericsTypes()[0].getType(); + } + + if (isPrimitiveType(receiverType)) { + // GROOVY-6590: wrap primitive types + receiverType = getWrapper(receiverType); + } + + MethodVisitor mv = controller.getMethodVisitor(); + + if (receiverType.isArray() && methodName.equals("length")) { + receiver.visit(controller.getAcg()); + ClassNode arrayGetReturnType = typeChooser.resolveType(receiver, classNode); + controller.getOperandStack().doGroovyCast(arrayGetReturnType); + mv.visitInsn(ARRAYLENGTH); + controller.getOperandStack().replace(int_TYPE); + return; + } else if ( + (receiverType.implementsInterface(COLLECTION_TYPE) + || COLLECTION_TYPE.equals(receiverType)) && ("size".equals(methodName) || "length".equals(methodName))) { + MethodCallExpression expr = new MethodCallExpression( + receiver, + "size", + ArgumentListExpression.EMPTY_ARGUMENTS + ); + expr.setMethodTarget(COLLECTION_SIZE_METHOD); + expr.setImplicitThis(implicitThis); + expr.setSafe(safe); + expr.visit(controller.getAcg()); + return; + } + + boolean isStaticProperty = receiver instanceof ClassExpression + && (receiverType.isDerivedFrom(receiver.getType()) || receiverType.implementsInterface(receiver.getType())); + + if (!isStaticProperty && (receiverType.implementsInterface(MAP_TYPE) || MAP_TYPE.equals(receiverType))) { + // for maps, replace map.foo with map.get('foo') + writeMapDotProperty(receiver, methodName, mv, safe); + return; + } + if (makeGetPropertyWithGetter(receiver, receiverType, methodName, safe, implicitThis)) return; + if (makeGetField(receiver, receiverType, methodName, safe, implicitThis, samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return; + if (receiver instanceof ClassExpression) { + if (makeGetField(receiver, receiver.getType(), methodName, safe, implicitThis, samePackages(receiver.getType().getPackageName(), classNode.getPackageName()))) return; + if (makeGetPropertyWithGetter(receiver, receiver.getType(), methodName, safe, implicitThis)) return; + if (makeGetPrivateFieldWithBridgeMethod(receiver, receiver.getType(), methodName, safe, implicitThis)) return; + } + if (isClassReceiver) { + // we are probably looking for a property of the class + if (makeGetPropertyWithGetter(receiver, CLASS_Type, methodName, safe, implicitThis)) return; + if (makeGetField(receiver, CLASS_Type, methodName, safe, false, true)) return; + } + if (receiverType.isEnum()) { + mv.visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), methodName, BytecodeHelper.getTypeDescription(receiverType)); + controller.getOperandStack().push(receiverType); + return; + } + if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, methodName, safe, implicitThis)) return; + + // GROOVY-5580, it is still possible that we're calling a superinterface property + String getterName = "get" + MetaClassHelper.capitalize(methodName); + String altGetterName = "is" + MetaClassHelper.capitalize(methodName); + if (receiverType.isInterface()) { + Set<ClassNode> allInterfaces = receiverType.getAllInterfaces(); + MethodNode getterMethod = null; + for (ClassNode anInterface : allInterfaces) { + getterMethod = anInterface.getGetterMethod(getterName); + if (getterMethod == null) getterMethod = anInterface.getGetterMethod(altGetterName); + if (getterMethod != null) break; + } + // GROOVY-5585 + if (getterMethod == null) { + getterMethod = OBJECT_TYPE.getGetterMethod(getterName); + } + + if (getterMethod != null) { + MethodCallExpression call = new MethodCallExpression( + receiver, + getterName, + ArgumentListExpression.EMPTY_ARGUMENTS + ); + call.setMethodTarget(getterMethod); + call.setImplicitThis(false); + call.setSourcePosition(receiver); + call.setSafe(safe); + call.visit(controller.getAcg()); + return; + } + + } + + // GROOVY-5568, we would be facing a DGM call, but instead of foo.getText(), have foo.text + List<MethodNode> methods = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), receiverType, getterName, ClassNode.EMPTY_ARRAY); + for (MethodNode m: findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), receiverType, altGetterName, ClassNode.EMPTY_ARRAY)) { + if (Boolean_TYPE.equals(getWrapper(m.getReturnType()))) methods.add(m); + } + if (!methods.isEmpty()) { + List<MethodNode> methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY); + if (methodNodes.size() == 1) { + MethodNode getter = methodNodes.get(0); + MethodCallExpression call = new MethodCallExpression( + receiver, + getter.getName(), + ArgumentListExpression.EMPTY_ARGUMENTS + ); + call.setMethodTarget(getter); + call.setImplicitThis(false); + call.setSafe(safe); + call.setSourcePosition(receiver); + call.visit(controller.getAcg()); + return; + } + } + + if (!isStaticProperty && (receiverType.implementsInterface(LIST_TYPE) || LIST_TYPE.equals(receiverType))) { + writeListDotProperty(receiver, methodName, mv, safe); + return; + } + + controller.getSourceUnit().addError( + new SyntaxException("Access to "+ + (receiver instanceof ClassExpression ?receiver.getType():receiverType).toString(false) + +"#"+methodName+" is forbidden", receiver.getLineNumber(), receiver.getColumnNumber(), receiver.getLastLineNumber(), receiver.getLastColumnNumber()) + ); + controller.getMethodVisitor().visitInsn(ACONST_NULL); + controller.getOperandStack().push(OBJECT_TYPE); + } + + private void makeDynamicGetProperty(final Expression receiver, final String methodName, final boolean safe) { + MethodNode target = safe?INVOKERHELPER_GETPROPERTYSAFE_METHOD:INVOKERHELPER_GETPROPERTY_METHOD; + MethodCallExpression mce = new MethodCallExpression( + new ClassExpression(INVOKERHELPER_TYPE), + target.getName(), + new ArgumentListExpression(receiver, new ConstantExpression(methodName)) + ); + mce.setSafe(false); + mce.setImplicitThis(false); + mce.setMethodTarget(target); + mce.visit(controller.getAcg()); + } + + private void writeMapDotProperty(final Expression receiver, final String methodName, final MethodVisitor mv, final boolean safe) { + receiver.visit(controller.getAcg()); // load receiver + + Label exit = new Label(); + if (safe) { + Label doGet = new Label(); + mv.visitJumpInsn(IFNONNULL, doGet); + controller.getOperandStack().remove(1); + mv.visitInsn(ACONST_NULL); + mv.visitJumpInsn(GOTO, exit); + mv.visitLabel(doGet); + receiver.visit(controller.getAcg()); + } + + mv.visitLdcInsn(methodName); // load property name + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true); + if (safe) { + mv.visitLabel(exit); + } + controller.getOperandStack().replace(OBJECT_TYPE); + } + + private void writeListDotProperty(final Expression receiver, final String methodName, final MethodVisitor mv, final boolean safe) { + ClassNode componentType = receiver.getNodeMetaData(StaticCompilationMetadataKeys.COMPONENT_TYPE); + if (componentType==null) { + componentType = OBJECT_TYPE; + } + // for lists, replace list.foo with: + // def result = new ArrayList(list.size()) + // for (e in list) { result.add (e.foo) } + // result + CompileStack compileStack = controller.getCompileStack(); + + Label exit = new Label(); + if (safe) { + receiver.visit(controller.getAcg()); + Label doGet = new Label(); + mv.visitJumpInsn(IFNONNULL, doGet); + controller.getOperandStack().remove(1); + mv.visitInsn(ACONST_NULL); + mv.visitJumpInsn(GOTO, exit); + mv.visitLabel(doGet); + } + + Variable tmpList = new VariableExpression("tmpList", make(ArrayList.class)); + int var = compileStack.defineTemporaryVariable(tmpList, false); + Variable iterator = new VariableExpression("iterator", Iterator_TYPE); + int it = compileStack.defineTemporaryVariable(iterator, false); + Variable nextVar = new VariableExpression("next", componentType); + final int next = compileStack.defineTemporaryVariable(nextVar, false); + + mv.visitTypeInsn(NEW, "java/util/ArrayList"); + mv.visitInsn(DUP); + receiver.visit(controller.getAcg()); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "size", "()I", true); + controller.getOperandStack().remove(1); + mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "(I)V", false); + mv.visitVarInsn(ASTORE, var); + Label l1 = new Label(); + mv.visitLabel(l1); + receiver.visit(controller.getAcg()); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true); + controller.getOperandStack().remove(1); + mv.visitVarInsn(ASTORE, it); + Label l2 = new Label(); + mv.visitLabel(l2); + mv.visitVarInsn(ALOAD, it); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true); + Label l3 = new Label(); + mv.visitJumpInsn(IFEQ, l3); + mv.visitVarInsn(ALOAD, it); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true); + mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(componentType)); + mv.visitVarInsn(ASTORE, next); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitVarInsn(ALOAD, var); + final ClassNode finalComponentType = componentType; + PropertyExpression pexp = new PropertyExpression(new BytecodeExpression() { + @Override + public void visit(final MethodVisitor mv) { + mv.visitVarInsn(ALOAD, next); + } + + @Override + public ClassNode getType() { + return finalComponentType; + } + }, methodName); + pexp.visit(controller.getAcg()); + controller.getOperandStack().box(); + controller.getOperandStack().remove(1); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true); + mv.visitInsn(POP); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l3); + mv.visitVarInsn(ALOAD, var); + if (safe) { + mv.visitLabel(exit); + } + controller.getOperandStack().push(make(ArrayList.class)); + controller.getCompileStack().removeVar(next); + controller.getCompileStack().removeVar(it); + controller.getCompileStack().removeVar(var); + } + + @SuppressWarnings("unchecked") + private boolean makeGetPrivateFieldWithBridgeMethod(final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean safe, final boolean implicitThis) { + FieldNode field = receiverType.getField(fieldName); + ClassNode outerClass = receiverType.getOuterClass(); + if (field==null && implicitThis && outerClass !=null && !receiverType.isStaticClass()) { + Expression pexp; + if (controller.isInClosure()) { + MethodCallExpression mce = new MethodCallExpression( + new VariableExpression("this"), + "getThisObject", + ArgumentListExpression.EMPTY_ARGUMENTS + ); + mce.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, controller.getOutermostClass()); + mce.setImplicitThis(true); + mce.setMethodTarget(CLOSURE_GETTHISOBJECT_METHOD); + pexp = new CastExpression(controller.getOutermostClass(),mce); + } else { + pexp = new PropertyExpression( + new ClassExpression(outerClass), + "this" + ); + ((PropertyExpression)pexp).setImplicitThis(true); + } + pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, outerClass); + pexp.setSourcePosition(receiver); + return makeGetPrivateFieldWithBridgeMethod(pexp, outerClass, fieldName, safe, true); + } + ClassNode classNode = controller.getClassNode(); + if (field!=null && Modifier.isPrivate(field.getModifiers()) + && (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode) || StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode,receiverType)) + && !receiverType.equals(classNode)) { + Map<String, MethodNode> accessors = receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_ACCESSORS); + if (accessors!=null) { + MethodNode methodNode = accessors.get(fieldName); + if (methodNode!=null) { + MethodCallExpression mce = new MethodCallExpression(receiver, methodNode.getName(), + new ArgumentListExpression(field.isStatic()?new ConstantExpression(null):receiver)); + mce.setMethodTarget(methodNode); + mce.setSafe(safe); + mce.setImplicitThis(implicitThis); + mce.visit(controller.getAcg()); + return true; + } + } + } + return false; + } + + @Override + public void makeGroovyObjectGetPropertySite(final Expression receiver, final String methodName, final boolean safe, final boolean implicitThis) { + TypeChooser typeChooser = controller.getTypeChooser(); + ClassNode classNode = controller.getClassNode(); + ClassNode receiverType = typeChooser.resolveType(receiver, classNode); + if (receiver instanceof VariableExpression && ((VariableExpression) receiver).isThisExpression() && !controller.isInClosure()) { + receiverType = classNode; + } + + String property = methodName; + if (implicitThis) { + if (controller.getInvocationWriter() instanceof StaticInvocationWriter) { + MethodCallExpression currentCall = ((StaticInvocationWriter) controller.getInvocationWriter()).getCurrentCall(); + if (currentCall != null && currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER) != null) { + property = currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + String[] props = property.split("\\."); + BytecodeExpression thisLoader = new BytecodeExpression() { + @Override + public void visit(final MethodVisitor mv) { + mv.visitVarInsn(ALOAD, 0); // load this + } + }; + thisLoader.setType(CLOSURE_TYPE); + Expression pexp = new PropertyExpression(thisLoader, new ConstantExpression(props[0]), safe); + for (int i = 1, propsLength = props.length; i < propsLength; i++) { + final String prop = props[i]; + pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, CLOSURE_TYPE); + pexp = new PropertyExpression(pexp, prop); + } + pexp.visit(controller.getAcg()); + return; + } + } + } + + if (makeGetPropertyWithGetter(receiver, receiverType, property, safe, implicitThis)) return; + if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, property, safe, implicitThis)) return; + if (makeGetField(receiver, receiverType, property, safe, implicitThis, samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return; + + MethodCallExpression call = new MethodCallExpression( + receiver, + "getProperty", + new ArgumentListExpression(new ConstantExpression(property)) + ); + call.setImplicitThis(implicitThis); + call.setSafe(safe); + call.setMethodTarget(GROOVYOBJECT_GETPROPERTY_METHOD); + call.visit(controller.getAcg()); + return; + } + + @Override + public void makeCallSiteArrayInitializer() { + } + + private boolean makeGetPropertyWithGetter(final Expression receiver, final ClassNode receiverType, final String methodName, final boolean safe, final boolean implicitThis) { + // does a getter exists ? + String getterName = "get" + MetaClassHelper.capitalize(methodName); + MethodNode getterNode = receiverType.getGetterMethod(getterName); + if (getterNode==null) { + getterName = "is" + MetaClassHelper.capitalize(methodName); + getterNode = receiverType.getGetterMethod(getterName); + } + if (getterNode!=null && receiver instanceof ClassExpression && !CLASS_Type.equals(receiverType) && !getterNode.isStatic()) { + return false; + } + + // GROOVY-5561: if two files are compiled in the same source unit + // and that one references the other, the getters for properties have not been + // generated by the compiler yet (generated by the Verifier) + PropertyNode propertyNode = receiverType.getProperty(methodName); + if (getterNode == null && propertyNode != null) { + // it is possible to use a getter + String prefix = "get"; + if (boolean_TYPE.equals(propertyNode.getOriginType())) { + prefix = "is"; + } + getterName = prefix + MetaClassHelper.capitalize(methodName); + getterNode = new MethodNode( + getterName, + ACC_PUBLIC, + propertyNode.getOriginType(), + Parameter.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, + EmptyStatement.INSTANCE); + getterNode.setDeclaringClass(receiverType); + if (propertyNode.isStatic()) getterNode.setModifiers(ACC_PUBLIC + ACC_STATIC); + } + if (getterNode!=null) { + MethodCallExpression call = new MethodCallExpression( + receiver, + getterName, + ArgumentListExpression.EMPTY_ARGUMENTS + ); + call.setSourcePosition(receiver); + call.setMethodTarget(getterNode); + call.setImplicitThis(implicitThis); + call.setSafe(safe); + call.visit(controller.getAcg()); + return true; + } + + if (receiverType instanceof InnerClassNode && !receiverType.isStaticClass()) { + if (makeGetPropertyWithGetter(receiver, receiverType.getOuterClass(), methodName, safe, implicitThis)) { + return true; + } + } + + // check direct interfaces (GROOVY-7149) + for (ClassNode node : receiverType.getInterfaces()) { + if (makeGetPropertyWithGetter(receiver, node, methodName, safe, implicitThis)) { + return true; + } + } + // go upper level + ClassNode superClass = receiverType.getSuperClass(); + if (superClass !=null) { + return makeGetPropertyWithGetter(receiver, superClass, methodName, safe, implicitThis); + } + + return false; + } + + boolean makeGetField(final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean safe, final boolean implicitThis, final boolean samePackage) { + FieldNode field = receiverType.getField(fieldName); + // direct access is allowed if we are in the same class as the declaring class + // or we are in an inner class + if (field !=null + && isDirectAccessAllowed(field, controller.getClassNode(), samePackage)) { + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + ClassNode replacementType = field.getOriginType(); + OperandStack operandStack = controller.getOperandStack(); + if (field.isStatic()) { + mv.visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(replacementType)); + operandStack.push(replacementType); + } else { + if (implicitThis) { + compileStack.pushImplicitThis(implicitThis); + } + receiver.visit(controller.getAcg()); + if (implicitThis) compileStack.popImplicitThis(); + Label exit = new Label(); + if (safe) { + mv.visitInsn(DUP); + Label doGet = new Label(); + mv.visitJumpInsn(IFNONNULL, doGet); + mv.visitInsn(POP); + mv.visitInsn(ACONST_NULL); + mv.visitJumpInsn(GOTO, exit); + mv.visitLabel(doGet); + } + if (!operandStack.getTopOperand().isDerivedFrom(field.getOwner())) { + mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(field.getOwner())); + } + mv.visitFieldInsn(GETFIELD, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(replacementType)); + if (safe) { + if (ClassHelper.isPrimitiveType(replacementType)) { + operandStack.replace(replacementType); + operandStack.box(); + replacementType = operandStack.getTopOperand(); + } + mv.visitLabel(exit); + } + } + operandStack.replace(replacementType); + return true; + } + + for (ClassNode intf : receiverType.getInterfaces()) { + // GROOVY-7039 + if (intf!=receiverType && makeGetField(receiver, intf, fieldName, safe, implicitThis, false)) { + return true; + } + } + + ClassNode superClass = receiverType.getSuperClass(); + if (superClass !=null) { + return makeGetField(receiver, superClass, fieldName, safe, implicitThis, false); + } + return false; + } + + private static boolean samePackages(final String pkg1, final String pkg2) { + return ( + (pkg1 ==null && pkg2 ==null) + || pkg1 !=null && pkg1.equals(pkg2) + ); + } + + private static boolean isDirectAccessAllowed(FieldNode a, ClassNode receiver, boolean isSamePackage) { + ClassNode declaringClass = a.getDeclaringClass().redirect(); + ClassNode receiverType = receiver.redirect(); + + // first, direct access from within the class or inner class nodes + if (declaringClass.equals(receiverType)) return true; + if (receiverType instanceof InnerClassNode) { + while (receiverType!=null && receiverType instanceof InnerClassNode) { + if (declaringClass.equals(receiverType)) return true; + receiverType = receiverType.getOuterClass(); + } + } + + // no getter + return a.isPublic() || (a.isProtected() && isSamePackage); + } + + @Override + public void makeSiteEntry() { + } + + @Override + public void prepareCallSite(final String message) { + } + + @Override + public void makeSingleArgumentCall(final Expression receiver, final String message, final Expression arguments, boolean safe) { + TypeChooser typeChooser = controller.getTypeChooser(); + ClassNode classNode = controller.getClassNode(); + ClassNode rType = typeChooser.resolveType(receiver, classNode); + ClassNode aType = typeChooser.resolveType(arguments, classNode); + if (trySubscript(receiver, message, arguments, rType, aType, safe)) { + return; + } + // now try with flow type instead of declaration type + rType = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + if (receiver instanceof VariableExpression && rType == null) { + // TODO: can STCV be made smarter to avoid this check? + VariableExpression ve = (VariableExpression) ((VariableExpression)receiver).getAccessedVariable(); + rType = ve.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + } + if (rType!=null && trySubscript(receiver, message, arguments, rType, aType, safe)) { + return; + } + // todo: more cases + throw new GroovyBugError( + "At line " + receiver.getLineNumber() + " column " + receiver.getColumnNumber() + "\n" + + "On receiver: " + receiver.getText() + " with message: " + message + " and arguments: " + arguments.getText() + "\n" + + "This method should not have been called. Please try to create a simple example reproducing\n" + + "this error and file a bug report at https://issues.apache.org/jira/browse/GROOVY"); + } + + private boolean trySubscript(final Expression receiver, final String message, final Expression arguments, ClassNode rType, final ClassNode aType, boolean safe) { + if (getWrapper(rType).isDerivedFrom(Number_TYPE) + && getWrapper(aType).isDerivedFrom(Number_TYPE)) { + if ("plus".equals(message) || "minus".equals(message) || "multiply".equals(message) || "div".equals(message)) { + writeNumberNumberCall(receiver, message, arguments); + return true; + } else if ("power".equals(message)) { + writePowerCall(receiver, arguments, rType, aType); + return true; + } else if ("mod".equals(message) || "leftShift".equals(message) || "rightShift".equals(message) || "rightShiftUnsigned".equals(message) + || "and".equals(message) || "or".equals(message) || "xor".equals(message)) { + writeOperatorCall(receiver, arguments, message); + return true; + } + } else if (STRING_TYPE.equals(rType) && "plus".equals(message)) { + writeStringPlusCall(receiver, message, arguments); + return true; + } else if ("getAt".equals(message)) { + if (rType.isArray() && getWrapper(aType).isDerivedFrom(Number_TYPE) && !safe) { + writeArrayGet(receiver, arguments, rType, aType); + return true; + } else { + // check if a getAt method can be found on the receiver + ClassNode current = rType; + MethodNode getAtNode = null; + while (current!=null && getAtNode==null) { + getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(aType, "index")}); + if (getAtNode == null) { + getAtNode = getCompatibleMethod(current, "getAt", aType); + } + if (getAtNode==null && isPrimitiveType(aType)) { + getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(getWrapper(aType), "index")}); + if (getAtNode == null) { + getAtNode = getCompatibleMethod(current, "getAt", getWrapper(aType)); + } + } else if (getAtNode==null && aType.isDerivedFrom(Number_TYPE)) { + getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(getUnwrapper(aType), "index")}); + if (getAtNode == null) { + getAtNode = getCompatibleMethod(current, "getAt", getUnwrapper(aType)); + } + } + current = current.getSuperClass(); + } + if (getAtNode!=null) { + MethodCallExpression call = new MethodCallExpression( + receiver, + "getAt", + arguments + ); + + call.setSafe(safe); + call.setSourcePosition(arguments); + call.setImplicitThis(false); + call.setMethodTarget(getAtNode); + call.visit(controller.getAcg()); + return true; + } + + // make sure Map#getAt() and List#getAt handled with the bracket syntax are properly compiled + ClassNode[] args = {aType}; + boolean acceptAnyMethod = + MAP_TYPE.equals(rType) || rType.implementsInterface(MAP_TYPE) + || LIST_TYPE.equals(rType) || rType.implementsInterface(LIST_TYPE); + List<MethodNode> nodes = StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args); + if (nodes.isEmpty()) { + // retry with raw types + rType = rType.getPlainNodeReference(); + nodes = StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args); + } + nodes = StaticTypeCheckingSupport.chooseBestMethod(rType, nodes, args); + if (nodes.size()==1 || nodes.size()>1 && acceptAnyMethod) { + MethodNode methodNode = nodes.get(0); + MethodCallExpression call = new MethodCallExpression( + receiver, + message, + arguments + ); + + call.setSafe(safe); + call.setSourcePosition(arguments); + call.setImplicitThis(false); + call.setMethodTarget(methodNode); + call.visit(controller.getAcg()); + return true; + } + if (implementsInterfaceOrIsSubclassOf(rType, MAP_TYPE)) { + // fallback to Map#get + MethodCallExpression call = new MethodCallExpression( + receiver, + "get", + arguments + ); + + call.setSafe(safe); + call.setMethodTarget(MAP_GET_METHOD); + call.setSourcePosition(arguments); + call.setImplicitThis(false); + call.visit(controller.getAcg()); + return true; + } + } + } + return false; + } + + private MethodNode getCompatibleMethod(ClassNode current, String getAt, ClassNode aType) { + // TODO this really should find "best" match or find all matches and complain about ambiguity if more than one + // TODO handle getAt with more than one parameter + // TODO handle default getAt methods on Java 8 interfaces + for (MethodNode methodNode : current.getDeclaredMethods("getAt")) { + if (methodNode.getParameters().length == 1) { + ClassNode paramType = methodNode.getParameters()[0].getType(); + if (aType.isDerivedFrom(paramType) || aType.declaresInterface(paramType)) { + return methodNode; + } + } + } + return null; + } + + private void writeArrayGet(final Expression receiver, final Expression arguments, final ClassNode rType, final ClassNode aType) { + OperandStack operandStack = controller.getOperandStack(); + int m1 = operandStack.getStackLength(); + // visit receiver + receiver.visit(controller.getAcg()); + // visit arguments as array index + arguments.visit(controller.getAcg()); + operandStack.doGroovyCast(int_TYPE); + int m2 = operandStack.getStackLength(); + // array access + controller.getMethodVisitor().visitInsn(AALOAD); + operandStack.replace(rType.getComponentType(), m2-m1); + } + + private void writeOperatorCall(Expression receiver, Expression arguments, String operator) { + prepareSiteAndReceiver(receiver, operator, false, controller.getCompileStack().isLHS()); + controller.getOperandStack().doGroovyCast(Number_TYPE); + visitBoxedArgument(arguments); + controller.getOperandStack().doGroovyCast(Number_TYPE); + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/typehandling/NumberMath", operator, "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false); + controller.getOperandStack().replace(Number_TYPE, 2); + } + + private void writePowerCall(Expression receiver, Expression arguments, final ClassNode rType, ClassNode aType) { + OperandStack operandStack = controller.getOperandStack(); + int m1 = operandStack.getStackLength(); + //slow Path + prepareSiteAndReceiver(receiver, "power", false, controller.getCompileStack().isLHS()); + operandStack.doGroovyCast(getWrapper(rType)); + visitBoxedArgument(arguments); + operandStack.doGroovyCast(getWrapper(aType)); + int m2 = operandStack.getStackLength(); + MethodVisitor mv = controller.getMethodVisitor(); + if (BigDecimal_TYPE.equals(rType) && Integer_TYPE.equals(getWrapper(aType))) { + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/math/BigDecimal;Ljava/lang/Integer;)Ljava/lang/Number;", false); + } else if (BigInteger_TYPE.equals(rType) && Integer_TYPE.equals(getWrapper(aType))) { + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/math/BigInteger;Ljava/lang/Integer;)Ljava/lang/Number;", false); + } else if (Long_TYPE.equals(getWrapper(rType)) && Integer_TYPE.equals(getWrapper(aType))) { + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Long;Ljava/lang/Integer;)Ljava/lang/Number;", false); + } else if (Integer_TYPE.equals(getWrapper(rType)) && Integer_TYPE.equals(getWrapper(aType))) { + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Number;", false); + } else { + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false); + } + controller.getOperandStack().replace(Number_TYPE, m2 - m1); + } + + private void writeStringPlusCall(final Expression receiver, final String message, final Expression arguments) { + // todo: performance would be better if we created a StringBuilder + OperandStack operandStack = controller.getOperandStack(); + int m1 = operandStack.getStackLength(); + //slow Path + prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS()); + visitBoxedArgument(arguments); + int m2 = operandStack.getStackLength(); + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "plus", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", false); + controller.getOperandStack().replace(STRING_TYPE, m2-m1); + } + + private void writeNumberNumberCall(final Expression receiver, final String message, final Expression arguments) { + OperandStack operandStack = controller.getOperandStack(); + int m1 = operandStack.getStackLength(); + //slow Path + prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS()); + controller.getOperandStack().doGroovyCast(Number_TYPE); + visitBoxedArgument(arguments); + controller.getOperandStack().doGroovyCast(Number_TYPE); + int m2 = operandStack.getStackLength(); + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/dgmimpl/NumberNumber" + MetaClassHelper.capitalize(message), message, "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false); + controller.getOperandStack().replace(Number_TYPE, m2 - m1); + } + + +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java new file mode 100644 index 0000000..fc26486 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java @@ -0,0 +1,135 @@ +/* + * 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.sc; + +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +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.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.classgen.asm.ClosureWriter; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import org.objectweb.asm.Opcodes; + +import java.util.List; + +/** + * Writer responsible for generating closure classes in statically compiled mode. + * + * @author Cedric Champeau + */ +public class StaticTypesClosureWriter extends ClosureWriter { + public StaticTypesClosureWriter(WriterController wc) { + super(wc); + } + + @Override + protected ClassNode createClosureClass(final ClosureExpression expression, final int mods) { + ClassNode closureClass = super.createClosureClass(expression, mods); + List<MethodNode> methods = closureClass.getDeclaredMethods("call"); + List<MethodNode> doCall = closureClass.getMethods("doCall"); + if (doCall.size() != 1) { + throw new GroovyBugError("Expected to find one (1) doCall method on generated closure, but found " + doCall.size()); + } + MethodNode doCallMethod = doCall.get(0); + if (methods.isEmpty() && doCallMethod.getParameters().length == 1) { + createDirectCallMethod(closureClass, doCallMethod); + } + MethodTargetCompletionVisitor visitor = new MethodTargetCompletionVisitor(doCallMethod); + Object dynamic = expression.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION); + if (dynamic != null) { + doCallMethod.putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, dynamic); + } + for (MethodNode method : methods) { + visitor.visitMethod(method); + } + closureClass.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, Boolean.TRUE); + return closureClass; + } + + private static void createDirectCallMethod(final ClassNode closureClass, final MethodNode doCallMethod) { + // in case there is no "call" method on the closure, we can create a "fast invocation" paths + // to avoid going through ClosureMetaClass by call(Object...) method + + // we can't have a specialized version of call(Object...) because the dispatch logic in ClosureMetaClass + // is too complex! + + // call(Object) + Parameter args = new Parameter(ClassHelper.OBJECT_TYPE, "args"); + MethodCallExpression doCall1arg = new MethodCallExpression( + new VariableExpression("this", closureClass), + "doCall", + new ArgumentListExpression(new VariableExpression(args)) + ); + doCall1arg.setImplicitThis(true); + doCall1arg.setMethodTarget(doCallMethod); + closureClass.addMethod( + new MethodNode("call", + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + new Parameter[]{args}, + ClassNode.EMPTY_ARRAY, + new ReturnStatement(doCall1arg))); + + // call() + MethodCallExpression doCallNoArgs = new MethodCallExpression(new VariableExpression("this", closureClass), "doCall", new ArgumentListExpression(new ConstantExpression(null))); + doCallNoArgs.setImplicitThis(true); + doCallNoArgs.setMethodTarget(doCallMethod); + closureClass.addMethod( + new MethodNode("call", + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + Parameter.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, + new ReturnStatement(doCallNoArgs))); + } + + private static final class MethodTargetCompletionVisitor extends ClassCodeVisitorSupport { + + private final MethodNode doCallMethod; + + private MethodTargetCompletionVisitor(final MethodNode doCallMethod) { + this.doCallMethod = doCallMethod; + } + + @Override + protected SourceUnit getSourceUnit() { + return null; + } + + @Override + public void visitMethodCallExpression(final MethodCallExpression call) { + super.visitMethodCallExpression(call); + MethodNode mn = call.getMethodTarget(); + if (mn == null) { + call.setMethodTarget(doCallMethod); + } + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java new file mode 100644 index 0000000..cabc3c4 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java @@ -0,0 +1,299 @@ +/* + * 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.sc; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.asm.BytecodeVariable; +import org.codehaus.groovy.classgen.asm.CompileStack; +import org.codehaus.groovy.classgen.asm.MethodCaller; +import org.codehaus.groovy.classgen.asm.OperandStack; +import org.codehaus.groovy.classgen.asm.StatementWriter; +import org.codehaus.groovy.classgen.asm.TypeChooser; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import java.util.Enumeration; + +import static org.objectweb.asm.Opcodes.AALOAD; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ARRAYLENGTH; +import static org.objectweb.asm.Opcodes.BALOAD; +import static org.objectweb.asm.Opcodes.CALOAD; +import static org.objectweb.asm.Opcodes.DALOAD; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.FALOAD; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.IALOAD; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.IFEQ; +import static org.objectweb.asm.Opcodes.IFNULL; +import static org.objectweb.asm.Opcodes.IF_ICMPGE; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.LALOAD; +import static org.objectweb.asm.Opcodes.SALOAD; + +/** + * A class to write out the optimized statements + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class StaticTypesStatementWriter extends StatementWriter { + + private static final ClassNode ITERABLE_CLASSNODE = ClassHelper.make(Iterable.class); + private static final ClassNode ENUMERATION_CLASSNODE = ClassHelper.make(Enumeration.class); + private static final MethodCaller ENUMERATION_NEXT_METHOD = MethodCaller.newInterface(Enumeration.class, "nextElement"); + private static final MethodCaller ENUMERATION_HASMORE_METHOD = MethodCaller.newInterface(Enumeration.class, "hasMoreElements"); + + private final StaticTypesWriterController controller; + + public StaticTypesStatementWriter(StaticTypesWriterController controller) { + super(controller); + this.controller = controller; + } + + @Override + public void writeBlockStatement(BlockStatement statement) { + controller.switchToFastPath(); + super.writeBlockStatement(statement); + controller.switchToSlowPath(); + } + + @Override + protected void writeForInLoop(final ForStatement loop) { + controller.getAcg().onLineNumber(loop,"visitForLoop"); + writeStatementLabel(loop); + + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + + compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabels()); + + // Identify type of collection + TypeChooser typeChooser = controller.getTypeChooser(); + Expression collectionExpression = loop.getCollectionExpression(); + ClassNode collectionType = typeChooser.resolveType(collectionExpression, controller.getClassNode()); + Parameter loopVariable = loop.getVariable(); + int size = operandStack.getStackLength(); + if (collectionType.isArray() && loopVariable.getOriginType().equals(collectionType.getComponentType())) { + writeOptimizedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); + } else if (ENUMERATION_CLASSNODE.equals(collectionType)) { + writeEnumerationBasedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); + } else { + writeIteratorBasedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); + } + operandStack.popDownTo(size); + compileStack.pop(); + } + + private void writeOptimizedForEachLoop( + CompileStack compileStack, + OperandStack operandStack, + MethodVisitor mv, + ForStatement loop, + Expression collectionExpression, + ClassNode collectionType, + Parameter loopVariable) { + BytecodeVariable variable = compileStack.defineVariable(loopVariable, false); + + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + AsmClassGenerator acg = controller.getAcg(); + + // load array on stack + collectionExpression.visit(acg); + mv.visitInsn(DUP); + int array = compileStack.defineTemporaryVariable("$arr", collectionType, true); + mv.visitJumpInsn(IFNULL, breakLabel); + + // $len = array.length + mv.visitVarInsn(ALOAD, array); + mv.visitInsn(ARRAYLENGTH); + operandStack.push(ClassHelper.int_TYPE); + int arrayLen = compileStack.defineTemporaryVariable("$len", ClassHelper.int_TYPE, true); + + // $idx = 0 + mv.visitInsn(ICONST_0); + operandStack.push(ClassHelper.int_TYPE); + int loopIdx = compileStack.defineTemporaryVariable("$idx", ClassHelper.int_TYPE, true); + + mv.visitLabel(continueLabel); + // $idx<$len? + mv.visitVarInsn(ILOAD, loopIdx); + mv.visitVarInsn(ILOAD, arrayLen); + mv.visitJumpInsn(IF_ICMPGE, breakLabel); + + // get array element + loadFromArray(mv, variable, array, loopIdx); + + // $idx++ + mv.visitIincInsn(loopIdx, 1); + + // loop body + loop.getLoopBlock().visit(acg); + + mv.visitJumpInsn(GOTO, continueLabel); + + mv.visitLabel(breakLabel); + + compileStack.removeVar(loopIdx); + compileStack.removeVar(arrayLen); + compileStack.removeVar(array); + } + + private void loadFromArray(MethodVisitor mv, BytecodeVariable variable, int array, int iteratorIdx) { + OperandStack os = controller.getOperandStack(); + mv.visitVarInsn(ALOAD, array); + mv.visitVarInsn(ILOAD, iteratorIdx); + + ClassNode varType = variable.getType(); + boolean primitiveType = ClassHelper.isPrimitiveType(varType); + boolean isByte = ClassHelper.byte_TYPE.equals(varType); + boolean isShort = ClassHelper.short_TYPE.equals(varType); + boolean isInt = ClassHelper.int_TYPE.equals(varType); + boolean isLong = ClassHelper.long_TYPE.equals(varType); + boolean isFloat = ClassHelper.float_TYPE.equals(varType); + boolean isDouble = ClassHelper.double_TYPE.equals(varType); + boolean isChar = ClassHelper.char_TYPE.equals(varType); + boolean isBoolean = ClassHelper.boolean_TYPE.equals(varType); + + if (primitiveType) { + if (isByte) { + mv.visitInsn(BALOAD); + } + if (isShort) { + mv.visitInsn(SALOAD); + } + if (isInt || isChar || isBoolean) { + mv.visitInsn(isChar ? CALOAD : isBoolean ? BALOAD : IALOAD); + } + if (isLong) { + mv.visitInsn(LALOAD); + } + if (isFloat) { + mv.visitInsn(FALOAD); + } + if (isDouble) { + mv.visitInsn(DALOAD); + } + } else { + mv.visitInsn(AALOAD); + } + os.push(varType); + os.storeVar(variable); + } + + private void writeIteratorBasedForEachLoop( + CompileStack compileStack, + OperandStack operandStack, + MethodVisitor mv, + ForStatement loop, + Expression collectionExpression, + ClassNode collectionType, + Parameter loopVariable) { + // Declare the loop counter. + BytecodeVariable variable = compileStack.defineVariable(loopVariable, false); + + if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(collectionType, ITERABLE_CLASSNODE)) { + MethodCallExpression iterator = new MethodCallExpression(collectionExpression, "iterator", new ArgumentListExpression()); + iterator.setMethodTarget(collectionType.getMethod("iterator", Parameter.EMPTY_ARRAY)); + iterator.setImplicitThis(false); + iterator.visit(controller.getAcg()); + } else { + collectionExpression.visit(controller.getAcg()); + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "iterator", "(Ljava/lang/Object;)Ljava/util/Iterator;", false); + operandStack.replace(ClassHelper.Iterator_TYPE); + } + + // Then get the iterator and generate the loop control + + int iteratorIdx = compileStack.defineTemporaryVariable("iterator", ClassHelper.Iterator_TYPE, true); + + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + mv.visitLabel(continueLabel); + mv.visitVarInsn(ALOAD, iteratorIdx); + writeIteratorHasNext(mv); + // note: ifeq tests for ==0, a boolean is 0 if it is false + mv.visitJumpInsn(IFEQ, breakLabel); + + mv.visitVarInsn(ALOAD, iteratorIdx); + writeIteratorNext(mv); + operandStack.push(ClassHelper.OBJECT_TYPE); + operandStack.storeVar(variable); + + // Generate the loop body + loop.getLoopBlock().visit(controller.getAcg()); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + compileStack.removeVar(iteratorIdx); + } + + private void writeEnumerationBasedForEachLoop( + CompileStack compileStack, + OperandStack operandStack, + MethodVisitor mv, + ForStatement loop, + Expression collectionExpression, + ClassNode collectionType, + Parameter loopVariable) { + // Declare the loop counter. + BytecodeVariable variable = compileStack.defineVariable(loopVariable, false); + + collectionExpression.visit(controller.getAcg()); + + // Then get the iterator and generate the loop control + + int enumIdx = compileStack.defineTemporaryVariable("$enum", ENUMERATION_CLASSNODE, true); + + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + mv.visitLabel(continueLabel); + mv.visitVarInsn(ALOAD, enumIdx); + ENUMERATION_HASMORE_METHOD.call(mv); + // note: ifeq tests for ==0, a boolean is 0 if it is false + mv.visitJumpInsn(IFEQ, breakLabel); + + mv.visitVarInsn(ALOAD, enumIdx); + ENUMERATION_NEXT_METHOD.call(mv); + operandStack.push(ClassHelper.OBJECT_TYPE); + operandStack.storeVar(variable); + + // Generate the loop body + loop.getLoopBlock().visit(controller.getAcg()); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java new file mode 100644 index 0000000..a3e4551 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java @@ -0,0 +1,75 @@ +/* + * 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.sc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.classgen.asm.StatementMetaTypeChooser; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; + +/** + * A {@link org.codehaus.groovy.classgen.asm.TypeChooser} which reads type information from node metadata + * generated by the {@link groovy.transform.CompileStatic} annotation. + * + * @author Cedric Champeau + */ +public class StaticTypesTypeChooser extends StatementMetaTypeChooser { + @Override + public ClassNode resolveType(final Expression exp, final ClassNode current) { + ASTNode target = exp instanceof VariableExpression ? getTarget((VariableExpression) exp) : exp; + ClassNode inferredType = target.getNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE); + if (inferredType == null) { + inferredType = target.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + if (inferredType == null && target instanceof VariableExpression && ((VariableExpression) target).getAccessedVariable() instanceof Parameter) { + target = (Parameter) ((VariableExpression) target).getAccessedVariable(); + inferredType = ((Parameter) target).getOriginType(); + } + } + if (inferredType != null) { + if (ClassHelper.VOID_TYPE == inferredType) { + // we are in a case of a type inference failure, probably because code was generated + // it is better to avoid using this + inferredType = super.resolveType(exp, current); + } + return inferredType; + } + if (target instanceof VariableExpression && ((VariableExpression) target).isThisExpression()) { + // AsmClassGenerator may create "this" expressions that the type checker knows nothing about + return current; + } + return super.resolveType(exp, current); + } + + /** + * The inferred type, in case of a variable expression, can be set on the accessed variable, so we take it instead + * of the facade one. + * + * @param ve the variable expression for which to return the target expression + * @return the target variable expression + */ + private static VariableExpression getTarget(VariableExpression ve) { + if (ve.getAccessedVariable() == null || ve.getAccessedVariable() == ve || (!(ve.getAccessedVariable() instanceof VariableExpression))) + return ve; + return getTarget((VariableExpression) ve.getAccessedVariable()); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesUnaryExpressionHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesUnaryExpressionHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesUnaryExpressionHelper.java new file mode 100644 index 0000000..73adb10 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesUnaryExpressionHelper.java @@ -0,0 +1,178 @@ +/* + * 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.sc; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; +import org.codehaus.groovy.ast.expr.EmptyExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.NotExpression; +import org.codehaus.groovy.ast.expr.UnaryMinusExpression; +import org.codehaus.groovy.ast.expr.UnaryPlusExpression; +import org.codehaus.groovy.classgen.BytecodeExpression; +import org.codehaus.groovy.classgen.asm.TypeChooser; +import org.codehaus.groovy.classgen.asm.UnaryExpressionHelper; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.char_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.double_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.float_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.int_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; +import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; + +/** + * An unary expression helper which generates optimized bytecode depending on + * the current type on top of the operand stack. + * + * @author Cedric Champeau + */ +public class StaticTypesUnaryExpressionHelper extends UnaryExpressionHelper implements Opcodes { + private static final UnaryMinusExpression EMPTY_UNARY_MINUS = new UnaryMinusExpression(EmptyExpression.INSTANCE); + private static final UnaryPlusExpression EMPTY_UNARY_PLUS = new UnaryPlusExpression(EmptyExpression.INSTANCE); + private static final BitwiseNegationExpression EMPTY_BITWISE_NEGATE = new BitwiseNegationExpression(EmptyExpression.INSTANCE); + + private final WriterController controller; + + public StaticTypesUnaryExpressionHelper(final WriterController controller) { + super(controller); + this.controller = controller; + } + + @Override + public void writeBitwiseNegate(final BitwiseNegationExpression expression) { + expression.getExpression().visit(controller.getAcg()); + if (isPrimitiveOnTop()) { + final ClassNode top = getTopOperand(); + if (top==int_TYPE || top==short_TYPE || top==byte_TYPE || top==char_TYPE || top==long_TYPE) { + BytecodeExpression bytecodeExpression = new BytecodeExpression() { + @Override + public void visit(final MethodVisitor mv) { + if (long_TYPE==top) { + mv.visitLdcInsn(-1); + mv.visitInsn(LXOR); + } else { + mv.visitInsn(ICONST_M1); + mv.visitInsn(IXOR); + if (byte_TYPE==top) { + mv.visitInsn(I2B); + } else if (char_TYPE==top) { + mv.visitInsn(I2C); + } else if (short_TYPE==top) { + mv.visitInsn(I2S); + } + } + } + }; + bytecodeExpression.visit(controller.getAcg()); + controller.getOperandStack().remove(1); + return; + } + } + super.writeBitwiseNegate(EMPTY_BITWISE_NEGATE); + } + + @Override + public void writeNotExpression(final NotExpression expression) { + TypeChooser typeChooser = controller.getTypeChooser(); + Expression subExpression = expression.getExpression(); + ClassNode classNode = controller.getClassNode(); + if (typeChooser.resolveType(subExpression, classNode) == boolean_TYPE) { + subExpression.visit(controller.getAcg()); + controller.getOperandStack().doGroovyCast(boolean_TYPE); + BytecodeExpression bytecodeExpression = new BytecodeExpression() { + @Override + public void visit(final MethodVisitor mv) { + Label ne = new Label(); + mv.visitJumpInsn(IFNE, ne); + mv.visitInsn(ICONST_1); + Label out = new Label(); + mv.visitJumpInsn(GOTO, out); + mv.visitLabel(ne); + mv.visitInsn(ICONST_0); + mv.visitLabel(out); + } + }; + bytecodeExpression.visit(controller.getAcg()); + controller.getOperandStack().remove(1); + return; + } + super.writeNotExpression(expression); + } + + @Override + public void writeUnaryMinus(final UnaryMinusExpression expression) { + expression.getExpression().visit(controller.getAcg()); + if (isPrimitiveOnTop()) { + final ClassNode top = getTopOperand(); + if (top!=boolean_TYPE) { + BytecodeExpression bytecodeExpression = new BytecodeExpression() { + @Override + public void visit(final MethodVisitor mv) { + if (int_TYPE == top || short_TYPE == top || byte_TYPE==top || char_TYPE==top) { + mv.visitInsn(INEG); + if (byte_TYPE==top) { + mv.visitInsn(I2B); + } else if (char_TYPE==top) { + mv.visitInsn(I2C); + } else if (short_TYPE==top) { + mv.visitInsn(I2S); + } + } else if (long_TYPE == top) { + mv.visitInsn(LNEG); + } else if (float_TYPE == top) { + mv.visitInsn(FNEG); + } else if (double_TYPE == top) { + mv.visitInsn(DNEG); + } + } + }; + bytecodeExpression.visit(controller.getAcg()); + controller.getOperandStack().remove(1); + return; + } + } + // we already visited the sub expression + super.writeUnaryMinus(EMPTY_UNARY_MINUS); + } + + @Override + public void writeUnaryPlus(final UnaryPlusExpression expression) { + expression.getExpression().visit(controller.getAcg()); + if (isPrimitiveOnTop()) { + // only visit the expression + return; + } + super.writeUnaryPlus(EMPTY_UNARY_PLUS); + } + + private boolean isPrimitiveOnTop() { + return isPrimitiveType(getTopOperand()); + } + + private ClassNode getTopOperand() { + return controller.getOperandStack().getTopOperand(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java new file mode 100644 index 0000000..47ef3d4 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java @@ -0,0 +1,186 @@ +/* + * 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.sc; + +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.classgen.asm.BinaryExpressionHelper; +import org.codehaus.groovy.classgen.asm.BinaryExpressionMultiTypeDispatcher; +import org.codehaus.groovy.classgen.asm.CallSiteWriter; +import org.codehaus.groovy.classgen.asm.ClosureWriter; +import org.codehaus.groovy.classgen.asm.DelegatingController; +import org.codehaus.groovy.classgen.asm.InvocationWriter; +import org.codehaus.groovy.classgen.asm.StatementWriter; +import org.codehaus.groovy.classgen.asm.TypeChooser; +import org.codehaus.groovy.classgen.asm.UnaryExpressionHelper; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.classgen.asm.indy.sc.IndyStaticTypesMultiTypeDispatcher; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.sc.StaticCompilationVisitor; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import org.objectweb.asm.ClassVisitor; + + +/** + * An alternative {@link org.codehaus.groovy.classgen.asm.WriterController} which handles static types and method + * dispatch. In case of a "mixed mode" where only some methods are annotated with {@link groovy.transform.TypeChecked} + * then this writer will delegate to the classic writer controller. + * + * @author Cedric Champeau + */ +public class StaticTypesWriterController extends DelegatingController { + + protected boolean isInStaticallyCheckedMethod; + private StaticTypesCallSiteWriter callSiteWriter; + private StaticTypesStatementWriter statementWriter; + private StaticTypesTypeChooser typeChooser; + private StaticInvocationWriter invocationWriter; + private BinaryExpressionMultiTypeDispatcher binaryExprHelper; + private UnaryExpressionHelper unaryExpressionHelper; + private ClosureWriter closureWriter; + + public StaticTypesWriterController(WriterController normalController) { + super(normalController); + isInStaticallyCheckedMethod = false; + } + + @Override + public void init(final AsmClassGenerator asmClassGenerator, final GeneratorContext gcon, final ClassVisitor cv, final ClassNode cn) { + super.init(asmClassGenerator, gcon, cv, cn); + this.callSiteWriter = new StaticTypesCallSiteWriter(this); + this.statementWriter = new StaticTypesStatementWriter(this); + this.typeChooser = new StaticTypesTypeChooser(); + this.invocationWriter = new StaticInvocationWriter(this); + this.closureWriter = new StaticTypesClosureWriter(this); + this.unaryExpressionHelper = new StaticTypesUnaryExpressionHelper(this); + + CompilerConfiguration config = cn.getCompileUnit().getConfig(); + this.binaryExprHelper = config.isIndyEnabled() + ? new IndyStaticTypesMultiTypeDispatcher(this) + : new StaticTypesBinaryExpressionMultiTypeDispatcher(this); + } + + @Override + public void setMethodNode(final MethodNode mn) { + updateStaticCompileFlag(mn); + super.setMethodNode(mn); + } + + private void updateStaticCompileFlag(final MethodNode mn) { + ClassNode classNode = getClassNode(); + AnnotatedNode node = mn; + if (classNode.implementsInterface(ClassHelper.GENERATED_CLOSURE_Type)) { + node = classNode.getOuterClass(); + } + + isInStaticallyCheckedMethod = mn != null && ( + StaticCompilationVisitor.isStaticallyCompiled(node) + || classNode.implementsInterface(ClassHelper.GENERATED_CLOSURE_Type)&&classNode.getNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE)!=null); + } + + @Override + public void setConstructorNode(final ConstructorNode cn) { + updateStaticCompileFlag(cn); + super.setConstructorNode(cn); + } + + @Override + public boolean isFastPath() { + if (isInStaticallyCheckedMethod) return true; + return super.isFastPath(); + } + + @Override + public CallSiteWriter getCallSiteWriter() { + MethodNode methodNode = getMethodNode(); + if (methodNode !=null && methodNode.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION)==Boolean.TRUE) { + return super.getCallSiteWriter(); + } + if (isInStaticallyCheckedMethod) { + return callSiteWriter; + } + return super.getCallSiteWriter(); + } + + public CallSiteWriter getRegularCallSiteWriter() { + return super.getCallSiteWriter(); + } + + @Override + public StatementWriter getStatementWriter() { + if (isInStaticallyCheckedMethod) { + return statementWriter; + } else { + return super.getStatementWriter(); + } + } + + @Override + public TypeChooser getTypeChooser() { + if (isInStaticallyCheckedMethod) { + return typeChooser; + } else { + return super.getTypeChooser(); + } + } + + @Override + public InvocationWriter getInvocationWriter() { + if (isInStaticallyCheckedMethod) { + return invocationWriter; + } else { + return super.getInvocationWriter(); + } + } + + public InvocationWriter getRegularInvocationWriter() { + return super.getInvocationWriter(); + } + + @Override + public BinaryExpressionHelper getBinaryExpressionHelper() { + if (isInStaticallyCheckedMethod) { + return binaryExprHelper; + } else { + return super.getBinaryExpressionHelper(); + } + } + + @Override + public UnaryExpressionHelper getUnaryExpressionHelper() { + if (isInStaticallyCheckedMethod) { + return unaryExpressionHelper; + } + return super.getUnaryExpressionHelper(); + } + + @Override + public ClosureWriter getClosureWriter() { + if (isInStaticallyCheckedMethod) { + return closureWriter; + } + return super.getClosureWriter(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterControllerFactoryImpl.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterControllerFactoryImpl.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterControllerFactoryImpl.java new file mode 100644 index 0000000..c68f635 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterControllerFactoryImpl.java @@ -0,0 +1,33 @@ +/* + * 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.sc; + +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.classgen.asm.WriterControllerFactory; + +/** + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class StaticTypesWriterControllerFactoryImpl implements WriterControllerFactory { + + public WriterController makeController(WriterController normalController) { + return new StaticTypesWriterController(normalController); + } + +}
