http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java b/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java new file mode 100644 index 0000000..213a82b --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java @@ -0,0 +1,700 @@ +/* + * 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.ConstructorNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.tools.WideningCategories; +import org.codehaus.groovy.classgen.ClassGeneratorException; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.objectweb.asm.Opcodes.ACONST_NULL; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ASTORE; +import static org.objectweb.asm.Opcodes.BIPUSH; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.D2F; +import static org.objectweb.asm.Opcodes.D2I; +import static org.objectweb.asm.Opcodes.D2L; +import static org.objectweb.asm.Opcodes.DCONST_0; +import static org.objectweb.asm.Opcodes.DCONST_1; +import static org.objectweb.asm.Opcodes.DSTORE; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.DUP2; +import static org.objectweb.asm.Opcodes.DUP2_X1; +import static org.objectweb.asm.Opcodes.DUP2_X2; +import static org.objectweb.asm.Opcodes.DUP_X2; +import static org.objectweb.asm.Opcodes.F2D; +import static org.objectweb.asm.Opcodes.F2I; +import static org.objectweb.asm.Opcodes.F2L; +import static org.objectweb.asm.Opcodes.FCONST_0; +import static org.objectweb.asm.Opcodes.FCONST_1; +import static org.objectweb.asm.Opcodes.FCONST_2; +import static org.objectweb.asm.Opcodes.FSTORE; +import static org.objectweb.asm.Opcodes.GETSTATIC; +import static org.objectweb.asm.Opcodes.I2B; +import static org.objectweb.asm.Opcodes.I2C; +import static org.objectweb.asm.Opcodes.I2D; +import static org.objectweb.asm.Opcodes.I2F; +import static org.objectweb.asm.Opcodes.I2L; +import static org.objectweb.asm.Opcodes.I2S; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.ICONST_1; +import static org.objectweb.asm.Opcodes.ICONST_2; +import static org.objectweb.asm.Opcodes.ICONST_3; +import static org.objectweb.asm.Opcodes.ICONST_4; +import static org.objectweb.asm.Opcodes.ICONST_5; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.ISTORE; +import static org.objectweb.asm.Opcodes.L2D; +import static org.objectweb.asm.Opcodes.L2F; +import static org.objectweb.asm.Opcodes.L2I; +import static org.objectweb.asm.Opcodes.LCONST_0; +import static org.objectweb.asm.Opcodes.LCONST_1; +import static org.objectweb.asm.Opcodes.LSTORE; +import static org.objectweb.asm.Opcodes.NEW; +import static org.objectweb.asm.Opcodes.POP; +import static org.objectweb.asm.Opcodes.POP2; +import static org.objectweb.asm.Opcodes.SIPUSH; +import static org.objectweb.asm.Opcodes.SWAP; + +public class OperandStack { + + private final WriterController controller; + private final List<ClassNode> stack = new ArrayList<ClassNode>(); + + public OperandStack(WriterController wc) { + this.controller = wc; + } + + public int getStackLength() { + return stack.size(); + } + + public void popDownTo(int elements) { + int last = stack.size(); + MethodVisitor mv = controller.getMethodVisitor(); + while (last>elements) { + last--; + ClassNode element = popWithMessage(last); + if (isTwoSlotType(element)) { + mv.visitInsn(POP2); + } else { + mv.visitInsn(POP); + } + } + } + + private ClassNode popWithMessage(int last) { + try { + return stack.remove(last); + } catch (ArrayIndexOutOfBoundsException ai) { + String method = controller.getMethodNode() == null ? + controller.getConstructorNode().getTypeDescriptor() : + controller.getMethodNode().getTypeDescriptor(); + throw new GroovyBugError("Error while popping argument from operand stack tracker in class " + + controller.getClassName() + " method " + method + "."); + } + } + + /** + * returns true for long and double + */ + private static boolean isTwoSlotType(ClassNode type) { + return type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE; + } + + /** + * ensure last marked parameter on the stack is a primitive boolean + * if mark==stack size, we assume an empty expression or statement. + * was used and we will use the value given in emptyDefault as boolean + * if mark==stack.size()-1 the top element will be cast to boolean using + * Groovy truth. + * In other cases we throw a GroovyBugError + */ + public void castToBool(int mark, boolean emptyDefault) { + int size = stack.size(); + MethodVisitor mv = controller.getMethodVisitor(); + if (mark==size) { + // no element, so use emptyDefault + if (emptyDefault) { + mv.visitIntInsn(BIPUSH, 1); + } else { + mv.visitIntInsn(BIPUSH, 0); + } + stack.add(null); + } else if (mark==stack.size()-1) { + ClassNode last = stack.get(size-1); + // nothing to do in that case + if (last == ClassHelper.boolean_TYPE) return; + // not a primitive type, so call booleanUnbox + if (!ClassHelper.isPrimitiveType(last)) { + controller.getInvocationWriter().castNonPrimitiveToBool(last); + } else { + BytecodeHelper.convertPrimitiveToBoolean(mv, last); + } + } else { + throw new GroovyBugError( + "operand stack contains "+stack.size()+ + " elements, but we expected only "+mark + ); + } + stack.set(mark,ClassHelper.boolean_TYPE); + } + + /** + * remove operand stack top element using bytecode pop + */ + public void pop() { + popDownTo(stack.size()-1); + } + + public Label jump(int ifIns) { + Label label = new Label(); + jump(ifIns,label); + return label; + } + + public void jump(int ifIns, Label label) { + controller.getMethodVisitor().visitJumpInsn(ifIns, label); + // remove the boolean from the operand stack tracker + remove(1); + } + + /** + * duplicate top element + */ + public void dup() { + ClassNode type = getTopOperand(); + stack.add(type); + MethodVisitor mv = controller.getMethodVisitor(); + if (type == ClassHelper.double_TYPE || type == ClassHelper.long_TYPE) { + mv.visitInsn(DUP2); + } else { + mv.visitInsn(DUP); + } + } + + public ClassNode box() { + MethodVisitor mv = controller.getMethodVisitor(); + int size = stack.size(); + ClassNode type = stack.get(size-1); + if (ClassHelper.isPrimitiveType(type) && ClassHelper.VOID_TYPE!=type) { + ClassNode wrapper = ClassHelper.getWrapper(type); + BytecodeHelper.doCastToWrappedType(mv, type, wrapper); + type = wrapper; + } // else nothing to box + stack.set(size-1, type); + return type; + } + + /** + * Remove amount elements from the operand stack, without using pop. + * For example after a method invocation + */ + public void remove(int amount) { + int size = stack.size(); + for (int i=size-1; i>size-1-amount; i--) { + popWithMessage(i); + } + } + + /** + * push operand on stack + */ + public void push(ClassNode type) { + stack.add(type); + } + + /** + * swap two top level operands + */ + public void swap() { + MethodVisitor mv = controller.getMethodVisitor(); + int size = stack.size(); + ClassNode b = stack.get(size-1); + ClassNode a = stack.get(size-2); + // dup_x1: --- + // dup_x2: aab -> baab + // dup2_x1: abb -> bbabb + // dup2_x2: aabb -> bbaabb + // b = top element, a = element under b + // top element at right + if (isTwoSlotType(a)) { // aa + if (isTwoSlotType(b)) { // aabb + // aabb -> bbaa + mv.visitInsn(DUP2_X2); // bbaabb + mv.visitInsn(POP2); // bbaa + } else { + // aab -> baa + mv.visitInsn(DUP_X2); // baab + mv.visitInsn(POP); // baa + } + } else { // a + if (isTwoSlotType(b)) { //abb + // abb -> bba + mv.visitInsn(DUP2_X1); // bbabb + mv.visitInsn(POP2); // bba + } else { + // ab -> ba + mv.visitInsn(SWAP); + } + } + stack.set(size-1,a); + stack.set(size-2,b); + } + + /** + * replace top level element with new element of given type + */ + public void replace(ClassNode type) { + int size = stack.size(); + try { + if (size==0) throw new ArrayIndexOutOfBoundsException("size==0"); + } catch (ArrayIndexOutOfBoundsException ai) { + System.err.println("index problem in "+controller.getSourceUnit().getName()); + throw ai; + } + stack.set(size-1, type); + } + + /** + * replace n top level elements with new element of given type + */ + public void replace(ClassNode type, int n) { + remove(n); + push(type); + } + + /** + * do Groovy cast for top level element + */ + public void doGroovyCast(ClassNode targetType) { + doConvertAndCast(targetType,false); + } + + public void doGroovyCast(Variable v) { + ClassNode targetType = v.getOriginType(); + doConvertAndCast(targetType,false); + } + + public void doAsType(ClassNode targetType) { + doConvertAndCast(targetType,true); + } + + private void throwExceptionForNoStackElement(int size, ClassNode targetType, boolean coerce) { + if (size>0) return; + StringBuilder sb = new StringBuilder(); + sb.append("Internal compiler error while compiling ").append(controller.getSourceUnit().getName()).append("\n"); + MethodNode methodNode = controller.getMethodNode(); + if (methodNode!=null) { + sb.append("Method: "); + sb.append(methodNode); + sb.append("\n"); + } + ConstructorNode constructorNode = controller.getConstructorNode(); + if (constructorNode!=null) { + sb.append("Constructor: "); + sb.append(methodNode); + sb.append("\n"); + } + sb.append("Line ").append(controller.getLineNumber()).append(","); + sb.append(" expecting ").append(coerce ? "coercion" : "casting").append(" to ").append(targetType.toString(false)); + sb.append(" but operand stack is empty"); + throw new ArrayIndexOutOfBoundsException(sb.toString()); + } + + private void doConvertAndCast(ClassNode targetType, boolean coerce) { + int size = stack.size(); + throwExceptionForNoStackElement(size, targetType, coerce); + + ClassNode top = stack.get(size-1); + targetType = targetType.redirect(); + if (targetType == top) return; + + if (coerce) { + controller.getInvocationWriter().coerce(top,targetType); + return; + } + + boolean primTarget = ClassHelper.isPrimitiveType(targetType); + boolean primTop = ClassHelper.isPrimitiveType(top); + + if (primTop && primTarget) { + // here we box and unbox to get the goal type + if (convertPrimitive(top, targetType)) { + replace(targetType); + return; + } + box(); + } else if (primTarget) { + // top is not primitive so unbox + // leave that BH#doCast later + } else { + // top might be primitive, target is not + // so let invocation writer box if needed and do groovy cast otherwise + controller.getInvocationWriter().castToNonPrimitiveIfNecessary(top, targetType); + } + + MethodVisitor mv = controller.getMethodVisitor(); + if (primTarget && !ClassHelper.boolean_TYPE.equals(targetType) && !primTop && ClassHelper.getWrapper(targetType).equals(top)) { + BytecodeHelper.doCastToPrimitive(mv, top, targetType); + } else { + top = stack.get(size-1); + if (!WideningCategories.implementsInterfaceOrSubclassOf(top, targetType)) { + BytecodeHelper.doCast(mv,targetType); + } + } + replace(targetType); + } + + private boolean convertFromInt(ClassNode target) { + int convertCode; + if (target==ClassHelper.char_TYPE){ + convertCode = I2C; + } else if (target==ClassHelper.byte_TYPE){ + convertCode = I2B; + } else if (target==ClassHelper.short_TYPE){ + convertCode = I2S; + } else if (target==ClassHelper.long_TYPE){ + convertCode = I2L; + } else if (target==ClassHelper.float_TYPE){ + convertCode = I2F; + } else if (target==ClassHelper.double_TYPE){ + convertCode = I2D; + } else { + return false; + } + controller.getMethodVisitor().visitInsn(convertCode); + return true; + } + + private boolean convertFromLong(ClassNode target) { + MethodVisitor mv = controller.getMethodVisitor(); + if (target==ClassHelper.int_TYPE){ + mv.visitInsn(L2I); + return true; + } else if ( target==ClassHelper.char_TYPE || + target==ClassHelper.byte_TYPE || + target==ClassHelper.short_TYPE) + { + mv.visitInsn(L2I); + return convertFromInt(target); + } else if (target==ClassHelper.double_TYPE){ + mv.visitInsn(L2D); + return true; + } else if (target==ClassHelper.float_TYPE){ + mv.visitInsn(L2F); + return true; + } + return false; + } + + private boolean convertFromDouble(ClassNode target) { + MethodVisitor mv = controller.getMethodVisitor(); + if (target==ClassHelper.int_TYPE){ + mv.visitInsn(D2I); + return true; + } else if ( target==ClassHelper.char_TYPE || + target==ClassHelper.byte_TYPE || + target==ClassHelper.short_TYPE) + { + mv.visitInsn(D2I); + return convertFromInt(target); + } else if (target==ClassHelper.long_TYPE){ + mv.visitInsn(D2L); + return true; + } else if (target==ClassHelper.float_TYPE){ + mv.visitInsn(D2F); + return true; + } + return false; + } + + private boolean convertFromFloat(ClassNode target) { + MethodVisitor mv = controller.getMethodVisitor(); + if (target==ClassHelper.int_TYPE){ + mv.visitInsn(F2I); + return true; + } else if ( target==ClassHelper.char_TYPE || + target==ClassHelper.byte_TYPE || + target==ClassHelper.short_TYPE) + { + mv.visitInsn(F2I); + return convertFromInt(target); + } else if (target==ClassHelper.long_TYPE){ + mv.visitInsn(F2L); + return true; + } else if (target==ClassHelper.double_TYPE){ + mv.visitInsn(F2D); + return true; + } + return false; + } + + private boolean convertPrimitive(ClassNode top, ClassNode target) { + if (top==target) return true; + if (top==ClassHelper.int_TYPE) { + return convertFromInt(target); + } else if ( top==ClassHelper.char_TYPE || + top==ClassHelper.byte_TYPE || + top==ClassHelper.short_TYPE) + { + return target == ClassHelper.int_TYPE || convertFromInt(target); + } else if ( top==ClassHelper.float_TYPE) { + return convertFromFloat(target); + } else if ( top==ClassHelper.double_TYPE) { + return convertFromDouble(target); + } else if ( top==ClassHelper.long_TYPE) { + return convertFromLong(target); + } + return false; + } + + /** + * load the constant on the operand stack. + */ + public void pushConstant(ConstantExpression expression) { + MethodVisitor mv = controller.getMethodVisitor(); + Object value = expression.getValue(); + ClassNode origType = expression.getType().redirect(); + ClassNode type = ClassHelper.getUnwrapper(origType); + boolean boxing = origType!=type; + boolean asPrimitive = boxing || ClassHelper.isPrimitiveType(type); + + if (value == null) { + mv.visitInsn(ACONST_NULL); + } else if (boxing && value instanceof Boolean) { + // special path for boxed boolean + Boolean bool = (Boolean) value; + String text = bool ? "TRUE" : "FALSE"; + mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;"); + boxing = false; + type = origType; + } else if (asPrimitive) { + pushPrimitiveConstant(mv, value, type); + } else if (value instanceof BigDecimal) { + String className = BytecodeHelper.getClassInternalName(value.getClass().getName()); + mv.visitTypeInsn(NEW, className); + mv.visitInsn(DUP); + mv.visitLdcInsn(value.toString()); + mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V", false); + } else if (value instanceof BigInteger) { + String className = BytecodeHelper.getClassInternalName(value.getClass().getName()); + mv.visitTypeInsn(NEW, className); + mv.visitInsn(DUP); + mv.visitLdcInsn(value.toString()); + mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V", false); + } else if (value instanceof String) { + mv.visitLdcInsn(value); + } else { + throw new ClassGeneratorException( + "Cannot generate bytecode for constant: " + value + " of type: " + type.getName()); + } + + push(type); + if (boxing) box(); + } + + private static void pushPrimitiveConstant(final MethodVisitor mv, final Object value, final ClassNode type) { + boolean isInt = ClassHelper.int_TYPE.equals(type); + boolean isShort = ClassHelper.short_TYPE.equals(type); + boolean isByte = ClassHelper.byte_TYPE.equals(type); + boolean isChar = ClassHelper.char_TYPE.equals(type); + if (isInt || isShort || isByte || isChar) { + int val = isInt?(Integer)value:isShort?(Short)value:isChar?(Character)value:(Byte)value; + switch (val) { + case 0: + mv.visitInsn(ICONST_0); + break; + case 1: + mv.visitInsn(ICONST_1); + break; + case 2: + mv.visitInsn(ICONST_2); + break; + case 3: + mv.visitInsn(ICONST_3); + break; + case 4: + mv.visitInsn(ICONST_4); + break; + case 5: + mv.visitInsn(ICONST_5); + break; + default: + if (val>=Byte.MIN_VALUE && val<=Byte.MAX_VALUE) { + mv.visitIntInsn(BIPUSH, val); + } else if (val>=Short.MIN_VALUE && val<=Short.MAX_VALUE) { + mv.visitIntInsn(SIPUSH, val); + } else { + mv.visitLdcInsn(value); + } + } + } else if (ClassHelper.long_TYPE.equals(type)) { + if ((Long)value==0L) { + mv.visitInsn(LCONST_0); + } else if ((Long)value==1L) { + mv.visitInsn(LCONST_1); + } else { + mv.visitLdcInsn(value); + } + } else if (ClassHelper.float_TYPE.equals(type)) { + if ((Float)value==0f) { + mv.visitInsn(FCONST_0); + } else if ((Float)value==1f) { + mv.visitInsn(FCONST_1); + } else if ((Float)value==2f) { + mv.visitInsn(FCONST_2); + } else { + mv.visitLdcInsn(value); + } + } else if (ClassHelper.double_TYPE.equals(type)) { + if ((Double)value==0d) { + mv.visitInsn(DCONST_0); + } else if ((Double)value==1d) { + mv.visitInsn(DCONST_1); + } else { + mv.visitLdcInsn(value); + } + } else if (ClassHelper.boolean_TYPE.equals(type)) { + boolean b = (Boolean) value; + if (b) { + mv.visitInsn(ICONST_1); + } else { + mv.visitInsn(ICONST_0); + } + } else { + mv.visitLdcInsn(value); + } + } + + public void pushDynamicName(Expression name) { + if (name instanceof ConstantExpression) { + ConstantExpression ce = (ConstantExpression) name; + Object value = ce.getValue(); + if (value instanceof String) { + pushConstant(ce); + return; + } + } + new CastExpression(ClassHelper.STRING_TYPE, name).visit(controller.getAcg()); + } + + public void loadOrStoreVariable(BytecodeVariable variable, boolean useReferenceDirectly) { + CompileStack compileStack = controller.getCompileStack(); + + if (compileStack.isLHS()) { + storeVar(variable); + } else { + MethodVisitor mv = controller.getMethodVisitor(); + int idx = variable.getIndex(); + ClassNode type = variable.getType(); + + if (variable.isHolder()) { + mv.visitVarInsn(ALOAD, idx); + if (!useReferenceDirectly) { + mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;", false); + BytecodeHelper.doCast(mv, type); + push(type); + } else { + push(ClassHelper.REFERENCE_TYPE); + } + } else { + load(type,idx); + } + } + } + + public void storeVar(BytecodeVariable variable) { + MethodVisitor mv = controller.getMethodVisitor(); + int idx = variable.getIndex(); + ClassNode type = variable.getType(); + // value is on stack + if (variable.isHolder()) { + doGroovyCast(type); + box(); + mv.visitVarInsn(ALOAD, idx); + mv.visitTypeInsn(CHECKCAST, "groovy/lang/Reference"); + mv.visitInsn(SWAP); + mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V", false); + } else { + doGroovyCast(type); + if (type == ClassHelper.double_TYPE) { + mv.visitVarInsn(DSTORE, idx); + } else if (type == ClassHelper.float_TYPE) { + mv.visitVarInsn(FSTORE, idx); + } else if (type == ClassHelper.long_TYPE) { + mv.visitVarInsn(LSTORE, idx); + } else if ( + type == ClassHelper.boolean_TYPE + || type == ClassHelper.char_TYPE + || type == ClassHelper.byte_TYPE + || type == ClassHelper.int_TYPE + || type == ClassHelper.short_TYPE) { + mv.visitVarInsn(ISTORE, idx); + } else { + mv.visitVarInsn(ASTORE, idx); + } + } + // remove RHS value from operand stack + remove(1); + } + + public void load(ClassNode type, int idx) { + MethodVisitor mv = controller.getMethodVisitor(); + BytecodeHelper.load(mv, type, idx); + push(type); + } + + public void pushBool(boolean inclusive) { + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitLdcInsn(inclusive); + push(ClassHelper.boolean_TYPE); + } + + public String toString() { + return "OperandStack(size="+stack.size()+":"+stack.toString()+")"; + } + + public ClassNode getTopOperand() { + int size = stack.size(); + try { + if (size==0) throw new ArrayIndexOutOfBoundsException("size==0"); + } catch (ArrayIndexOutOfBoundsException ai) { + System.err.println("index problem in "+controller.getSourceUnit().getName()); + throw ai; + } + return stack.get(size-1); + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/OptimizingStatementWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/OptimizingStatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/OptimizingStatementWriter.java new file mode 100644 index 0000000..e553477 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/OptimizingStatementWriter.java @@ -0,0 +1,948 @@ +/* + * 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.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.VariableScope; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; +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.DeclarationExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PostfixExpression; +import org.codehaus.groovy.ast.expr.PrefixExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.UnaryMinusExpression; +import org.codehaus.groovy.ast.expr.UnaryPlusExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.DoWhileStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.ast.tools.ParameterUtils; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.Verifier; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.runtime.BytecodeInterface8; +import org.codehaus.groovy.syntax.Types; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import java.util.LinkedList; +import java.util.List; + +import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.GROOVY_INTERCEPTABLE_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.double_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.tools.WideningCategories.isBigDecCategory; +import static org.codehaus.groovy.ast.tools.WideningCategories.isDoubleCategory; +import static org.codehaus.groovy.ast.tools.WideningCategories.isFloatingCategory; +import static org.codehaus.groovy.ast.tools.WideningCategories.isIntCategory; +import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory; +import static org.codehaus.groovy.classgen.asm.BinaryExpressionMultiTypeDispatcher.typeMap; +import static org.codehaus.groovy.classgen.asm.BinaryExpressionMultiTypeDispatcher.typeMapKeyNames; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.GETSTATIC; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.IFEQ; +import static org.objectweb.asm.Opcodes.IFNE; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; + +/** + * A class to write out the optimized statements + */ +public class OptimizingStatementWriter extends StatementWriter { + + private static class FastPathData { + private Label pathStart = new Label(); + private Label afterPath = new Label(); + } + + public static class ClassNodeSkip{} + + public static class StatementMeta { + private boolean optimize=false; + protected MethodNode target; + protected ClassNode type; + protected VariableExpression declaredVariableExpression; + protected boolean[] involvedTypes = new boolean[typeMapKeyNames.length]; + public void chainInvolvedTypes(OptimizeFlagsCollector opt) { + for (int i=0; i<typeMapKeyNames.length; i++) { + if (opt.current.involvedTypes[i]) { + this.involvedTypes[i] = true; + } + } + } + public String toString() { + StringBuilder ret = new StringBuilder("optimize=" + optimize + " target=" + target + " type=" + type + " involvedTypes="); + for (int i=0; i<typeMapKeyNames.length; i++) { + if (involvedTypes[i]) ret.append(" ").append(typeMapKeyNames[i]); + } + return ret.toString(); + } + } + + private static final MethodCaller[] guards = { + null, + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigInt"), + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigL"), + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigD"), + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigC"), + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigB"), + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigS"), + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigF"), + MethodCaller.newStatic(BytecodeInterface8.class, "isOrigZ"), + }; + + private static final MethodCaller disabledStandardMetaClass = MethodCaller.newStatic(BytecodeInterface8.class, "disabledStandardMetaClass"); + private boolean fastPathBlocked = false; + private final WriterController controller; + + public OptimizingStatementWriter(WriterController controller) { + super(controller); + this.controller = controller; + } + + private boolean notEnableFastPath(StatementMeta meta) { + // return false if cannot do fast path and if are already on the path + return fastPathBlocked || meta==null || !meta.optimize || controller.isFastPath(); + } + + private FastPathData writeGuards(StatementMeta meta, Statement statement) { + if (notEnableFastPath(meta)) return null; + controller.getAcg().onLineNumber(statement, null); + MethodVisitor mv = controller.getMethodVisitor(); + FastPathData fastPathData = new FastPathData(); + Label slowPath = new Label(); + + for (int i=0; i<guards.length; i++) { + if (meta.involvedTypes[i]) { + guards[i].call(mv); + mv.visitJumpInsn(IFEQ, slowPath); + } + } + + // meta class check with boolean holder + String owner = BytecodeHelper.getClassInternalName(controller.getClassNode()); + MethodNode mn = controller.getMethodNode(); + if (mn!=null) { + mv.visitFieldInsn(GETSTATIC, owner, Verifier.STATIC_METACLASS_BOOL, "Z"); + mv.visitJumpInsn(IFNE, slowPath); + } + + //standard metaclass check + disabledStandardMetaClass.call(mv); + mv.visitJumpInsn(IFNE, slowPath); + + // other guards here + + mv.visitJumpInsn(GOTO, fastPathData.pathStart); + mv.visitLabel(slowPath); + + return fastPathData; + } + + private void writeFastPathPrelude(FastPathData meta) { + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitJumpInsn(GOTO, meta.afterPath); + mv.visitLabel(meta.pathStart); + controller.switchToFastPath(); + } + + private void writeFastPathEpilogue(FastPathData meta) { + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitLabel(meta.afterPath); + controller.switchToSlowPath(); + } + + @Override + public void writeBlockStatement(BlockStatement statement) { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + FastPathData fastPathData = writeGuards(meta, statement); + + if (fastPathData==null) { + // normal mode with different paths + // important is to not to have a fastpathblock here, + // otherwise the per expression statement improvement + // is impossible + super.writeBlockStatement(statement); + } else { + // fast/slow path generation + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeBlockStatement(statement); + fastPathBlocked = oldFastPathBlock; + + writeFastPathPrelude(fastPathData); + super.writeBlockStatement(statement); + writeFastPathEpilogue(fastPathData); + } + } + + @Override + public void writeDoWhileLoop(DoWhileStatement statement) { + if (controller.isFastPath()) { + super.writeDoWhileLoop(statement); + } else { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + FastPathData fastPathData = writeGuards(meta, statement); + + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeDoWhileLoop(statement); + fastPathBlocked = oldFastPathBlock; + + if (fastPathData==null) return; + writeFastPathPrelude(fastPathData); + super.writeDoWhileLoop(statement); + writeFastPathEpilogue(fastPathData); + } + } + + @Override + protected void writeIteratorHasNext(MethodVisitor mv) { + if (controller.isFastPath()) { + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true); + } else { + super.writeIteratorHasNext(mv); + } + } + + @Override + protected void writeIteratorNext(MethodVisitor mv) { + if (controller.isFastPath()) { + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true); + } else { + super.writeIteratorNext(mv); + } + } + + @Override + protected void writeForInLoop(ForStatement statement) { + if (controller.isFastPath()) { + super.writeForInLoop(statement); + } else { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + FastPathData fastPathData = writeGuards(meta, statement); + + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeForInLoop(statement); + fastPathBlocked = oldFastPathBlock; + + if (fastPathData==null) return; + writeFastPathPrelude(fastPathData); + super.writeForInLoop(statement); + writeFastPathEpilogue(fastPathData); + } + } + + @Override + protected void writeForLoopWithClosureList(ForStatement statement) { + if (controller.isFastPath()) { + super.writeForLoopWithClosureList(statement); + } else { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + FastPathData fastPathData = writeGuards(meta, statement); + + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeForLoopWithClosureList(statement); + fastPathBlocked = oldFastPathBlock; + + if (fastPathData==null) return; + writeFastPathPrelude(fastPathData); + super.writeForLoopWithClosureList(statement); + writeFastPathEpilogue(fastPathData); + } + } + + @Override + public void writeWhileLoop(WhileStatement statement) { + if (controller.isFastPath()) { + super.writeWhileLoop(statement); + } else { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + FastPathData fastPathData = writeGuards(meta, statement); + + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeWhileLoop(statement); + fastPathBlocked = oldFastPathBlock; + + if (fastPathData==null) return; + writeFastPathPrelude(fastPathData); + super.writeWhileLoop(statement); + writeFastPathEpilogue(fastPathData); + } + } + + @Override + public void writeIfElse(IfStatement statement) { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + FastPathData fastPathData = writeGuards(meta, statement); + + if (fastPathData==null) { + super.writeIfElse(statement); + } else { + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeIfElse(statement); + fastPathBlocked = oldFastPathBlock; + + if (fastPathData == null) return; + writeFastPathPrelude(fastPathData); + super.writeIfElse(statement); + writeFastPathEpilogue(fastPathData); + } + } + + private boolean isNewPathFork(StatementMeta meta) { + // meta.optimize -> can do fast path + if (meta==null || meta.optimize==false) return false; + // fastPathBlocked -> slow path + if (fastPathBlocked) return false; + // controller.isFastPath() -> fastPath + return !controller.isFastPath(); + } + + @Override + public void writeReturn(ReturnStatement statement) { + if (controller.isFastPath()) { + super.writeReturn(statement); + } else { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + if (isNewPathFork(meta) && writeDeclarationExtraction(statement)) { + if (meta.declaredVariableExpression != null) { + // declaration was replaced by assignment so we need to define the variable + controller.getCompileStack().defineVariable(meta.declaredVariableExpression, false); + } + FastPathData fastPathData = writeGuards(meta, statement); + + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeReturn(statement); + fastPathBlocked = oldFastPathBlock; + + if (fastPathData==null) return; + writeFastPathPrelude(fastPathData); + super.writeReturn(statement); + writeFastPathEpilogue(fastPathData); + } else { + super.writeReturn(statement); + } + } + } + + @Override + public void writeExpressionStatement(ExpressionStatement statement) { + if (controller.isFastPath()) { + super.writeExpressionStatement(statement); + } else { + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + // we have to have handle DelcarationExpressions special, since their + // entry should be outside the optimization path, we have to do that of + // course only if we are actually going to do two different paths, + // otherwise it is not needed + // + // there are several cases to be considered now. + // (1) no fast path possible, so just do super + // (2) fast path possible, and at path split point (meaning not in + // fast path and not in slow path). Here we have to extract the + // Declaration and replace by an assignment + // (3) fast path possible and in slow or fastPath. Nothing to do here. + // + // the only case we need to handle is then (2). + + if (isNewPathFork(meta) && writeDeclarationExtraction(statement)) { + if (meta.declaredVariableExpression != null) { + // declaration was replaced by assignment so we need to define the variable + controller.getCompileStack().defineVariable(meta.declaredVariableExpression, false); + } + FastPathData fastPathData = writeGuards(meta, statement); + + boolean oldFastPathBlock = fastPathBlocked; + fastPathBlocked = true; + super.writeExpressionStatement(statement); + fastPathBlocked = oldFastPathBlock; + + if (fastPathData==null) return; + writeFastPathPrelude(fastPathData); + super.writeExpressionStatement(statement); + writeFastPathEpilogue(fastPathData); + } else { + super.writeExpressionStatement(statement); + } + } + } + + private boolean writeDeclarationExtraction(Statement statement) { + Expression ex = null; + if (statement instanceof ReturnStatement) { + ReturnStatement rs = (ReturnStatement) statement; + ex = rs.getExpression(); + } else if (statement instanceof ExpressionStatement) { + ExpressionStatement es = (ExpressionStatement) statement; + ex = es.getExpression(); + } else { + throw new GroovyBugError("unknown statement type :"+statement.getClass()); + } + if (!(ex instanceof DeclarationExpression)) return true; + DeclarationExpression declaration = (DeclarationExpression) ex; + ex = declaration.getLeftExpression(); + if (ex instanceof TupleExpression) return false; + + // stash declared variable in case we do subsequent visits after we + // change to assignment only + StatementMeta meta = statement.getNodeMetaData(StatementMeta.class); + if (meta != null) { + meta.declaredVariableExpression = declaration.getVariableExpression(); + } + + // change statement to do assignment only + BinaryExpression assignment = new BinaryExpression( + declaration.getLeftExpression(), + declaration.getOperation(), + declaration.getRightExpression()); + assignment.setSourcePosition(declaration); + assignment.copyNodeMetaData(declaration); + // replace statement code + if (statement instanceof ReturnStatement) { + ReturnStatement rs = (ReturnStatement) statement; + rs.setExpression(assignment); + } else if (statement instanceof ExpressionStatement) { + ExpressionStatement es = (ExpressionStatement) statement; + es.setExpression(assignment); + } else { + throw new GroovyBugError("unknown statement type :"+statement.getClass()); + } + return true; + } + + public static void setNodeMeta(TypeChooser chooser, ClassNode classNode) { + if (classNode.getNodeMetaData(ClassNodeSkip.class)!=null) return; + new OptVisitor(chooser).visitClass(classNode); + } + + private static StatementMeta addMeta(ASTNode node) { + StatementMeta metaOld = node.getNodeMetaData(StatementMeta.class); + StatementMeta meta = metaOld; + if (meta==null) meta = new StatementMeta(); + meta.optimize = true; + if (metaOld==null) node.setNodeMetaData(StatementMeta.class, meta); + return meta; + } + + private static StatementMeta addMeta(ASTNode node, OptimizeFlagsCollector opt) { + StatementMeta meta = addMeta(node); + meta.chainInvolvedTypes(opt); + return meta; + } + + private static class OptimizeFlagsCollector { + private static class OptimizeFlagsEntry { + private boolean canOptimize = false; + private boolean shouldOptimize = false; + private boolean[] involvedTypes = new boolean[typeMapKeyNames.length]; + } + private OptimizeFlagsEntry current = new OptimizeFlagsEntry(); + private final LinkedList<OptimizeFlagsEntry> olderEntries = new LinkedList<OptimizeFlagsEntry>(); + public void push() { + olderEntries.addLast(current); + current = new OptimizeFlagsEntry(); + } + public void pop(boolean propagateFlags){ + OptimizeFlagsEntry old = current; + current = olderEntries.removeLast(); + if (propagateFlags) { + chainCanOptimize(old.canOptimize); + chainShouldOptimize(old.shouldOptimize); + for (int i=0; i<typeMapKeyNames.length; i++) current.involvedTypes[i] |= old.involvedTypes[i]; + } + } + public String toString() { + StringBuilder ret; + if (current.shouldOptimize) { + ret = new StringBuilder("should optimize, can = " + current.canOptimize); + } else if (current.canOptimize) { + ret = new StringBuilder("can optimize"); + } else { + ret = new StringBuilder("don't optimize"); + } + ret.append(" involvedTypes ="); + for (int i=0; i<typeMapKeyNames.length; i++) { + if (current.involvedTypes[i]) ret.append(" ").append(typeMapKeyNames[i]); + } + return ret.toString(); + } + /** + * @return true iff we should Optimize - this is almost seen as must + */ + private boolean shouldOptimize() { + return current.shouldOptimize; + } + /** + * @return true iff we can optimize, but not have to + */ + private boolean canOptimize() { + return current.canOptimize || current.shouldOptimize; + } + /** + * set "should" to true, if not already + */ + public void chainShouldOptimize(boolean opt) { + current.shouldOptimize = shouldOptimize() || opt; + } + /** + * set "can" to true, if not already + */ + public void chainCanOptimize(boolean opt) { + current.canOptimize = current.canOptimize || opt; + } + public void chainInvolvedType(ClassNode type) { + Integer res = typeMap.get(type); + if (res==null) return; + current.involvedTypes[res] = true; + } + public void reset() { + current.canOptimize = false; + current.shouldOptimize = false; + current.involvedTypes = new boolean[typeMapKeyNames.length]; + } + } + + private static class OptVisitor extends ClassCodeVisitorSupport { + private final TypeChooser typeChooser; + + public OptVisitor(final TypeChooser chooser) { + this.typeChooser = chooser; + } + + @Override protected SourceUnit getSourceUnit() {return null;} + + private ClassNode node; + private OptimizeFlagsCollector opt = new OptimizeFlagsCollector(); + private boolean optimizeMethodCall = true; + private VariableScope scope; + private static final VariableScope nonStaticScope = new VariableScope(); + + @Override + public void visitClass(ClassNode node) { + this.optimizeMethodCall = !node.implementsInterface(GROOVY_INTERCEPTABLE_TYPE); + this.node = node; + this.scope = nonStaticScope; + super.visitClass(node); + this.scope=null; + this.node=null; + } + + @Override + public void visitMethod(MethodNode node) { + scope = node.getVariableScope(); + super.visitMethod(node); + opt.reset(); + } + + @Override + public void visitConstructor(ConstructorNode node) { + scope = node.getVariableScope(); + super.visitConstructor(node); + } + + @Override + public void visitReturnStatement(ReturnStatement statement) { + opt.push(); + super.visitReturnStatement(statement); + if (opt.shouldOptimize()) addMeta(statement,opt); + opt.pop(opt.shouldOptimize()); + } + + @Override + public void visitUnaryMinusExpression(UnaryMinusExpression expression) { + //TODO: implement int operations for this + super.visitUnaryMinusExpression(expression); + StatementMeta meta = addMeta(expression); + meta.type = OBJECT_TYPE; + } + + @Override + public void visitUnaryPlusExpression(UnaryPlusExpression expression) { + //TODO: implement int operations for this + super.visitUnaryPlusExpression(expression); + StatementMeta meta = addMeta(expression); + meta.type = OBJECT_TYPE; + } + + @Override + public void visitBitwiseNegationExpression(BitwiseNegationExpression expression) { + //TODO: implement int operations for this + super.visitBitwiseNegationExpression(expression); + StatementMeta meta = addMeta(expression); + meta.type = OBJECT_TYPE; + } + + private void addTypeInformation(Expression expression, Expression orig) { + ClassNode type = typeChooser.resolveType(expression, node); + if (isPrimitiveType(type)) { + StatementMeta meta = addMeta(orig); + meta.type = type; + opt.chainShouldOptimize(true); + opt.chainInvolvedType(type); + } + } + + @Override + public void visitPrefixExpression(PrefixExpression expression) { + super.visitPrefixExpression(expression); + addTypeInformation(expression.getExpression(),expression); + } + + @Override + public void visitPostfixExpression(PostfixExpression expression) { + super.visitPostfixExpression(expression); + addTypeInformation(expression.getExpression(),expression); + } + + @Override + public void visitDeclarationExpression(DeclarationExpression expression) { + Expression right = expression.getRightExpression(); + right.visit(this); + + ClassNode leftType = typeChooser.resolveType(expression.getLeftExpression(), node); + Expression rightExpression = expression.getRightExpression(); + ClassNode rightType = optimizeDivWithIntOrLongTarget(rightExpression, leftType); + if (rightType==null) rightType = typeChooser.resolveType(expression.getRightExpression(), node); + if (isPrimitiveType(leftType) && isPrimitiveType(rightType)) { + // if right is a constant, then we optimize only if it makes + // a block complete, so we set a maybe + if (right instanceof ConstantExpression) { + opt.chainCanOptimize(true); + } else { + opt.chainShouldOptimize(true); + } + StatementMeta meta = addMeta(expression); + ClassNode declarationType = typeChooser.resolveType(expression, node); + meta.type = declarationType!=null?declarationType:leftType; + opt.chainInvolvedType(leftType); + opt.chainInvolvedType(rightType); + } + } + + @Override + public void visitBinaryExpression(BinaryExpression expression) { + if (expression.getNodeMetaData(StatementMeta.class)!=null) return; + super.visitBinaryExpression(expression); + + ClassNode leftType = typeChooser.resolveType(expression.getLeftExpression(), node); + ClassNode rightType = typeChooser.resolveType(expression.getRightExpression(), node); + ClassNode resultType = null; + int operation = expression.getOperation().getType(); + + if (operation==Types.LEFT_SQUARE_BRACKET && leftType.isArray()) { + opt.chainShouldOptimize(true); + resultType = leftType.getComponentType(); + } else { + switch (operation) { + case Types.COMPARE_EQUAL: + case Types.COMPARE_LESS_THAN: + case Types.COMPARE_LESS_THAN_EQUAL: + case Types.COMPARE_GREATER_THAN: + case Types.COMPARE_GREATER_THAN_EQUAL: + case Types.COMPARE_NOT_EQUAL: + if (isIntCategory(leftType) && isIntCategory(rightType)) { + opt.chainShouldOptimize(true); + } else if (isLongCategory(leftType) && isLongCategory(rightType)) { + opt.chainShouldOptimize(true); + } else if (isDoubleCategory(leftType) && isDoubleCategory(rightType)) { + opt.chainShouldOptimize(true); + } else { + opt.chainCanOptimize(true); + } + resultType = boolean_TYPE; + break; + case Types.LOGICAL_AND: case Types.LOGICAL_AND_EQUAL: + case Types.LOGICAL_OR: case Types.LOGICAL_OR_EQUAL: + if (boolean_TYPE.equals(leftType) && boolean_TYPE.equals(rightType)) { + opt.chainShouldOptimize(true); + } else { + opt.chainCanOptimize(true); + } + expression.setType(boolean_TYPE); + resultType = boolean_TYPE; + break; + case Types.DIVIDE: case Types.DIVIDE_EQUAL: + if (isLongCategory(leftType) && isLongCategory(rightType)) { + resultType = BigDecimal_TYPE; + opt.chainShouldOptimize(true); + } else if (isBigDecCategory(leftType) && isBigDecCategory(rightType)) { + // no optimization for BigDecimal yet + //resultType = BigDecimal_TYPE; + } else if (isDoubleCategory(leftType) && isDoubleCategory(rightType)) { + resultType = double_TYPE; + opt.chainShouldOptimize(true); + } + break; + case Types.POWER: case Types.POWER_EQUAL: + //TODO: implement + break; + case Types.ASSIGN: + resultType = optimizeDivWithIntOrLongTarget(expression.getRightExpression(), leftType); + opt.chainCanOptimize(true); + break; + default: + if (isIntCategory(leftType) && isIntCategory(rightType)) { + resultType = int_TYPE; + opt.chainShouldOptimize(true); + } else if (isLongCategory(leftType) && isLongCategory(rightType)) { + resultType = long_TYPE; + opt.chainShouldOptimize(true); + } else if (isBigDecCategory(leftType) && isBigDecCategory(rightType)) { + // no optimization for BigDecimal yet + //resultType = BigDecimal_TYPE; + } else if (isDoubleCategory(leftType) && isDoubleCategory(rightType)) { + resultType = double_TYPE; + opt.chainShouldOptimize(true); + } + } + } + + if (resultType!=null) { + StatementMeta meta = addMeta(expression); + meta.type = resultType; + opt.chainInvolvedType(resultType); + opt.chainInvolvedType(leftType); + opt.chainInvolvedType(rightType); + } + } + + /** + * method to optimize Z = X/Y with Z being int or long style + * @returns null if the optimization cannot be applied, otherwise it + * will return the new target type + */ + private ClassNode optimizeDivWithIntOrLongTarget(Expression rhs, ClassNode assignmentTartgetType) { + if (!(rhs instanceof BinaryExpression)) return null; + BinaryExpression binExp = (BinaryExpression) rhs; + int op = binExp.getOperation().getType(); + if (op!=Types.DIVIDE && op!=Types.DIVIDE_EQUAL) return null; + + ClassNode originalResultType = typeChooser.resolveType(binExp, node); + if ( !originalResultType.equals(BigDecimal_TYPE) || + !(isLongCategory(assignmentTartgetType) || isFloatingCategory(assignmentTartgetType)) + ) { + return null; + } + + ClassNode leftType = typeChooser.resolveType(binExp.getLeftExpression(), node); + if (!isLongCategory(leftType)) return null; + ClassNode rightType = typeChooser.resolveType(binExp.getRightExpression(), node); + if (!isLongCategory(rightType)) return null; + + ClassNode target; + if (isIntCategory(leftType) && isIntCategory(rightType)) { + target = int_TYPE; + } else if (isLongCategory(leftType) && isLongCategory(rightType)) { + target = long_TYPE; + } else if (isDoubleCategory(leftType) && isDoubleCategory(rightType)) { + target = double_TYPE; + } else { + return null; + } + StatementMeta meta = addMeta(rhs); + meta.type = target; + opt.chainInvolvedType(target); + return target; + } + + @Override + public void visitExpressionStatement(ExpressionStatement statement) { + if (statement.getNodeMetaData(StatementMeta.class)!=null) return; + opt.push(); + super.visitExpressionStatement(statement); + if (opt.shouldOptimize()) addMeta(statement,opt); + opt.pop(opt.shouldOptimize()); + } + + @Override + public void visitBlockStatement(BlockStatement block) { + opt.push(); + boolean optAll = true; + for (Statement statement : block.getStatements()) { + opt.push(); + statement.visit(this); + optAll = optAll && opt.canOptimize(); + opt.pop(true); + } + if (block.isEmpty()) { + opt.chainCanOptimize(true); + opt.pop(true); + } else { + opt.chainShouldOptimize(optAll); + if (optAll) addMeta(block,opt); + opt.pop(optAll); + } + } + + @Override + public void visitIfElse(IfStatement statement) { + opt.push(); + super.visitIfElse(statement); + if (opt.shouldOptimize()) addMeta(statement,opt); + opt.pop(opt.shouldOptimize()); + } + + /*@Override + public void visitConstantExpression(ConstantExpression expression) { + super.visitConstantExpression(expression); + opt.chainShouldOptimize(true); + }*/ + + @Override + public void visitStaticMethodCallExpression(StaticMethodCallExpression expression) { + if (expression.getNodeMetaData(StatementMeta.class)!=null) return; + super.visitStaticMethodCallExpression(expression); + + setMethodTarget(expression,expression.getMethod(), expression.getArguments(), true); + } + + @Override + public void visitMethodCallExpression(MethodCallExpression expression) { + if (expression.getNodeMetaData(StatementMeta.class)!=null) return; + super.visitMethodCallExpression(expression); + + Expression object = expression.getObjectExpression(); + boolean setTarget = AsmClassGenerator.isThisExpression(object); + if (!setTarget) { + if (!(object instanceof ClassExpression)) return; + setTarget = object.equals(node); + } + + if (!setTarget) return; + setMethodTarget(expression, expression.getMethodAsString(), expression.getArguments(), true); + } + + @Override + public void visitConstructorCallExpression(ConstructorCallExpression call) { + if (call.getNodeMetaData(StatementMeta.class)!=null) return; + super.visitConstructorCallExpression(call); + + // we cannot a target for the constructor call, since we cannot easily + // check the meta class of the other class + // setMethodTarget(call, "<init>", call.getArguments(), false); + } + + private void setMethodTarget(Expression expression, String name, Expression callArgs, boolean isMethod) { + if (name==null) return; + if (!optimizeMethodCall) return; + if (AsmClassGenerator.containsSpreadExpression(callArgs)) return; + // find method call target + Parameter[] paraTypes = null; + if (callArgs instanceof ArgumentListExpression) { + ArgumentListExpression args = (ArgumentListExpression) callArgs; + int size = args.getExpressions().size(); + paraTypes = new Parameter[size]; + int i=0; + for (Expression exp: args.getExpressions()) { + ClassNode type = typeChooser.resolveType(exp, node); + if (!validTypeForCall(type)) return; + paraTypes[i] = new Parameter(type,""); + i++; + } + } else { + ClassNode type = typeChooser.resolveType(callArgs, node); + if (!validTypeForCall(type)) return; + paraTypes = new Parameter[]{new Parameter(type,"")}; + } + + MethodNode target; + ClassNode type; + if (isMethod) { + target = node.getMethod(name, paraTypes); + if (target==null) return; + if (!target.getDeclaringClass().equals(node)) return; + if (scope.isInStaticContext() && !target.isStatic()) return; + type = target.getReturnType().redirect(); + } else { + type = expression.getType(); + target = selectConstructor(type, paraTypes); + if (target==null) return; + } + + StatementMeta meta = addMeta(expression); + meta.target = target; + meta.type = type; + opt.chainShouldOptimize(true); + } + + private static MethodNode selectConstructor(ClassNode node, Parameter[] paraTypes) { + List<ConstructorNode> cl = node.getDeclaredConstructors(); + MethodNode res = null; + for (ConstructorNode cn : cl) { + if (ParameterUtils.parametersEqual(cn.getParameters(), paraTypes)) { + res = cn; + break; + } + } + if (res !=null && res.isPublic()) return res; + return null; + } + + private static boolean validTypeForCall(ClassNode type) { + // do call only for final classes and primitive types + if (isPrimitiveType(type)) return true; + return (type.getModifiers() & ACC_FINAL) > 0; + } + + @Override + public void visitClosureExpression(ClosureExpression expression) { + return; + } + + @Override + public void visitForLoop(ForStatement statement) { + opt.push(); + super.visitForLoop(statement); + if (opt.shouldOptimize()) addMeta(statement,opt); + opt.pop(opt.shouldOptimize()); + } + } + + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/StatementMetaTypeChooser.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/StatementMetaTypeChooser.java b/src/main/java/org/codehaus/groovy/classgen/asm/StatementMetaTypeChooser.java new file mode 100644 index 0000000..2ada09d --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/StatementMetaTypeChooser.java @@ -0,0 +1,58 @@ +/* + * 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.FieldNode; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.VariableExpression; + +/** + * A {@link TypeChooser} which is aware of statement metadata. + * + * @author Jochen Theodorou + * @author Cedric Champeau + */ +public class StatementMetaTypeChooser implements TypeChooser { + public ClassNode resolveType(final Expression exp, final ClassNode current) { + if (exp instanceof ClassExpression) return ClassHelper.CLASS_Type; + OptimizingStatementWriter.StatementMeta meta = exp.getNodeMetaData(OptimizingStatementWriter.StatementMeta.class); + ClassNode type = null; + if (meta != null) type = meta.type; + if (type != null) return type; + if (exp instanceof VariableExpression) { + VariableExpression ve = (VariableExpression) exp; + if (ve.isClosureSharedVariable()) return ve.getType(); + type = ve.getOriginType(); + if (ve.getAccessedVariable() instanceof FieldNode) { + FieldNode fn = (FieldNode) ve.getAccessedVariable(); + if (!fn.getDeclaringClass().equals(current)) return fn.getOriginType(); + } + } else if (exp instanceof Variable) { + Variable v = (Variable) exp; + type = v.getOriginType(); + } else { + type = exp.getType(); + } + return type.redirect(); + } +}
