http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionWriter.java new file mode 100644 index 0000000..c03515a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionWriter.java @@ -0,0 +1,328 @@ +/* + * 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.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import static org.codehaus.groovy.syntax.Types.BITWISE_OR; +import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL; +import static org.codehaus.groovy.syntax.Types.COMPARE_TO; +import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT; +import static org.codehaus.groovy.syntax.Types.LEFT_SQUARE_BRACKET; +import static org.codehaus.groovy.syntax.Types.MINUS_MINUS; +import static org.codehaus.groovy.syntax.Types.PLUS; +import static org.codehaus.groovy.syntax.Types.PLUS_PLUS; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.ICONST_1; +import static org.objectweb.asm.Opcodes.ICONST_M1; +import static org.objectweb.asm.Opcodes.IFEQ; +import static org.objectweb.asm.Opcodes.IFGE; +import static org.objectweb.asm.Opcodes.IFGT; +import static org.objectweb.asm.Opcodes.IFLE; +import static org.objectweb.asm.Opcodes.IFLT; +import static org.objectweb.asm.Opcodes.IFNE; + +/** + * Base class for writing primitive typed operations + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public abstract class BinaryExpressionWriter { + + private final WriterController controller; + private MethodCaller arraySet, arrayGet; + + public BinaryExpressionWriter(WriterController controller, MethodCaller arraySet, MethodCaller arrayGet) { + this.controller = controller; + this.arraySet = arraySet; + this.arrayGet = arrayGet; + } + + /** + * return writer controller + * @since 2.5.0 + */ + public WriterController getController() { + return controller; + } + + protected static final int[] stdCompareCodes = { + IFEQ, // COMPARE_NOT_EQUAL 120 + IFNE, // COMPARE_IDENTICAL 121 + IFEQ, // COMPARE_NOT_IDENTICAL 122 + IFNE, // COMPARE_EQUAL 123 + IFGE, // COMPARE_LESS_THAN 124 + IFGT, // COMPARE_LESS_THAN_EQUAL 125 + IFLE, // COMPARE_GREATER_THAN 126 + IFLT, // COMPARE_GREATER_THAN_EQUAL 127 + }; + + protected abstract int getCompareCode(); + + /** + * writes some int standard operations for compares + * @param type the token type + * @return true if a successful std operator write + */ + protected boolean writeStdCompare(int type, boolean simulate) { + type = type-COMPARE_NOT_EQUAL; + // look if really compare + if (type<0||type>7) return false; + + if (!simulate) { + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + // operands are on the stack already + int bytecode = stdCompareCodes[type]; + mv.visitInsn(getCompareCode()); + Label l1 = new Label(); + mv.visitJumpInsn(bytecode,l1); + mv.visitInsn(ICONST_1); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); + mv.visitInsn(ICONST_0); + mv.visitLabel(l2); + operandStack.replace(ClassHelper.boolean_TYPE, 2); + } + return true; + } + + protected abstract void doubleTwoOperands(MethodVisitor mv); + protected abstract void removeTwoOperands(MethodVisitor mv); + + protected boolean writeSpaceship(int type, boolean simulate) { + if (type != COMPARE_TO) return false; + /* + we will actually do + + (x < y) ? -1 : ((x == y) ? 0 : 1) + which is the essence of what the call with Number would do + this compiles to something along + + <x> + <y> + LCMP + IFGE L1 + ICONST_M1 + GOTO L2 + L1 + <x> + <y> + LCMP + IFNE L3 + ICONST_0 + GOTO L2 + L3 + ICONST_1 + L2 + + since the operators are already on the stack and we don't want + to load them again, we will instead duplicate them. This will + require some pop actions in the branches! + + DUP4 (operands: L1L2L1L2) + LCMP + IFGE L1 (operands: L1L2) + ICONST_M1 (operands: L1L2I) + GOTO L2 + L1 + ----- (operands: L1L2) + LCMP + IFNE L3 (operands: -) + ICONST_0 (operands: I) + GOTO L2 + L3 + - jump from L1 branch to here (operands: -) + ICONST_1 (operands: I) + L2 + - if jump from GOTO L2 we have LLI, but need only I + - if from L3 branch we get only I + + this means we have to pop of LL before loading -1 + + since there is no DUP4 we have to do this: + DUP2_X1 + POP2 + DUP2_X1 + DUP2_X1 + POP2 + DUP2_X1 + */ + if (!simulate) { + MethodVisitor mv = controller.getMethodVisitor(); + // duplicate arguments + doubleTwoOperands(mv); + + Label l1 = new Label(); + mv.visitInsn(getCompareCode()); + mv.visitJumpInsn(IFGE,l1); + // no jump, so -1, need to pop off surplus LL + removeTwoOperands(mv); + mv.visitInsn(ICONST_M1); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + + mv.visitLabel(l1); + Label l3 = new Label(); + mv.visitInsn(getCompareCode()); + mv.visitJumpInsn(IFNE,l3); + mv.visitInsn(ICONST_0); + mv.visitJumpInsn(GOTO,l2); + + mv.visitLabel(l3); + mv.visitInsn(ICONST_1); + + controller.getOperandStack().replace(ClassHelper.int_TYPE, 2); + } + return true; + } + + protected abstract ClassNode getNormalOpResultType(); + protected abstract int getStandardOperationBytecode(int type); + + protected boolean writeStdOperators(int type, boolean simulate) { + type = type-PLUS; + if (type<0 || type>5 || type == 3 /*DIV*/) return false; + + if (!simulate) { + int bytecode = getStandardOperationBytecode(type); + controller.getMethodVisitor().visitInsn(bytecode); + controller.getOperandStack().replace(getNormalOpResultType(), 2); + } + return true; + } + + protected boolean writeDivision(boolean simulate) { + if (!supportsDivision()) return false; + if (!simulate) { + int bytecode = getStandardOperationBytecode(3 /*DIV*/); + controller.getMethodVisitor().visitInsn(bytecode); + controller.getOperandStack().replace(getDevisionOpResultType(), 2); + } + return true; + } + + protected boolean supportsDivision() { + return false; + } + + protected abstract ClassNode getDevisionOpResultType(); + + protected abstract int getBitwiseOperationBytecode(int type); + + /** + * writes some the bitwise operations. type is one of BITWISE_OR, + * BITWISE_AND, BITWISE_XOR + * @param type the token type + * @return true if a successful bitwise operation write + */ + protected boolean writeBitwiseOp(int type, boolean simulate) { + type = type-BITWISE_OR; + if (type<0 || type>2) return false; + + if (!simulate) { + int bytecode = getBitwiseOperationBytecode(type); + controller.getMethodVisitor().visitInsn(bytecode); + controller.getOperandStack().replace(getNormalOpResultType(), 2); + } + return true; + } + + protected abstract int getShiftOperationBytecode(int type); + + /** + * Write shifting operations. + * Type is one of LEFT_SHIFT, RIGHT_SHIFT, or RIGHT_SHIFT_UNSIGNED + * + * @param type the token type + * @return true on a successful shift operation write + */ + protected boolean writeShiftOp(int type, boolean simulate) { + type = type - LEFT_SHIFT; + if (type < 0 || type > 2) return false; + + if (!simulate) { + int bytecode = getShiftOperationBytecode(type); + controller.getMethodVisitor().visitInsn(bytecode); + controller.getOperandStack().replace(getNormalOpResultType(), 2); + } + return true; + } + + public boolean write(int operation, boolean simulate) { + return writeStdCompare(operation, simulate) || + writeSpaceship(operation, simulate) || + writeStdOperators(operation, simulate) || + writeBitwiseOp(operation, simulate) || + writeShiftOp(operation, simulate); + } + + protected MethodCaller getArrayGetCaller() { + return arrayGet; + } + + protected ClassNode getArrayGetResultType(){ + return getNormalOpResultType(); + } + + protected MethodCaller getArraySetCaller() { + return arraySet; + } + + public void setArraySetAndGet(MethodCaller arraySet, MethodCaller arrayGet) { + this.arraySet = arraySet; + this.arrayGet = arrayGet; + } + + public boolean arrayGet(int operation, boolean simulate) { + if (operation!=LEFT_SQUARE_BRACKET) return false; + + if (!simulate) { + getArrayGetCaller().call(controller.getMethodVisitor()); + } + return true; + } + + public boolean arraySet(boolean simulate) { + if (!simulate) { + getArraySetCaller().call(controller.getMethodVisitor()); + } + return true; + } + + public boolean writePostOrPrefixMethod(int operation, boolean simulate) { + if (operation!=PLUS_PLUS && operation!=MINUS_MINUS) return false; + if (!simulate) { + MethodVisitor mv = controller.getMethodVisitor(); + if (operation==PLUS_PLUS) { + writePlusPlus(mv); + } else { + writeMinusMinus(mv); + } + } + return true; + } + + protected abstract void writePlusPlus(MethodVisitor mv); + protected abstract void writeMinusMinus(MethodVisitor mv); +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BinaryFloatExpressionHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryFloatExpressionHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryFloatExpressionHelper.java new file mode 100644 index 0000000..6a4552a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryFloatExpressionHelper.java @@ -0,0 +1,111 @@ +/* + * 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.runtime.BytecodeInterface8; +import org.objectweb.asm.MethodVisitor; + +import static org.objectweb.asm.Opcodes.DUP2; +import static org.objectweb.asm.Opcodes.FADD; +import static org.objectweb.asm.Opcodes.FCMPG; +import static org.objectweb.asm.Opcodes.FCONST_1; +import static org.objectweb.asm.Opcodes.FDIV; +import static org.objectweb.asm.Opcodes.FMUL; +import static org.objectweb.asm.Opcodes.FREM; +import static org.objectweb.asm.Opcodes.FSUB; +import static org.objectweb.asm.Opcodes.POP2; + +/** + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class BinaryFloatExpressionHelper extends BinaryExpressionWriter { + + public BinaryFloatExpressionHelper(WriterController controller) { + super(controller, floatArraySet, floatArrayGet); + } + + protected void doubleTwoOperands(MethodVisitor mv) { + mv.visitInsn(DUP2); + } + + private static final MethodCaller + floatArrayGet = MethodCaller.newStatic(BytecodeInterface8.class, "fArrayGet"), + floatArraySet = MethodCaller.newStatic(BytecodeInterface8.class, "fArraySet"); + + protected boolean writeBitwiseOp(int type, boolean simulate) { + if (!simulate) throw new GroovyBugError("should not reach here"); + return false; + } + + protected int getBitwiseOperationBytecode(int type) { + return -1; + } + + protected int getCompareCode() { + return FCMPG; + } + + protected ClassNode getNormalOpResultType() { + return ClassHelper.float_TYPE; + } + + protected boolean writeShiftOp(int type, boolean simulate) { + if (!simulate) throw new GroovyBugError("should not reach here"); + return false; + } + + protected int getShiftOperationBytecode(int type) { + return -1; + } + + private static final int[] stdOperations = { + FADD, // PLUS 200 + FSUB, // MINUS 201 + FMUL, // MULTIPLY 202 + 0, // DIV, (203) but we don't want that one + FDIV, // INTDIV 204 + FREM, // MOD 203 + }; + + protected int getStandardOperationBytecode(int type) { + return stdOperations[type]; + } + + protected void removeTwoOperands(MethodVisitor mv) { + mv.visitInsn(POP2); + } + + protected void writeMinusMinus(MethodVisitor mv) { + mv.visitInsn(FCONST_1); + mv.visitInsn(FSUB); + } + + protected void writePlusPlus(MethodVisitor mv) { + mv.visitInsn(FCONST_1); + mv.visitInsn(FADD); + } + + protected ClassNode getDevisionOpResultType() { + return ClassHelper.BigDecimal_TYPE; + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BinaryIntExpressionHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryIntExpressionHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryIntExpressionHelper.java new file mode 100644 index 0000000..dfa1245 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryIntExpressionHelper.java @@ -0,0 +1,293 @@ +/* + * 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.runtime.BytecodeInterface8; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL; +import static org.codehaus.groovy.syntax.Types.COMPARE_TO; +import static org.objectweb.asm.Opcodes.DUP2; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.IADD; +import static org.objectweb.asm.Opcodes.IAND; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.ICONST_1; +import static org.objectweb.asm.Opcodes.ICONST_M1; +import static org.objectweb.asm.Opcodes.IDIV; +import static org.objectweb.asm.Opcodes.IF_ICMPEQ; +import static org.objectweb.asm.Opcodes.IF_ICMPGE; +import static org.objectweb.asm.Opcodes.IF_ICMPGT; +import static org.objectweb.asm.Opcodes.IF_ICMPLE; +import static org.objectweb.asm.Opcodes.IF_ICMPLT; +import static org.objectweb.asm.Opcodes.IF_ICMPNE; +import static org.objectweb.asm.Opcodes.IMUL; +import static org.objectweb.asm.Opcodes.IOR; +import static org.objectweb.asm.Opcodes.IREM; +import static org.objectweb.asm.Opcodes.ISHL; +import static org.objectweb.asm.Opcodes.ISHR; +import static org.objectweb.asm.Opcodes.ISUB; +import static org.objectweb.asm.Opcodes.IUSHR; +import static org.objectweb.asm.Opcodes.IXOR; +import static org.objectweb.asm.Opcodes.POP2; + +public class BinaryIntExpressionHelper extends BinaryExpressionWriter { + + private static final MethodCaller intArrayGet = MethodCaller.newStatic(BytecodeInterface8.class, "intArrayGet"); + private static final MethodCaller intArraySet = MethodCaller.newStatic(BytecodeInterface8.class, "intArraySet"); + + private static final int[] stdCompareCodes = { + IF_ICMPEQ, // COMPARE_NOT_EQUAL 120 + IF_ICMPNE, // COMPARE_IDENTICAL 121 + IF_ICMPEQ, // COMPARE_NOT_IDENTICAL 122 + IF_ICMPNE, // COMPARE_EQUAL 123 + IF_ICMPGE, // COMPARE_LESS_THAN 124 + IF_ICMPGT, // COMPARE_LESS_THAN_EQUAL 125 + IF_ICMPLE, // COMPARE_GREATER_THAN 126 + IF_ICMPLT, // COMPARE_GREATER_THAN_EQUAL 127 + }; + + private static final int[] stdOperations = { + IADD, // PLUS 200 + ISUB, // MINUS 201 + IMUL, // MULTIPLY 202 + IDIV, // DIV 203 + IDIV, // INTDIV 204 + IREM, // MOD 203 + }; + + private static final int[] bitOp = { + IOR, // BITWISE_OR / PIPE 340 + IAND, // BITWISE_AND 341 + IXOR, // BIWISE_XOR 342 + }; + + /* unhandled types from from org.codehaus.groovy.syntax.Types + public static final int LOGICAL_OR = 162; // || + public static final int LOGICAL_AND = 164; // && + + public static final int DIVIDE = 203; // / + public static final int STAR_STAR = 206; // ** + public static final int POWER = STAR_STAR; // + + public static final int PLUS_EQUAL = 210; // += + public static final int MINUS_EQUAL = 211; // -= + public static final int MULTIPLY_EQUAL = 212; // *= + public static final int DIVIDE_EQUAL = 213; // /= + public static final int INTDIV_EQUAL = 214; // \= + public static final int MOD_EQUAL = 215; // %= + public static final int POWER_EQUAL = 216; // **= + + public static final int PLUS_PLUS = 250; // ++ + public static final int PREFIX_PLUS_PLUS = 251; // ++ + public static final int POSTFIX_PLUS_PLUS = 252; // ++ + public static final int PREFIX_PLUS = 253; // + + + public static final int MINUS_MINUS = 260; // -- + public static final int PREFIX_MINUS_MINUS = 261; // -- + public static final int POSTFIX_MINUS_MINUS = 262; // -- + public static final int PREFIX_MINUS = 263; // - (negation) +*/ + private static final int[] shiftOp = { + ISHL, // LEFT_SHIFT 280 + ISHR, // RIGHT_SHIFT 281 + IUSHR // RIGHT_SHIFT_UNSIGNED 282 + }; + +/* + public static final int LEFT_SHIFT_EQUAL = 285; // <<= + public static final int RIGHT_SHIFT_EQUAL = 286; // >>= + public static final int RIGHT_SHIFT_UNSIGNED_EQUAL = 287; // >>>= + + public static final int BITWISE_OR_EQUAL = 350; // |= + public static final int BITWISE_AND_EQUAL = 351; // &= + public static final int BITWISE_XOR_EQUAL = 352; // ^= + public static final int BITWISE_NEGATION = REGEX_PATTERN; // ~ + */ + + public BinaryIntExpressionHelper(WriterController wc) { + this(wc, intArraySet, intArrayGet); + } + + /** + * @since 2.5.0 + */ + public BinaryIntExpressionHelper(WriterController wc, MethodCaller arraySet, MethodCaller arrayGet) { + super(wc, arraySet, arrayGet); + } + + + /** + * writes a std compare. This involves the tokens IF_ICMPEQ, IF_ICMPNE, + * IF_ICMPEQ, IF_ICMPNE, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE and IF_ICMPLT + * @param type the token type + * @return true if a successful std compare write + */ + protected boolean writeStdCompare(int type, boolean simulate) { + type = type-COMPARE_NOT_EQUAL; + // look if really compare + if (type<0||type>7) return false; + + if (!simulate) { + MethodVisitor mv = getController().getMethodVisitor(); + OperandStack operandStack = getController().getOperandStack(); + // operands are on the stack already + int bytecode = stdCompareCodes[type]; + Label l1 = new Label(); + mv.visitJumpInsn(bytecode,l1); + mv.visitInsn(ICONST_1); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); + mv.visitInsn(ICONST_0); + mv.visitLabel(l2); + operandStack.replace(ClassHelper.boolean_TYPE, 2); + } + return true; + } + + /** + * writes the spaceship operator, type should be COMPARE_TO + * @param type the token type + * @return true if a successful spaceship operator write + */ + protected boolean writeSpaceship(int type, boolean simulate) { + if (type != COMPARE_TO) return false; + /* + we will actually do + + (x < y) ? -1 : ((x == y) ? 0 : 1) + which is the essence of what the call with Integers would do + this compiles to something along + + <x> + <y> + IF_ICMPGE L1 + ICONST_M1 + GOTO L2 + L1 + <x> + <y> + IF_ICMPNE L3 + ICONST_0 + GOTO L2 + L3 + ICONST_1 + L2 + + since the operators are already on the stack and we don't want + to load them again, we will instead duplicate them. This will + require some pop actions in the branches! + + DUP2 (operands: IIII) + IF_ICMPGE L1 (operands: II) + ICONST_M1 (operands: III) + GOTO L2 + L1 + ----- (operands: II) + IF_ICMPNE L3 (operands: -) + ICONST_0 (operands: I) + GOTO L2 + L3 + - jump from L1 branch to here (operands: -) + ICONST_1 (operands: I) + L2 + - if jump from GOTO L2 we have III, but need only I + - if from L3 branch we get only I + + this means we have to pop of II before loading -1 + + */ + if (!simulate) { + MethodVisitor mv = getController().getMethodVisitor(); + // duplicate int arguments + mv.visitInsn(DUP2); + + Label l1 = new Label(); + mv.visitJumpInsn(IF_ICMPGE,l1); + // no jump, so -1, need to pop off surplus II + mv.visitInsn(POP2); + mv.visitInsn(ICONST_M1); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + + mv.visitLabel(l1); + Label l3 = new Label(); + mv.visitJumpInsn(IF_ICMPNE,l3); + mv.visitInsn(ICONST_0); + mv.visitJumpInsn(GOTO,l2); + + mv.visitLabel(l3); + mv.visitInsn(ICONST_1); + + getController().getOperandStack().replace(ClassHelper.int_TYPE, 2); + } + return true; + } + + protected void doubleTwoOperands(MethodVisitor mv) { + mv.visitInsn(DUP2); + } + + protected int getBitwiseOperationBytecode(int type) { + return bitOp[type]; + } + + protected int getCompareCode() { + return -1; + } + + protected ClassNode getNormalOpResultType() { + return ClassHelper.int_TYPE; + } + + protected int getShiftOperationBytecode(int type) { + return shiftOp[type]; + } + + protected int getStandardOperationBytecode(int type) { + return stdOperations[type]; + } + + protected void removeTwoOperands(MethodVisitor mv) { + mv.visitInsn(POP2); + } + + protected void writeMinusMinus(MethodVisitor mv) { + mv.visitInsn(ICONST_1); + mv.visitInsn(ISUB); + } + + protected void writePlusPlus(MethodVisitor mv) { + mv.visitInsn(ICONST_1); + mv.visitInsn(IADD); + } + + protected ClassNode getDevisionOpResultType() { + return ClassHelper.int_TYPE; + } + + @Override + protected boolean supportsDivision() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BinaryLongExpressionHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryLongExpressionHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryLongExpressionHelper.java new file mode 100644 index 0000000..8bfaf98 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryLongExpressionHelper.java @@ -0,0 +1,144 @@ +/* + * 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.runtime.BytecodeInterface8; +import org.objectweb.asm.MethodVisitor; + +import static org.objectweb.asm.Opcodes.DUP2_X1; +import static org.objectweb.asm.Opcodes.LADD; +import static org.objectweb.asm.Opcodes.LAND; +import static org.objectweb.asm.Opcodes.LCMP; +import static org.objectweb.asm.Opcodes.LCONST_1; +import static org.objectweb.asm.Opcodes.LDIV; +import static org.objectweb.asm.Opcodes.LMUL; +import static org.objectweb.asm.Opcodes.LOR; +import static org.objectweb.asm.Opcodes.LREM; +import static org.objectweb.asm.Opcodes.LSHL; +import static org.objectweb.asm.Opcodes.LSHR; +import static org.objectweb.asm.Opcodes.LSUB; +import static org.objectweb.asm.Opcodes.LUSHR; +import static org.objectweb.asm.Opcodes.LXOR; +import static org.objectweb.asm.Opcodes.POP2; + +/** + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class BinaryLongExpressionHelper extends BinaryExpressionWriter { + + /** + * @since 2.5.0 + */ + public BinaryLongExpressionHelper(WriterController controller, MethodCaller arraySet, MethodCaller arrayGet) { + super(controller, arraySet, arrayGet); + } + + public BinaryLongExpressionHelper(WriterController controller) { + this(controller, longArraySet, longArrayGet); + } + + protected void doubleTwoOperands(MethodVisitor mv) { + /* + since there is no DUP4 we have to do this: + DUP2_X1 + POP2 + DUP2_X1 + DUP2_X1 + POP2 + DUP2_X1 + */ + mv.visitInsn(DUP2_X1); + mv.visitInsn(POP2); + mv.visitInsn(DUP2_X1); + mv.visitInsn(DUP2_X1); + mv.visitInsn(POP2); + mv.visitInsn(DUP2_X1); + } + + protected void removeTwoOperands(MethodVisitor mv) { + mv.visitInsn(POP2); + mv.visitInsn(POP2); + } + + private static final MethodCaller + longArrayGet = MethodCaller.newStatic(BytecodeInterface8.class, "lArrayGet"), + longArraySet = MethodCaller.newStatic(BytecodeInterface8.class, "lArraySet"); + + private static final int[] bitOp = { + LOR, // BITWISE_OR / PIPE 340 + LAND, // BITWISE_AND 341 + LXOR, // BIWISE_XOR 342 + }; + + protected int getBitwiseOperationBytecode(int type) { + return bitOp[type]; + } + + protected int getCompareCode() { + return LCMP; + } + + protected ClassNode getNormalOpResultType() { + return ClassHelper.long_TYPE; + } + + private static final int[] shiftOp = { + LSHL, // LEFT_SHIFT 280 + LSHR, // RIGHT_SHIFT 281 + LUSHR // RIGHT_SHIFT_UNSIGNED 282 + }; + + protected int getShiftOperationBytecode(int type) { + return shiftOp[type]; + } + + private static final int[] stdOperations = { + LADD, // PLUS 200 + LSUB, // MINUS 201 + LMUL, // MULTIPLY 202 + LDIV, // DIV 203 + LDIV, // INTDIV 204 + LREM, // MOD 203 + }; + + protected int getStandardOperationBytecode(int type) { + return stdOperations[type]; + } + + protected void writeMinusMinus(MethodVisitor mv) { + mv.visitInsn(LCONST_1); + mv.visitInsn(LSUB); + } + + protected void writePlusPlus(MethodVisitor mv) { + mv.visitInsn(LCONST_1); + mv.visitInsn(LADD); + } + + protected ClassNode getDevisionOpResultType() { + return ClassHelper.long_TYPE; + } + + @Override + protected boolean supportsDivision() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BinaryObjectExpressionHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryObjectExpressionHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryObjectExpressionHelper.java new file mode 100644 index 0000000..31350d5 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryObjectExpressionHelper.java @@ -0,0 +1,87 @@ +/* + * 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.runtime.BytecodeInterface8; +import org.objectweb.asm.MethodVisitor; + +/** + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class BinaryObjectExpressionHelper extends BinaryExpressionWriter { + private static final MethodCaller arrayGet = MethodCaller.newStatic(BytecodeInterface8.class, "objectArrayGet"); + private static final MethodCaller arraySet = MethodCaller.newStatic(BytecodeInterface8.class, "objectArraySet"); + + public BinaryObjectExpressionHelper(WriterController controller) { + super(controller, arraySet, arrayGet); + } + + // dummy methods + public boolean writePostOrPrefixMethod(int operation, boolean simulate) { + if (simulate) return false; + throw new GroovyBugError("should not reach here"); + } + + public boolean write(int operation, boolean simulate) { + if (simulate) return false; + throw new GroovyBugError("should not reach here"); + } + + protected boolean writeDivision(boolean simulate) { + if (simulate) return false; + throw new GroovyBugError("should not reach here"); + } + + protected int getBitwiseOperationBytecode(int type) { + return -1; + } + + protected int getCompareCode() { + return -1; + } + + protected ClassNode getNormalOpResultType() { + return null; + } + + protected ClassNode getDevisionOpResultType() { + return null; + } + + protected int getShiftOperationBytecode(int type) { + return -1; + } + + protected int getStandardOperationBytecode(int type) { + return -1; + } + + protected void removeTwoOperands(MethodVisitor mv) {} + protected void writePlusPlus(MethodVisitor mv) {} + protected void writeMinusMinus(MethodVisitor mv) {} + protected void doubleTwoOperands(MethodVisitor mv) {} + + @Override + protected ClassNode getArrayGetResultType() { + return ClassHelper.OBJECT_TYPE; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeDumper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeDumper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeDumper.java new file mode 100644 index 0000000..935020f --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeDumper.java @@ -0,0 +1,52 @@ +/* + * 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.control.BytecodeProcessor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.TraceClassVisitor; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * An utility class which can be used in test cases to dump generated bytecode. + * + * @author Cédric Champeau + * @since 2.4.0 + */ +public class BytecodeDumper implements BytecodeProcessor { + public static final BytecodeDumper STANDARD_ERR = new BytecodeDumper(new PrintWriter(System.err)); + + private final Writer out; + + public BytecodeDumper(final Writer out) { + this.out = out; + } + + @Override + public byte[] processBytecode(final String name, final byte[] original) { + PrintWriter pw = out instanceof PrintWriter ? (PrintWriter) out : new PrintWriter(out); + TraceClassVisitor visitor = new TraceClassVisitor(pw); + ClassReader reader = new ClassReader(original); + reader.accept(visitor, 0); + return original; + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java new file mode 100644 index 0000000..d9eea08 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java @@ -0,0 +1,751 @@ +/* + * 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.CompileUnit; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.decompiled.DecompiledClassNode; +import org.codehaus.groovy.reflection.ReflectionCache; +import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; + +/** + * A helper class for bytecode generation with AsmClassGenerator. + * + * @author <a href="mailto:[email protected]">James Strachan</a> + * @author <a href="mailto:[email protected]">Bing Ran</a> + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + */ +public class BytecodeHelper implements Opcodes { + + private static String DTT_CLASSNAME = BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class.getName()); + + public static String getClassInternalName(ClassNode t) { + if (t.isPrimaryClassNode() || t instanceof DecompiledClassNode) { + if (t.isArray()) return "[L"+getClassInternalName(t.getComponentType())+";"; + return getClassInternalName(t.getName()); + } + return getClassInternalName(t.getTypeClass()); + } + + public static String getClassInternalName(Class t) { + return org.objectweb.asm.Type.getInternalName(t); + } + + /** + * @return the ASM internal name of the type + */ + public static String getClassInternalName(String name) { + return name.replace('.', '/'); + } + + public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) { + StringBuilder buffer = new StringBuilder("("); + for (int i = 0; i < parameters.length; i++) { + buffer.append(getTypeDescription(parameters[i].getType())); + } + buffer.append(")"); + buffer.append(getTypeDescription(returnType)); + return buffer.toString(); + } + + /** + * Returns a method descriptor for the given {@link org.codehaus.groovy.ast.MethodNode}. + * + * @param methodNode the method node for which to create the descriptor + * @return a method descriptor as defined in section JVMS section 4.3.3 + */ + public static String getMethodDescriptor(MethodNode methodNode) { + return getMethodDescriptor(methodNode.getReturnType(), methodNode.getParameters()); + } + + /** + * @return the ASM method type descriptor + */ + public static String getMethodDescriptor(Class returnType, Class[] paramTypes) { + // lets avoid class loading + StringBuilder buffer = new StringBuilder("("); + for (int i = 0; i < paramTypes.length; i++) { + buffer.append(getTypeDescription(paramTypes[i])); + } + buffer.append(")"); + buffer.append(getTypeDescription(returnType)); + return buffer.toString(); + } + + public static String getTypeDescription(Class c) { + return org.objectweb.asm.Type.getDescriptor(c); + } + + /** + * array types are special: + * eg.: String[]: classname: [Ljava.lang.String; + * Object: classname: java.lang.Object + * int[] : classname: [I + * unlike getTypeDescription '.' is not replaced by '/'. + * it seems that makes problems for + * the class loading if '.' is replaced by '/' + * + * @return the ASM type description for class loading + */ + public static String getClassLoadingTypeDescription(ClassNode c) { + StringBuilder buf = new StringBuilder(); + boolean array = false; + while (true) { + if (c.isArray()) { + buf.append('['); + c = c.getComponentType(); + array = true; + } else { + if (ClassHelper.isPrimitiveType(c)) { + buf.append(getTypeDescription(c)); + } else { + if (array) buf.append('L'); + buf.append(c.getName()); + if (array) buf.append(';'); + } + return buf.toString(); + } + } + } + + /** + * array types are special: + * eg.: String[]: classname: [Ljava/lang/String; + * int[]: [I + * + * @return the ASM type description + */ + public static String getTypeDescription(ClassNode c) { + return getTypeDescription(c, true); + } + + /** + * array types are special: + * eg.: String[]: classname: [Ljava/lang/String; + * int[]: [I + * + * @return the ASM type description + */ + private static String getTypeDescription(ClassNode c, boolean end) { + StringBuilder buf = new StringBuilder(); + ClassNode d = c; + while (true) { + if (ClassHelper.isPrimitiveType(d.redirect())) { + d = d.redirect(); + char car; + if (d == ClassHelper.int_TYPE) { + car = 'I'; + } else if (d == ClassHelper.VOID_TYPE) { + car = 'V'; + } else if (d == ClassHelper.boolean_TYPE) { + car = 'Z'; + } else if (d == ClassHelper.byte_TYPE) { + car = 'B'; + } else if (d == ClassHelper.char_TYPE) { + car = 'C'; + } else if (d == ClassHelper.short_TYPE) { + car = 'S'; + } else if (d == ClassHelper.double_TYPE) { + car = 'D'; + } else if (d == ClassHelper.float_TYPE) { + car = 'F'; + } else /* long */ { + car = 'J'; + } + buf.append(car); + return buf.toString(); + } else if (d.isArray()) { + buf.append('['); + d = d.getComponentType(); + } else { + buf.append('L'); + String name = d.getName(); + int len = name.length(); + for (int i = 0; i < len; ++i) { + char car = name.charAt(i); + buf.append(car == '.' ? '/' : car); + } + if (end) buf.append(';'); + return buf.toString(); + } + } + } + + /** + * @return an array of ASM internal names of the type + */ + public static String[] getClassInternalNames(ClassNode[] names) { + int size = names.length; + String[] answer = new String[size]; + for (int i = 0; i < size; i++) { + answer[i] = getClassInternalName(names[i]); + } + return answer; + } + + public static void pushConstant(MethodVisitor mv, int value) { + switch (value) { + 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 (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + mv.visitIntInsn(BIPUSH, value); + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + mv.visitIntInsn(SIPUSH, value); + } else { + mv.visitLdcInsn(Integer.valueOf(value)); + } + } + } + + /** + * negate a boolean on stack. true->false, false->true + */ + public static void negateBoolean(MethodVisitor mv) { + // code to negate the primitive boolean + Label endLabel = new Label(); + Label falseLabel = new Label(); + mv.visitJumpInsn(IFNE, falseLabel); + mv.visitInsn(ICONST_1); + mv.visitJumpInsn(GOTO, endLabel); + mv.visitLabel(falseLabel); + mv.visitInsn(ICONST_0); + mv.visitLabel(endLabel); + } + + /** + * load a message on the stack and remove it right away. Good for put a mark in the generated bytecode for debugging purpose. + * + * @param msg + */ + /*public void mark(String msg) { + mv.visitLdcInsn(msg); + mv.visitInsn(POP); + }*/ + + /** + * returns a name that Class.forName() can take. Notably for arrays: + * [I, [Ljava.lang.String; etc + * Regular object type: java.lang.String + * + * @param name + */ + public static String formatNameForClassLoading(String name) { + if (name == null) { + return "java.lang.Object;"; + } + + if (name.equals("int") + || name.equals("long") + || name.equals("short") + || name.equals("float") + || name.equals("double") + || name.equals("byte") + || name.equals("char") + || name.equals("boolean") + || name.equals("void") + ) { + return name; + } + + if (name.startsWith("[")) { + return name.replace('/', '.'); + } + + if (name.startsWith("L")) { + name = name.substring(1); + if (name.endsWith(";")) { + name = name.substring(0, name.length() - 1); + } + return name.replace('/', '.'); + } + + String prefix = ""; + if (name.endsWith("[]")) { // todo need process multi + prefix = "["; + name = name.substring(0, name.length() - 2); + if (name.equals("int")) { + return prefix + "I"; + } else if (name.equals("long")) { + return prefix + "J"; + } else if (name.equals("short")) { + return prefix + "S"; + } else if (name.equals("float")) { + return prefix + "F"; + } else if (name.equals("double")) { + return prefix + "D"; + } else if (name.equals("byte")) { + return prefix + "B"; + } else if (name.equals("char")) { + return prefix + "C"; + } else if (name.equals("boolean")) { + return prefix + "Z"; + } else { + return prefix + "L" + name.replace('/', '.') + ";"; + } + } + return name.replace('/', '.'); + + } + + /*public void dup() { + mv.visitInsn(DUP); + }*/ + + public static void doReturn(MethodVisitor mv, ClassNode returnType) { + if (returnType == ClassHelper.double_TYPE) { + mv.visitInsn(DRETURN); + } else if (returnType == ClassHelper.float_TYPE) { + mv.visitInsn(FRETURN); + } else if (returnType == ClassHelper.long_TYPE) { + mv.visitInsn(LRETURN); + } else if ( + returnType == ClassHelper.boolean_TYPE + || returnType == ClassHelper.char_TYPE + || returnType == ClassHelper.byte_TYPE + || returnType == ClassHelper.int_TYPE + || returnType == ClassHelper.short_TYPE) { + //byte,short,boolean,int are all IRETURN + mv.visitInsn(IRETURN); + } else if (returnType == ClassHelper.VOID_TYPE) { + mv.visitInsn(RETURN); + } else { + mv.visitInsn(ARETURN); + } + + } + + private static boolean hasGenerics(Parameter[] param) { + if (param.length == 0) return false; + for (int i = 0; i < param.length; i++) { + ClassNode type = param[i].getType(); + if (hasGenerics(type)) return true; + } + return false; + } + + private static boolean hasGenerics(ClassNode type) { + return type.isArray() ? hasGenerics(type.getComponentType()) : type.getGenericsTypes() != null; + } + + public static String getGenericsMethodSignature(MethodNode node) { + GenericsType[] generics = node.getGenericsTypes(); + Parameter[] param = node.getParameters(); + ClassNode returnType = node.getReturnType(); + + if (generics == null && !hasGenerics(param) && !hasGenerics(returnType)) return null; + + StringBuilder ret = new StringBuilder(100); + getGenericsTypeSpec(ret, generics); + + GenericsType[] paramTypes = new GenericsType[param.length]; + for (int i = 0; i < param.length; i++) { + ClassNode pType = param[i].getType(); + if (pType.getGenericsTypes() == null || !pType.isGenericsPlaceHolder()) { + paramTypes[i] = new GenericsType(pType); + } else { + paramTypes[i] = pType.getGenericsTypes()[0]; + } + } + addSubTypes(ret, paramTypes, "(", ")"); + addSubTypes(ret, new GenericsType[]{new GenericsType(returnType)}, "", ""); + return ret.toString(); + } + + private static boolean usesGenericsInClassSignature(ClassNode node) { + if (!node.isUsingGenerics()) return false; + if (hasGenerics(node)) return true; + ClassNode sclass = node.getUnresolvedSuperClass(false); + if (sclass.isUsingGenerics()) return true; + ClassNode[] interfaces = node.getInterfaces(); + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + if (interfaces[i].isUsingGenerics()) return true; + } + } + + return false; + } + + public static String getGenericsSignature(ClassNode node) { + if (!usesGenericsInClassSignature(node)) return null; + GenericsType[] genericsTypes = node.getGenericsTypes(); + StringBuilder ret = new StringBuilder(100); + getGenericsTypeSpec(ret, genericsTypes); + GenericsType extendsPart = new GenericsType(node.getUnresolvedSuperClass(false)); + writeGenericsBounds(ret, extendsPart, true); + ClassNode[] interfaces = node.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + GenericsType interfacePart = new GenericsType(interfaces[i]); + writeGenericsBounds(ret, interfacePart, false); + } + return ret.toString(); + } + + private static void getGenericsTypeSpec(StringBuilder ret, GenericsType[] genericsTypes) { + if (genericsTypes == null) return; + ret.append('<'); + for (int i = 0; i < genericsTypes.length; i++) { + String name = genericsTypes[i].getName(); + ret.append(name); + ret.append(':'); + writeGenericsBounds(ret, genericsTypes[i], true); + } + ret.append('>'); + } + + public static String getGenericsBounds(ClassNode type) { + GenericsType[] genericsTypes = type.getGenericsTypes(); + if (genericsTypes == null) return null; + StringBuilder ret = new StringBuilder(100); + if (type.isGenericsPlaceHolder()) { + addSubTypes(ret, type.getGenericsTypes(), "", ""); + } else { + GenericsType gt = new GenericsType(type); + writeGenericsBounds(ret, gt, false); + } + + return ret.toString(); + } + + private static void writeGenericsBoundType(StringBuilder ret, ClassNode printType, boolean writeInterfaceMarker) { + if (writeInterfaceMarker && printType.isInterface()) ret.append(":"); + if (printType.isGenericsPlaceHolder() && printType.getGenericsTypes()!=null) { + ret.append("T"); + ret.append(printType.getGenericsTypes()[0].getName()); + ret.append(";"); + } + else { + ret.append(getTypeDescription(printType, false)); + addSubTypes(ret, printType.getGenericsTypes(), "<", ">"); + if (!ClassHelper.isPrimitiveType(printType)) ret.append(";"); + } + } + + private static void writeGenericsBounds(StringBuilder ret, GenericsType type, boolean writeInterfaceMarker) { + if (type.getUpperBounds() != null) { + ClassNode[] bounds = type.getUpperBounds(); + for (int i = 0; i < bounds.length; i++) { + writeGenericsBoundType(ret, bounds[i], writeInterfaceMarker); + } + } else if (type.getLowerBound() != null) { + writeGenericsBoundType(ret, type.getLowerBound(), writeInterfaceMarker); + } else { + writeGenericsBoundType(ret, type.getType(), writeInterfaceMarker); + } + } + + private static void addSubTypes(StringBuilder ret, GenericsType[] types, String start, String end) { + if (types == null) return; + ret.append(start); + for (int i = 0; i < types.length; i++) { + if (types[i].getType().isArray()) { + ret.append("["); + addSubTypes(ret, new GenericsType[]{new GenericsType(types[i].getType().getComponentType())}, "", ""); + } + else { + if (types[i].isPlaceholder()) { + ret.append('T'); + String name = types[i].getName(); + ret.append(name); + ret.append(';'); + } else if (types[i].isWildcard()) { + if (types[i].getUpperBounds() != null) { + ret.append('+'); + writeGenericsBounds(ret, types[i], false); + } else if (types[i].getLowerBound() != null) { + ret.append('-'); + writeGenericsBounds(ret, types[i], false); + } else { + ret.append('*'); + } + } else { + writeGenericsBounds(ret, types[i], false); + } + } + } + ret.append(end); + } + + public static void load(MethodVisitor mv, ClassNode type, int idx) { + if (type == ClassHelper.double_TYPE) { + mv.visitVarInsn(DLOAD, idx); + } else if (type == ClassHelper.float_TYPE) { + mv.visitVarInsn(FLOAD, idx); + } else if (type == ClassHelper.long_TYPE) { + mv.visitVarInsn(LLOAD, 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(ILOAD, idx); + } else { + mv.visitVarInsn(ALOAD, idx); + } + } + + + public static void doCast(MethodVisitor mv, ClassNode type) { + if (type == ClassHelper.OBJECT_TYPE) return; + if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { + unbox(mv, type); + } else { + mv.visitTypeInsn( + CHECKCAST, + type.isArray() ? + BytecodeHelper.getTypeDescription(type) : + BytecodeHelper.getClassInternalName(type.getName())); + } + } + + /** + * Given a wrapped number type (Byte, Integer, Short, ...), generates bytecode + * to convert it to a primitive number (int, long, double) using calls to + * wrapped.[targetType]Value() + * @param mv method visitor + * @param sourceType the wrapped number type + * @param targetType the primitive target type + */ + public static void doCastToPrimitive(MethodVisitor mv, ClassNode sourceType, ClassNode targetType) { + mv.visitMethodInsn(INVOKEVIRTUAL, BytecodeHelper.getClassInternalName(sourceType), targetType.getName() + "Value", "()" + BytecodeHelper.getTypeDescription(targetType), false); + } + + /** + * Given a primitive number type (byte, integer, short, ...), generates bytecode + * to convert it to a wrapped number (Integer, Long, Double) using calls to + * [WrappedType].valueOf + * @param mv method visitor + * @param sourceType the primitive number type + * @param targetType the wrapped target type + */ + public static void doCastToWrappedType(MethodVisitor mv, ClassNode sourceType, ClassNode targetType) { + mv.visitMethodInsn(INVOKESTATIC, getClassInternalName(targetType), "valueOf", "(" + getTypeDescription(sourceType) + ")" + getTypeDescription(targetType), false); + } + + public static void doCast(MethodVisitor mv, Class type) { + if (type == Object.class) return; + if (type.isPrimitive() && type != Void.TYPE) { + unbox(mv, type); + } else { + mv.visitTypeInsn( + CHECKCAST, + type.isArray() ? + BytecodeHelper.getTypeDescription(type) : + BytecodeHelper.getClassInternalName(type.getName())); + } + } + + /** + * Generates the bytecode to unbox the current value on the stack + */ + public static void unbox(MethodVisitor mv, Class type) { + if (type.isPrimitive() && type != Void.TYPE) { + String returnString = "(Ljava/lang/Object;)" + BytecodeHelper.getTypeDescription(type); + mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, type.getName() + "Unbox", returnString, false); + } + } + + public static void unbox(MethodVisitor mv, ClassNode type) { + if (type.isPrimaryClassNode()) return; + unbox(mv, type.getTypeClass()); + } + + /** + * box top level operand + */ + @Deprecated + public static boolean box(MethodVisitor mv, ClassNode type) { + if (type.isPrimaryClassNode()) return false; + return box(mv, type.getTypeClass()); + } + + + /** + * Generates the bytecode to autobox the current value on the stack + */ + @Deprecated + public static boolean box(MethodVisitor mv, Class type) { + if (ReflectionCache.getCachedClass(type).isPrimitive && type != void.class) { + String returnString = "(" + BytecodeHelper.getTypeDescription(type) + ")Ljava/lang/Object;"; + mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, "box", returnString, false); + return true; + } + return false; + } + + /** + * Visits a class literal. If the type of the classnode is a primitive type, + * the generated bytecode will be a GETSTATIC Integer.TYPE. + * If the classnode is not a primitive type, we will generate a LDC instruction. + */ + public static void visitClassLiteral(MethodVisitor mv, ClassNode classNode) { + if (ClassHelper.isPrimitiveType(classNode)) { + mv.visitFieldInsn( + GETSTATIC, + getClassInternalName(ClassHelper.getWrapper(classNode)), + "TYPE", + "Ljava/lang/Class;"); + } else { + mv.visitLdcInsn(org.objectweb.asm.Type.getType(getTypeDescription(classNode))); + } + } + + /** + * Tells if a class node is candidate for class literal bytecode optimization. If so, + * bytecode may use LDC instructions instead of static constant Class fields to retrieve + * class literals. + * @param classNode the classnode for which we want to know if bytecode optimization is possible + * @return true if the bytecode can be optimized + */ + public static boolean isClassLiteralPossible(ClassNode classNode) { + // the current implementation only checks for public modifier, because Groovy used to allow + // handles on classes even if they are package protected and not in the same package. + // There are situations where we could make more fine grained checks, but be careful of + // potential breakage of existing code. + return Modifier.isPublic(classNode.getModifiers()); + } + + /** + * Returns true if the two classes share the same compilation unit. + * @param a class a + * @param b class b + * @return true if both classes share the same compilation unit + */ + public static boolean isSameCompilationUnit(ClassNode a, ClassNode b) { + CompileUnit cu1 = a.getCompileUnit(); + CompileUnit cu2 = b.getCompileUnit(); + return cu1 !=null && cu2 !=null && cu1==cu2; + } + + /** + * Computes a hash code for a string. The purpose of this hashcode is to be constant independently of + * the JDK being used. + * @param str the string for which to compute the hashcode + * @return hashcode of the string + */ + public static int hashCode(String str) { + final char[] chars = str.toCharArray(); + int h = 0; + for (int i = 0; i < chars.length; i++) { + h = 31 * h + chars[i]; + } + return h; + } + + /** + * Converts a primitive type to boolean. + * + * @param mv method visitor + * @param type primitive type to convert + */ + public static void convertPrimitiveToBoolean(MethodVisitor mv, ClassNode type) { + if (type == ClassHelper.boolean_TYPE) { + return; + } + // Special handling is done for floating point types in order to + // handle checking for 0 or NaN values. + if (type == ClassHelper.double_TYPE) { + convertDoubleToBoolean(mv, type); + return; + } else if (type == ClassHelper.float_TYPE) { + convertFloatToBoolean(mv, type); + return; + } + Label trueLabel = new Label(); + Label falseLabel = new Label(); + // Convert long to int for IFEQ comparison using LCMP + if (type==ClassHelper.long_TYPE) { + mv.visitInsn(LCONST_0); + mv.visitInsn(LCMP); + } + // This handles byte, short, char and int + mv.visitJumpInsn(IFEQ, falseLabel); + mv.visitInsn(ICONST_1); + mv.visitJumpInsn(GOTO, trueLabel); + mv.visitLabel(falseLabel); + mv.visitInsn(ICONST_0); + mv.visitLabel(trueLabel); + } + + private static void convertDoubleToBoolean(MethodVisitor mv, ClassNode type) { + Label trueLabel = new Label(); + Label falseLabel = new Label(); + Label falseLabelWithPop = new Label(); + mv.visitInsn(DUP2); // will need the extra for isNaN call if required + mv.visitInsn(DCONST_0); + mv.visitInsn(DCMPL); + mv.visitJumpInsn(IFEQ, falseLabelWithPop); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "isNaN", "(D)Z", false); + mv.visitJumpInsn(IFNE, falseLabel); + mv.visitInsn(ICONST_1); + mv.visitJumpInsn(GOTO, trueLabel); + mv.visitLabel(falseLabelWithPop); + mv.visitInsn(POP2); + mv.visitLabel(falseLabel); + mv.visitInsn(ICONST_0); + mv.visitLabel(trueLabel); + } + + private static void convertFloatToBoolean(MethodVisitor mv, ClassNode type) { + Label trueLabel = new Label(); + Label falseLabel = new Label(); + Label falseLabelWithPop = new Label(); + mv.visitInsn(DUP); // will need the extra for isNaN call if required + mv.visitInsn(FCONST_0); + mv.visitInsn(FCMPL); + mv.visitJumpInsn(IFEQ, falseLabelWithPop); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "isNaN", "(F)Z", false); + mv.visitJumpInsn(IFNE, falseLabel); + mv.visitInsn(ICONST_1); + mv.visitJumpInsn(GOTO, trueLabel); + mv.visitLabel(falseLabelWithPop); + mv.visitInsn(POP); + mv.visitLabel(falseLabel); + mv.visitInsn(ICONST_0); + mv.visitLabel(trueLabel); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java new file mode 100644 index 0000000..1f34bc3 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java @@ -0,0 +1,124 @@ +/* + * 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.objectweb.asm.Label; + +/** + * Represents compile time variable metadata while compiling a method. + * + * @author <a href="mailto:[email protected]">James Strachan</a> + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + */ +public class BytecodeVariable { + + public static final BytecodeVariable THIS_VARIABLE = new BytecodeVariable(); + public static final BytecodeVariable SUPER_VARIABLE = new BytecodeVariable(); + + private final int index; + private ClassNode type; + private String name; + private final int prevCurrent; + private boolean holder; + + // br for setting on the LocalVariableTable in the class file + // these fields should probably go to jvm Operand class + private Label startLabel = null; + private Label endLabel = null; + private boolean dynamicTyped; + + private BytecodeVariable(){ + dynamicTyped = true; + index=0; + holder=false; + prevCurrent=0; + } + + public BytecodeVariable(int index, ClassNode type, String name, int prevCurrent) { + this.index = index; + this.type = type; + this.name = name; + this.prevCurrent = prevCurrent; + } + + public String getName() { + return name; + } + + public ClassNode getType() { + return type; + } + + /** + * @return the stack index for this variable + */ + public int getIndex() { + return index; + } + + /** + * @return is this local variable shared in other scopes (and so must use a ValueHolder) + */ + public boolean isHolder() { + return holder; + } + + public void setHolder(boolean holder) { + this.holder = holder; + } + + public Label getStartLabel() { + return startLabel; + } + + public void setStartLabel(Label startLabel) { + this.startLabel = startLabel; + } + + public Label getEndLabel() { + return endLabel; + } + + public void setEndLabel(Label endLabel) { + this.endLabel = endLabel; + } + + public String toString() { + return name + "(index=" + index + ",type=" + type + ",holder="+holder+")"; + } + + public void setType(ClassNode type) { + this.type = type; + dynamicTyped |= type==ClassHelper.DYNAMIC_TYPE; + } + + public void setDynamicTyped(boolean b) { + dynamicTyped = b; + } + + public boolean isDynamicTyped() { + return dynamicTyped; + } + + public int getPrevIndex() { + return prevCurrent; + } +}
