http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java new file mode 100644 index 0000000..45d1908 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java @@ -0,0 +1,638 @@ +/* + * 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.Parameter; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.BooleanExpression; +import org.codehaus.groovy.ast.expr.ClosureListExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.EmptyExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.stmt.AssertStatement; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.BreakStatement; +import org.codehaus.groovy.ast.stmt.CaseStatement; +import org.codehaus.groovy.ast.stmt.CatchStatement; +import org.codehaus.groovy.ast.stmt.ContinueStatement; +import org.codehaus.groovy.ast.stmt.DoWhileStatement; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +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.SwitchStatement; +import org.codehaus.groovy.ast.stmt.SynchronizedStatement; +import org.codehaus.groovy.ast.stmt.ThrowStatement; +import org.codehaus.groovy.ast.stmt.TryCatchStatement; +import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.classgen.asm.CompileStack.BlockRecorder; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import java.util.Iterator; +import java.util.List; + +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ATHROW; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.IFEQ; +import static org.objectweb.asm.Opcodes.MONITORENTER; +import static org.objectweb.asm.Opcodes.MONITOREXIT; +import static org.objectweb.asm.Opcodes.NOP; +import static org.objectweb.asm.Opcodes.RETURN; + +public class StatementWriter { + // iterator + private static final MethodCaller iteratorNextMethod = MethodCaller.newInterface(Iterator.class, "next"); + private static final MethodCaller iteratorHasNextMethod = MethodCaller.newInterface(Iterator.class, "hasNext"); + + private final WriterController controller; + public StatementWriter(WriterController controller) { + this.controller = controller; + } + + protected void writeStatementLabel(Statement statement) { + String name = statement.getStatementLabel(); + if (name != null) { + Label label = controller.getCompileStack().createLocalLabel(name); + controller.getMethodVisitor().visitLabel(label); + } + } + + public void writeBlockStatement(BlockStatement block) { + CompileStack compileStack = controller.getCompileStack(); + + //GROOVY-4505 use no line number information for the block + writeStatementLabel(block); + + int mark = controller.getOperandStack().getStackLength(); + compileStack.pushVariableScope(block.getVariableScope()); + for (Statement statement : block.getStatements()) { + statement.visit(controller.getAcg()); + } + compileStack.pop(); + + controller.getOperandStack().popDownTo(mark); + } + + public void writeForStatement(ForStatement loop) { + Parameter loopVar = loop.getVariable(); + if (loopVar == ForStatement.FOR_LOOP_DUMMY) { + writeForLoopWithClosureList(loop); + } else { + writeForInLoop(loop); + } + } + + protected void writeIteratorHasNext(MethodVisitor mv) { + iteratorHasNextMethod.call(mv); + } + + protected void writeIteratorNext(MethodVisitor mv) { + iteratorNextMethod.call(mv); + } + + protected void writeForInLoop(ForStatement loop) { + controller.getAcg().onLineNumber(loop,"visitForLoop"); + writeStatementLabel(loop); + + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + + compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabels()); + + // Declare the loop counter. + BytecodeVariable variable = compileStack.defineVariable(loop.getVariable(), false); + + // Then get the iterator and generate the loop control + MethodCallExpression iterator = new MethodCallExpression(loop.getCollectionExpression(), "iterator", new ArgumentListExpression()); + iterator.visit(controller.getAcg()); + operandStack.doGroovyCast(ClassHelper.Iterator_TYPE); + + final int iteratorIdx = compileStack.defineTemporaryVariable("iterator", ClassHelper.Iterator_TYPE, true); + + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + mv.visitLabel(continueLabel); + mv.visitVarInsn(ALOAD, iteratorIdx); + writeIteratorHasNext(mv); + // note: ifeq tests for ==0, a boolean is 0 if it is false + mv.visitJumpInsn(IFEQ, breakLabel); + + mv.visitVarInsn(ALOAD, iteratorIdx); + writeIteratorNext(mv); + operandStack.push(ClassHelper.OBJECT_TYPE); + operandStack.storeVar(variable); + + // Generate the loop body + loop.getLoopBlock().visit(controller.getAcg()); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + + compileStack.removeVar(iteratorIdx); + compileStack.pop(); + } + + private void visitExpressionOfLoopStatement(Expression expression) { + if (expression instanceof ClosureListExpression) { + for (Expression e : ((ClosureListExpression) expression).getExpressions()) { + visitExpressionOrStatement(e); + } + } else { + visitExpressionOrStatement(expression); + } + } + + protected void writeForLoopWithClosureList(ForStatement loop) { + controller.getAcg().onLineNumber(loop,"visitForLoop"); + writeStatementLabel(loop); + + MethodVisitor mv = controller.getMethodVisitor(); + controller.getCompileStack().pushLoop(loop.getVariableScope(), loop.getStatementLabels()); + + ClosureListExpression clExpr = (ClosureListExpression) loop.getCollectionExpression(); + controller.getCompileStack().pushVariableScope(clExpr.getVariableScope()); + + List<Expression> expressions = clExpr.getExpressions(); + int size = expressions.size(); + + // middle element is condition, lower half is init, higher half is increment + int condIndex = (size - 1) / 2; + + // visit init + for (int i = 0; i < condIndex; i++) { + visitExpressionOfLoopStatement(expressions.get(i)); + } + + Label continueLabel = controller.getCompileStack().getContinueLabel(); + Label breakLabel = controller.getCompileStack().getBreakLabel(); + + Label cond = new Label(); + mv.visitLabel(cond); + // visit condition leave boolean on stack + { + Expression condExpr = expressions.get(condIndex); + int mark = controller.getOperandStack().getStackLength(); + condExpr.visit(controller.getAcg()); + controller.getOperandStack().castToBool(mark,true); + } + // jump if we don't want to continue + // note: ifeq tests for ==0, a boolean is 0 if it is false + controller.getOperandStack().jump(IFEQ, breakLabel); + + // Generate the loop body + loop.getLoopBlock().visit(controller.getAcg()); + + // visit increment + mv.visitLabel(continueLabel); + for (int i = condIndex + 1; i < size; i++) { + visitExpressionOfLoopStatement(expressions.get(i)); + } + + // jump to test the condition again + mv.visitJumpInsn(GOTO, cond); + + // loop end + mv.visitLabel(breakLabel); + + controller.getCompileStack().pop(); + controller.getCompileStack().pop(); + } + + private void visitExpressionOrStatement(Object o) { + if (o == EmptyExpression.INSTANCE) return; + if (o instanceof Expression) { + Expression expr = (Expression) o; + int mark = controller.getOperandStack().getStackLength(); + expr.visit(controller.getAcg()); + controller.getOperandStack().popDownTo(mark); + } else { + ((Statement) o).visit(controller.getAcg()); + } + } + + private void visitConditionOfLoopingStatement(BooleanExpression bool, Label breakLabel, MethodVisitor mv) { + boolean boolHandled = false; + if (bool.getExpression() instanceof ConstantExpression) { + ConstantExpression constant = (ConstantExpression) bool.getExpression(); + if (constant.getValue()==Boolean.TRUE) { + boolHandled = true; + // do nothing + } else if (constant.getValue()==Boolean.FALSE) { + boolHandled = true; + mv.visitJumpInsn(GOTO, breakLabel); + } + } + + if(!boolHandled) { + bool.visit(controller.getAcg()); + controller.getOperandStack().jump(IFEQ, breakLabel); + } + } + + public void writeWhileLoop(WhileStatement loop) { + controller.getAcg().onLineNumber(loop,"visitWhileLoop"); + writeStatementLabel(loop); + + MethodVisitor mv = controller.getMethodVisitor(); + + controller.getCompileStack().pushLoop(loop.getStatementLabels()); + Label continueLabel = controller.getCompileStack().getContinueLabel(); + Label breakLabel = controller.getCompileStack().getBreakLabel(); + + mv.visitLabel(continueLabel); + + this.visitConditionOfLoopingStatement(loop.getBooleanExpression(), breakLabel, mv); + loop.getLoopBlock().visit(controller.getAcg()); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + + controller.getCompileStack().pop(); + } + + public void writeDoWhileLoop(DoWhileStatement loop) { + controller.getAcg().onLineNumber(loop,"visitDoWhileLoop"); + writeStatementLabel(loop); + + MethodVisitor mv = controller.getMethodVisitor(); + + controller.getCompileStack().pushLoop(loop.getStatementLabels()); + Label continueLabel = controller.getCompileStack().getContinueLabel(); + Label breakLabel = controller.getCompileStack().getBreakLabel(); + + mv.visitLabel(continueLabel); + + loop.getLoopBlock().visit(controller.getAcg()); + this.visitConditionOfLoopingStatement(loop.getBooleanExpression(), breakLabel, mv); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + + controller.getCompileStack().pop(); + } + + public void writeIfElse(IfStatement ifElse) { + controller.getAcg().onLineNumber(ifElse,"visitIfElse"); + writeStatementLabel(ifElse); + + MethodVisitor mv = controller.getMethodVisitor(); + + ifElse.getBooleanExpression().visit(controller.getAcg()); + Label l0 = controller.getOperandStack().jump(IFEQ); + + // if-else is here handled as a special version + // of a boolean expression + controller.getCompileStack().pushBooleanExpression(); + ifElse.getIfBlock().visit(controller.getAcg()); + controller.getCompileStack().pop(); + + if (ifElse.getElseBlock()==EmptyStatement.INSTANCE) { + mv.visitLabel(l0); + } else { + Label l1 = new Label(); + mv.visitJumpInsn(GOTO, l1); + mv.visitLabel(l0); + + controller.getCompileStack().pushBooleanExpression(); + ifElse.getElseBlock().visit(controller.getAcg()); + controller.getCompileStack().pop(); + + mv.visitLabel(l1); + } + } + + public void writeTryCatchFinally(TryCatchStatement statement) { + controller.getAcg().onLineNumber(statement, "visitTryCatchFinally"); + writeStatementLabel(statement); + + MethodVisitor mv = controller.getMethodVisitor(); + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + + Statement tryStatement = statement.getTryStatement(); + final Statement finallyStatement = statement.getFinallyStatement(); + + // start try block, label needed for exception table + Label tryStart = new Label(); + mv.visitLabel(tryStart); + BlockRecorder tryBlock = makeBlockRecorder(finallyStatement); + tryBlock.startRange(tryStart); + + tryStatement.visit(controller.getAcg()); + + // goto finally part + Label finallyStart = new Label(); + mv.visitJumpInsn(GOTO, finallyStart); + + Label tryEnd = new Label(); + mv.visitLabel(tryEnd); + tryBlock.closeRange(tryEnd); + // pop for "makeBlockRecorder(finallyStatement)" + controller.getCompileStack().pop(); + + BlockRecorder catches = makeBlockRecorder(finallyStatement); + for (CatchStatement catchStatement : statement.getCatchStatements()) { + ClassNode exceptionType = catchStatement.getExceptionType(); + String exceptionTypeInternalName = BytecodeHelper.getClassInternalName(exceptionType); + + // start catch block, label needed for exception table + Label catchStart = new Label(); + mv.visitLabel(catchStart); + catches.startRange(catchStart); + + // create exception variable and store the exception + Parameter exceptionVariable = catchStatement.getVariable(); + compileStack.pushState(); + compileStack.defineVariable(exceptionVariable, true); + // handle catch body + catchStatement.visit(controller.getAcg()); + // place holder to avoid problems with empty catch blocks + mv.visitInsn(NOP); + // pop for the variable + controller.getCompileStack().pop(); + + // end of catch + Label catchEnd = new Label(); + mv.visitLabel(catchEnd); + catches.closeRange(catchEnd); + + // goto finally start + mv.visitJumpInsn(GOTO, finallyStart); + compileStack.writeExceptionTable(tryBlock, catchStart, exceptionTypeInternalName); + } + + // Label used to handle exceptions in catches and regularly + // visited finals. + Label catchAny = new Label(); + + // add "catch any" block to exception table for try part we do this + // after the exception blocks, because else this one would supersede + // any of those otherwise + compileStack.writeExceptionTable(tryBlock, catchAny, null); + // same for the catch parts + compileStack.writeExceptionTable(catches, catchAny, null); + + // pop for "makeBlockRecorder(catches)" + compileStack.pop(); + + // start finally + mv.visitLabel(finallyStart); + finallyStatement.visit(controller.getAcg()); + mv.visitInsn(NOP); //** + + // goto after all-catching block + Label skipCatchAll = new Label(); + mv.visitJumpInsn(GOTO, skipCatchAll); + + // start a block catching any Exception + mv.visitLabel(catchAny); + //store exception + //TODO: maybe define a Throwable and use it here instead of Object + operandStack.push(ClassHelper.OBJECT_TYPE); + final int anyExceptionIndex = compileStack.defineTemporaryVariable("exception", true); + + finallyStatement.visit(controller.getAcg()); + + // load the exception and rethrow it + mv.visitVarInsn(ALOAD, anyExceptionIndex); + mv.visitInsn(ATHROW); + + mv.visitLabel(skipCatchAll); + compileStack.removeVar(anyExceptionIndex); + } + + private BlockRecorder makeBlockRecorder(final Statement finallyStatement) { + final BlockRecorder block = new BlockRecorder(); + Runnable tryRunner = new Runnable() { + public void run() { + controller.getCompileStack().pushBlockRecorderVisit(block); + finallyStatement.visit(controller.getAcg()); + controller.getCompileStack().popBlockRecorderVisit(block); + } + }; + block.excludedStatement = tryRunner; + controller.getCompileStack().pushBlockRecorder(block); + return block; + } + + public void writeSwitch(SwitchStatement statement) { + controller.getAcg().onLineNumber(statement, "visitSwitch"); + writeStatementLabel(statement); + + statement.getExpression().visit(controller.getAcg()); + + // switch does not have a continue label. use its parent's for continue + Label breakLabel = controller.getCompileStack().pushSwitch(); + + final int switchVariableIndex = controller.getCompileStack().defineTemporaryVariable("switch", true); + + List caseStatements = statement.getCaseStatements(); + int caseCount = caseStatements.size(); + Label[] labels = new Label[caseCount + 1]; + for (int i = 0; i < caseCount; i++) { + labels[i] = new Label(); + } + + int i = 0; + for (Iterator iter = caseStatements.iterator(); iter.hasNext(); i++) { + CaseStatement caseStatement = (CaseStatement) iter.next(); + writeCaseStatement(caseStatement, switchVariableIndex, labels[i], labels[i + 1]); + } + + statement.getDefaultStatement().visit(controller.getAcg()); + + controller.getMethodVisitor().visitLabel(breakLabel); + + controller.getCompileStack().removeVar(switchVariableIndex); + controller.getCompileStack().pop(); + } + + protected void writeCaseStatement( + CaseStatement statement, int switchVariableIndex, + Label thisLabel, Label nextLabel) + { + controller.getAcg().onLineNumber(statement, "visitCaseStatement"); + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + + mv.visitVarInsn(ALOAD, switchVariableIndex); + + statement.getExpression().visit(controller.getAcg()); + operandStack.box(); + controller.getBinaryExpressionHelper().getIsCaseMethod().call(mv); + operandStack.replace(ClassHelper.boolean_TYPE); + + Label l0 = controller.getOperandStack().jump(IFEQ); + + mv.visitLabel(thisLabel); + + statement.getCode().visit(controller.getAcg()); + + // now if we don't finish with a break we need to jump past + // the next comparison + if (nextLabel != null) { + mv.visitJumpInsn(GOTO, nextLabel); + } + + mv.visitLabel(l0); + } + + public void writeBreak(BreakStatement statement) { + controller.getAcg().onLineNumber(statement, "visitBreakStatement"); + writeStatementLabel(statement); + + String name = statement.getLabel(); + Label breakLabel = controller.getCompileStack().getNamedBreakLabel(name); + controller.getCompileStack().applyFinallyBlocks(breakLabel, true); + + controller.getMethodVisitor().visitJumpInsn(GOTO, breakLabel); + } + + public void writeContinue(ContinueStatement statement) { + controller.getAcg().onLineNumber(statement, "visitContinueStatement"); + writeStatementLabel(statement); + + String name = statement.getLabel(); + Label continueLabel = controller.getCompileStack().getContinueLabel(); + if (name != null) continueLabel = controller.getCompileStack().getNamedContinueLabel(name); + controller.getCompileStack().applyFinallyBlocks(continueLabel, false); + controller.getMethodVisitor().visitJumpInsn(GOTO, continueLabel); + } + + public void writeSynchronized(SynchronizedStatement statement) { + controller.getAcg().onLineNumber(statement, "visitSynchronizedStatement"); + writeStatementLabel(statement); + final MethodVisitor mv = controller.getMethodVisitor(); + CompileStack compileStack = controller.getCompileStack(); + + statement.getExpression().visit(controller.getAcg()); + controller.getOperandStack().box(); + final int index = compileStack.defineTemporaryVariable("synchronized", ClassHelper.OBJECT_TYPE, true); + + final Label synchronizedStart = new Label(); + final Label synchronizedEnd = new Label(); + final Label catchAll = new Label(); + + mv.visitVarInsn(ALOAD, index); + mv.visitInsn(MONITORENTER); + mv.visitLabel(synchronizedStart); + // place holder for "empty" synchronized blocks, for example + // if there is only a break/continue. + mv.visitInsn(NOP); + + Runnable finallyPart = new Runnable() { + public void run() { + mv.visitVarInsn(ALOAD, index); + mv.visitInsn(MONITOREXIT); + } + }; + BlockRecorder fb = new BlockRecorder(finallyPart); + fb.startRange(synchronizedStart); + compileStack.pushBlockRecorder(fb); + statement.getCode().visit(controller.getAcg()); + + fb.closeRange(catchAll); + compileStack.writeExceptionTable(fb, catchAll, null); + compileStack.pop(); //pop fb + + finallyPart.run(); + mv.visitJumpInsn(GOTO, synchronizedEnd); + mv.visitLabel(catchAll); + finallyPart.run(); + mv.visitInsn(ATHROW); + + mv.visitLabel(synchronizedEnd); + compileStack.removeVar(index); + } + + public void writeAssert(AssertStatement statement) { + controller.getAcg().onLineNumber(statement, "visitAssertStatement"); + writeStatementLabel(statement); + controller.getAssertionWriter().writeAssertStatement(statement); + } + + public void writeThrow(ThrowStatement statement) { + controller.getAcg().onLineNumber(statement, "visitThrowStatement"); + writeStatementLabel(statement); + MethodVisitor mv = controller.getMethodVisitor(); + + statement.getExpression().visit(controller.getAcg()); + + // we should infer the type of the exception from the expression + mv.visitTypeInsn(CHECKCAST, "java/lang/Throwable"); + mv.visitInsn(ATHROW); + + controller.getOperandStack().remove(1); + } + + public void writeReturn(ReturnStatement statement) { + controller.getAcg().onLineNumber(statement, "visitReturnStatement"); + writeStatementLabel(statement); + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + ClassNode returnType = controller.getReturnType(); + + if (returnType == ClassHelper.VOID_TYPE) { + if (!(statement.isReturningNullOrVoid())) { + //TODO: move to Verifier + controller.getAcg().throwException("Cannot use return statement with an expression on a method that returns void"); + } + controller.getCompileStack().applyBlockRecorder(); + mv.visitInsn(RETURN); + return; + } + + Expression expression = statement.getExpression(); + expression.visit(controller.getAcg()); + + operandStack.doGroovyCast(returnType); + + if (controller.getCompileStack().hasBlockRecorder()) { + ClassNode type = operandStack.getTopOperand(); + int returnValueIdx = controller.getCompileStack().defineTemporaryVariable("returnValue", returnType, true); + controller.getCompileStack().applyBlockRecorder(); + operandStack.load(type, returnValueIdx); + controller.getCompileStack().removeVar(returnValueIdx); + } + + BytecodeHelper.doReturn(mv, returnType); + operandStack.remove(1); + } + + public void writeExpressionStatement(ExpressionStatement statement) { + controller.getAcg().onLineNumber(statement, "visitExpressionStatement: " + statement.getExpression().getClass().getName()); + writeStatementLabel(statement); + + Expression expression = statement.getExpression(); + + int mark = controller.getOperandStack().getStackLength(); + expression.visit(controller.getAcg()); + controller.getOperandStack().popDownTo(mark); + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/TypeChooser.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/TypeChooser.java b/src/main/java/org/codehaus/groovy/classgen/asm/TypeChooser.java new file mode 100644 index 0000000..ec552b3 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/TypeChooser.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.classgen.asm; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.expr.Expression; + +/** + * Interface for modules which are capable of resolving the type of an expression. + * Several implementations are available, depending on whether you are in a dynamic + * or static compilation mode. + * + * @author Cedric Champeau + */ +public interface TypeChooser { + + /** + * Resolve the type of an expression. Depending on the implementations, the + * returned type may be the declared type or an inferred type. + * @param expression the expression for which the type must be returned. + * @param classNode the classnode this expression belongs to + * @return the resolved type. + */ + ClassNode resolveType(final Expression expression, ClassNode classNode); + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/UnaryExpressionHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/UnaryExpressionHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/UnaryExpressionHelper.java new file mode 100644 index 0000000..0ebd9b4 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/UnaryExpressionHelper.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.ast.ClassHelper; +import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.NotExpression; +import org.codehaus.groovy.ast.expr.UnaryMinusExpression; +import org.codehaus.groovy.ast.expr.UnaryPlusExpression; +import org.codehaus.groovy.runtime.ScriptBytecodeAdapter; + +/** + * A helper class used to generate bytecode for unary expressions. AST transformations willing to use + * a custom unary expression helper may set the {@link WriterControllerFactory} node metadata on a + * class node to provide a custom {@link WriterController} which would in turn use a custom expression + * helper. + * + * @see BinaryExpressionHelper + * + * @author Cedric Champeau + */ +public class UnaryExpressionHelper { + + // unary plus, unary minus, bitwise negation + static final MethodCaller unaryPlus = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "unaryPlus"); + static final MethodCaller unaryMinus = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "unaryMinus"); + static final MethodCaller bitwiseNegate = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "bitwiseNegate"); + + private final WriterController controller; + + public UnaryExpressionHelper(final WriterController controller) { + this.controller = controller; + } + + public void writeUnaryPlus(UnaryPlusExpression expression) { + Expression subExpression = expression.getExpression(); + subExpression.visit(controller.getAcg()); + controller.getOperandStack().box(); + unaryPlus.call(controller.getMethodVisitor()); + controller.getOperandStack().replace(ClassHelper.OBJECT_TYPE); + controller.getAssertionWriter().record(expression); + } + + public void writeUnaryMinus(UnaryMinusExpression expression) { + Expression subExpression = expression.getExpression(); + subExpression.visit(controller.getAcg()); + controller.getOperandStack().box(); + unaryMinus.call(controller.getMethodVisitor()); + controller.getOperandStack().replace(ClassHelper.OBJECT_TYPE); + controller.getAssertionWriter().record(expression); + } + + public void writeBitwiseNegate(BitwiseNegationExpression expression) { + Expression subExpression = expression.getExpression(); + subExpression.visit(controller.getAcg()); + controller.getOperandStack().box(); + bitwiseNegate.call(controller.getMethodVisitor()); + controller.getOperandStack().replace(ClassHelper.OBJECT_TYPE); + controller.getAssertionWriter().record(expression); + } + + public void writeNotExpression(NotExpression expression) { + Expression subExpression = expression.getExpression(); + int mark = controller.getOperandStack().getStackLength(); + subExpression.visit(controller.getAcg()); + controller.getOperandStack().castToBool(mark, true); + BytecodeHelper.negateBoolean(controller.getMethodVisitor()); + controller.getAssertionWriter().record(expression); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/VariableSlotLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/VariableSlotLoader.java b/src/main/java/org/codehaus/groovy/classgen/asm/VariableSlotLoader.java new file mode 100644 index 0000000..63e48ca --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/VariableSlotLoader.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.classgen.asm; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.classgen.BytecodeExpression; +import org.objectweb.asm.MethodVisitor; + +public class VariableSlotLoader extends BytecodeExpression { + + private final int idx; + private final OperandStack operandStack; + + public VariableSlotLoader(ClassNode type, int index, OperandStack os) { + super(type); + this.idx = index; + this.operandStack = os; + } + + public VariableSlotLoader(int index, OperandStack os) { + this.idx = index; + this.operandStack = os; + } + + @Override + public void visit(MethodVisitor mv) { + operandStack.load(this.getType(), idx); + operandStack.remove(1); + } + + public int getIndex(){ + return idx; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java b/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java new file mode 100644 index 0000000..952814c --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java @@ -0,0 +1,401 @@ +/* + * 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.InnerClassNode; +import org.codehaus.groovy.ast.InterfaceHelperClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.classgen.asm.indy.IndyBinHelper; +import org.codehaus.groovy.classgen.asm.indy.IndyCallSiteWriter; +import org.codehaus.groovy.classgen.asm.indy.InvokeDynamicWriter; +import org.codehaus.groovy.classgen.asm.util.LoggableClassVisitor; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.SourceUnit; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class WriterController { + private static final String GROOVY_LOG_CLASSGEN = "groovy.log.classgen"; + private static final boolean LOG_CLASSGEN; + static { + LOG_CLASSGEN = Boolean.valueOf(System.getProperty(GROOVY_LOG_CLASSGEN)); + } + private AsmClassGenerator acg; + private MethodVisitor methodVisitor; + private CompileStack compileStack; + private OperandStack operandStack; + private ClassNode classNode; + private CallSiteWriter callSiteWriter; + private ClassVisitor cv; + private ClosureWriter closureWriter; + private String internalClassName; + private InvocationWriter invocationWriter; + private BinaryExpressionHelper binaryExpHelper, fastPathBinaryExpHelper; + private UnaryExpressionHelper unaryExpressionHelper, fastPathUnaryExpressionHelper; + private AssertionWriter assertionWriter; + private String internalBaseClassName; + private ClassNode outermostClass; + private MethodNode methodNode; + private SourceUnit sourceUnit; + private ConstructorNode constructorNode; + private GeneratorContext context; + private InterfaceHelperClassNode interfaceClassLoadingClass; + public boolean optimizeForInt = true; + private StatementWriter statementWriter; + private boolean fastPath = false; + private TypeChooser typeChooser; + private int bytecodeVersion = Opcodes.V1_5; + private int lineNumber = -1; + private int helperMethodIndex = 0; + private List<String> superMethodNames = new ArrayList<String>(); + + public void init(AsmClassGenerator asmClassGenerator, GeneratorContext gcon, ClassVisitor cv, ClassNode cn) { + CompilerConfiguration config = cn.getCompileUnit().getConfig(); + Map<String,Boolean> optOptions = config.getOptimizationOptions(); + boolean invokedynamic=false; + if (optOptions.isEmpty()) { + // IGNORE + } else if (Boolean.FALSE.equals(optOptions.get("all"))) { + optimizeForInt=false; + // set other optimizations options to false here + } else { + if (Boolean.TRUE.equals(optOptions.get(CompilerConfiguration.INVOKEDYNAMIC))) invokedynamic=true; + if (Boolean.FALSE.equals(optOptions.get("int"))) optimizeForInt=false; + if (invokedynamic) optimizeForInt=false; + // set other optimizations options to false here + } + this.classNode = cn; + this.outermostClass = null; + this.internalClassName = BytecodeHelper.getClassInternalName(classNode); + + bytecodeVersion = chooseBytecodeVersion(invokedynamic, config.getTargetBytecode()); + + if (invokedynamic) { + this.invocationWriter = new InvokeDynamicWriter(this); + this.callSiteWriter = new IndyCallSiteWriter(this); + this.binaryExpHelper = new IndyBinHelper(this); + } else { + this.callSiteWriter = new CallSiteWriter(this); + this.invocationWriter = new InvocationWriter(this); + this.binaryExpHelper = new BinaryExpressionHelper(this); + } + + this.unaryExpressionHelper = new UnaryExpressionHelper(this); + if (optimizeForInt) { + this.fastPathBinaryExpHelper = new BinaryExpressionMultiTypeDispatcher(this); + // todo: replace with a real fast path unary expression helper when available + this.fastPathUnaryExpressionHelper = new UnaryExpressionHelper(this); + } else { + this.fastPathBinaryExpHelper = this.binaryExpHelper; + this.fastPathUnaryExpressionHelper = new UnaryExpressionHelper(this); + } + + this.operandStack = new OperandStack(this); + this.assertionWriter = new AssertionWriter(this); + this.closureWriter = new ClosureWriter(this); + this.internalBaseClassName = BytecodeHelper.getClassInternalName(classNode.getSuperClass()); + this.acg = asmClassGenerator; + this.sourceUnit = acg.getSourceUnit(); + this.context = gcon; + this.compileStack = new CompileStack(this); + this.cv = this.createClassVisitor(cv); + if (optimizeForInt) { + this.statementWriter = new OptimizingStatementWriter(this); + } else { + this.statementWriter = new StatementWriter(this); + } + this.typeChooser = new StatementMetaTypeChooser(); + } + + private ClassVisitor createClassVisitor(ClassVisitor cv) { + if (!LOG_CLASSGEN) { + return cv; + } + if (cv instanceof LoggableClassVisitor) { + return cv; + } + return new LoggableClassVisitor(cv); + } + private static int chooseBytecodeVersion(final boolean invokedynamic, final String targetBytecode) { + if (invokedynamic) { + if (CompilerConfiguration.JDK8.equals(targetBytecode)) { + return Opcodes.V1_8; + } + return Opcodes.V1_7; + } else { + Integer bytecodeVersion = CompilerConfiguration.JDK_TO_BYTECODE_VERSION_MAP.get(targetBytecode); + + if (null != bytecodeVersion) { + return bytecodeVersion; + } + } + + throw new GroovyBugError("Bytecode version ["+targetBytecode+"] is not supported by the compiler"); + } + + public AsmClassGenerator getAcg() { + return acg; + } + + public void setMethodVisitor(MethodVisitor methodVisitor) { + this.methodVisitor = methodVisitor; + } + + public MethodVisitor getMethodVisitor() { + return methodVisitor; + } + + public CompileStack getCompileStack() { + return compileStack; + } + + public OperandStack getOperandStack() { + return operandStack; + } + + public ClassNode getClassNode() { + return classNode; + } + + public CallSiteWriter getCallSiteWriter() { + return callSiteWriter; + } + + public ClassVisitor getClassVisitor() { + return cv; + } + + public ClosureWriter getClosureWriter() { + return closureWriter; + } + + public ClassVisitor getCv() { + return cv; + } + + public String getInternalClassName() { + return internalClassName; + } + + public InvocationWriter getInvocationWriter() { + return invocationWriter; + } + + public BinaryExpressionHelper getBinaryExpressionHelper() { + if (fastPath) { + return fastPathBinaryExpHelper; + } else { + return binaryExpHelper; + } + } + + public UnaryExpressionHelper getUnaryExpressionHelper() { + if (fastPath) { + return fastPathUnaryExpressionHelper; + } else { + return unaryExpressionHelper; + } + } + + public AssertionWriter getAssertionWriter() { + return assertionWriter; + } + + public TypeChooser getTypeChooser() { + return typeChooser; + } + + public String getInternalBaseClassName() { + return internalBaseClassName; + } + + public MethodNode getMethodNode() { + return methodNode; + } + + public void setMethodNode(MethodNode mn) { + methodNode = mn; + constructorNode = null; + } + + public ConstructorNode getConstructorNode(){ + return constructorNode; + } + + public void setConstructorNode(ConstructorNode cn) { + constructorNode = cn; + methodNode = null; + } + + public boolean isNotClinit() { + return methodNode == null || !methodNode.getName().equals("<clinit>"); + } + + public SourceUnit getSourceUnit() { + return sourceUnit; + } + + public boolean isStaticContext() { + if (compileStack!=null && compileStack.getScope()!=null) { + return compileStack.getScope().isInStaticContext(); + } + if (!isInClosure()) return false; + if (constructorNode != null) return false; + return classNode.isStaticClass() || methodNode.isStatic(); + } + + public boolean isInClosure() { + return classNode.getOuterClass() != null + && classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE; + } + + public boolean isInClosureConstructor() { + return constructorNode != null + && classNode.getOuterClass() != null + && classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE; + } + + public boolean isNotExplicitThisInClosure(boolean implicitThis) { + return implicitThis || !isInClosure(); + } + + + public boolean isStaticMethod() { + return methodNode != null && methodNode.isStatic(); + } + + public ClassNode getReturnType() { + if (methodNode != null) { + return methodNode.getReturnType(); + } else if (constructorNode != null) { + return constructorNode.getReturnType(); + } else { + throw new GroovyBugError("I spotted a return that is neither in a method nor in a constructor... I can not handle that"); + } + } + + public boolean isStaticConstructor() { + return methodNode != null && methodNode.getName().equals("<clinit>"); + } + + public boolean isConstructor() { + return constructorNode!=null; + } + + /** + * @return true if we are in a script body, where all variables declared are no longer + * local variables but are properties + */ + public boolean isInScriptBody() { + if (classNode.isScriptBody()) { + return true; + } else { + return classNode.isScript() && methodNode != null && methodNode.getName().equals("run"); + } + } + + public String getClassName() { + String className; + if (!classNode.isInterface() || interfaceClassLoadingClass == null) { + className = internalClassName; + } else { + className = BytecodeHelper.getClassInternalName(interfaceClassLoadingClass); + } + return className; + } + + public ClassNode getOutermostClass() { + if (outermostClass == null) { + outermostClass = classNode; + while (outermostClass instanceof InnerClassNode) { + outermostClass = outermostClass.getOuterClass(); + } + } + return outermostClass; + } + + public GeneratorContext getContext() { + return context; + } + + public void setInterfaceClassLoadingClass(InterfaceHelperClassNode ihc) { + interfaceClassLoadingClass = ihc; + } + + public InterfaceHelperClassNode getInterfaceClassLoadingClass() { + return interfaceClassLoadingClass; + } + + public boolean shouldOptimizeForInt() { + return optimizeForInt; + } + + public StatementWriter getStatementWriter() { + return statementWriter; + } + + public void switchToFastPath() { + fastPath = true; + resetLineNumber(); + } + + public void switchToSlowPath() { + fastPath = false; + resetLineNumber(); + } + + public boolean isFastPath() { + return fastPath; + } + + public int getBytecodeVersion() { + return bytecodeVersion; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int n) { + lineNumber = n; + } + + public void resetLineNumber() { + setLineNumber(-1); + } + + public int getNextHelperMethodIndex() { + return helperMethodIndex++; + } + + public List<String> getSuperMethodNames() { + return superMethodNames; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/WriterControllerFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/WriterControllerFactory.java b/src/main/java/org/codehaus/groovy/classgen/asm/WriterControllerFactory.java new file mode 100644 index 0000000..a47f309 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/WriterControllerFactory.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * A non static factory to get alternative writer controller to be stored in the meta data + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public interface WriterControllerFactory { + WriterController makeController(WriterController normalController); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyBinHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyBinHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyBinHelper.java new file mode 100644 index 0000000..68ec884 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyBinHelper.java @@ -0,0 +1,44 @@ +/* + * 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.indy; + +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.EmptyExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.classgen.asm.BinaryExpressionHelper; +import org.codehaus.groovy.classgen.asm.InvocationWriter; +import org.codehaus.groovy.classgen.asm.WriterController; + +public class IndyBinHelper extends BinaryExpressionHelper { + + public IndyBinHelper(WriterController wc) { + super(wc); + } + + @Override + protected void writePostOrPrefixMethod(int op, String method, Expression expression, Expression orig) { + getController().getInvocationWriter().makeCall( + orig, EmptyExpression.INSTANCE, + new ConstantExpression(method), + MethodCallExpression.NO_ARGUMENTS, + InvocationWriter.invokeMethod, + false, false, false); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java new file mode 100644 index 0000000..d0aac70 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java @@ -0,0 +1,65 @@ +/* + * 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.indy; + +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.classgen.asm.CallSiteWriter; +import org.codehaus.groovy.classgen.asm.WriterController; + +/** + * Dummy class used by the indy implementation. + * This class mostly contains empty stubs for calls to the call site writer, + * since this class is normally used to prepare call site caching and in indy + * call site caching is done by the jvm. + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class IndyCallSiteWriter extends CallSiteWriter { + private final WriterController controller; + public IndyCallSiteWriter(WriterController controller) { + super(controller); + this.controller = controller; + } + + @Override + public void generateCallSiteArray() {} + @Override + public void makeCallSite(Expression receiver, String message, + Expression arguments, boolean safe, boolean implicitThis, + boolean callCurrent, boolean callStatic) {} + @Override + public void makeSingleArgumentCall(Expression receiver, String message, Expression arguments, boolean safe) {} + @Override + public void prepareCallSite(String message) {} + @Override + public void makeSiteEntry() {} + @Override + public void makeCallSiteArrayInitializer() {} + + @Override + public void makeGetPropertySite(Expression receiver, String name, boolean safe, boolean implicitThis) { + InvokeDynamicWriter idw = (InvokeDynamicWriter)controller.getInvocationWriter(); + idw.writeGetProperty(receiver, name, safe, implicitThis, false); + } + @Override + public void makeGroovyObjectGetPropertySite(Expression receiver, String name, boolean safe, boolean implicitThis) { + InvokeDynamicWriter idw = (InvokeDynamicWriter)controller.getInvocationWriter(); + idw.writeGetProperty(receiver, name, safe, implicitThis, true); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/indy/InvokeDynamicWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/indy/InvokeDynamicWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/indy/InvokeDynamicWriter.java new file mode 100644 index 0000000..4e677d9 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/indy/InvokeDynamicWriter.java @@ -0,0 +1,234 @@ +/* + * 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.indy; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.EmptyExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.tools.WideningCategories; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.classgen.asm.CompileStack; +import org.codehaus.groovy.classgen.asm.InvocationWriter; +import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter; +import org.codehaus.groovy.classgen.asm.OperandStack; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.runtime.wrappers.Wrapper; +import org.codehaus.groovy.vmplugin.v7.IndyInterface; +import org.objectweb.asm.Handle; + +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; + +import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getTypeDescription; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CALL_TYPES.CAST; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CALL_TYPES.GET; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CALL_TYPES.INIT; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.CALL_TYPES.METHOD; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.GROOVY_OBJECT; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.IMPLICIT_THIS; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.SAFE_NAVIGATION; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.SPREAD_CALL; +import static org.codehaus.groovy.vmplugin.v7.IndyInterface.THIS_CALL; +import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; + +/** + * This Writer is used to generate the call invocation byte codes + * for usage by invokedynamic. + * + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class InvokeDynamicWriter extends InvocationWriter { + + + private static final String INDY_INTERFACE_NAME = IndyInterface.class.getName().replace('.', '/'); + private static final String BSM_METHOD_TYPE_DESCRIPTOR = + MethodType.methodType( + CallSite.class, Lookup.class, String.class, MethodType.class, + String.class, int.class + ).toMethodDescriptorString(); + private static final Handle BSM = + new Handle( + H_INVOKESTATIC, + INDY_INTERFACE_NAME, + "bootstrap", + BSM_METHOD_TYPE_DESCRIPTOR); + + private final WriterController controller; + + public InvokeDynamicWriter(WriterController wc) { + super(wc); + this.controller = wc; + } + + @Override + protected boolean makeCachedCall(Expression origin, ClassExpression sender, + Expression receiver, Expression message, Expression arguments, + MethodCallerMultiAdapter adapter, boolean safe, boolean spreadSafe, + boolean implicitThis, boolean containsSpreadExpression + ) { + // fixed number of arguments && name is a real String and no GString + if ((adapter == null || adapter == invokeMethod || adapter == invokeMethodOnCurrent || adapter == invokeStaticMethod) && !spreadSafe) { + String methodName = getMethodName(message); + if (methodName != null) { + makeIndyCall(adapter, receiver, implicitThis, safe, methodName, arguments); + return true; + } + } + return false; + } + + private String prepareIndyCall(Expression receiver, boolean implicitThis) { + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + + compileStack.pushLHS(false); + + // load normal receiver as first argument + compileStack.pushImplicitThis(implicitThis); + receiver.visit(controller.getAcg()); + compileStack.popImplicitThis(); + return "("+getTypeDescription(operandStack.getTopOperand()); + } + + private void finishIndyCall(Handle bsmHandle, String methodName, String sig, int numberOfArguments, Object... bsmArgs) { + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + + controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, sig, bsmHandle, bsmArgs); + + operandStack.replace(ClassHelper.OBJECT_TYPE, numberOfArguments); + compileStack.popLHS(); + } + + private void makeIndyCall(MethodCallerMultiAdapter adapter, Expression receiver, boolean implicitThis, boolean safe, String methodName, Expression arguments) { + OperandStack operandStack = controller.getOperandStack(); + + StringBuilder sig = new StringBuilder(prepareIndyCall(receiver, implicitThis)); + + // load arguments + int numberOfArguments = 1; + ArgumentListExpression ae = makeArgumentList(arguments); + boolean containsSpreadExpression = AsmClassGenerator.containsSpreadExpression(arguments); + if (containsSpreadExpression) { + controller.getAcg().despreadList(ae.getExpressions(), true); + sig.append(getTypeDescription(Object[].class)); + } else { + for (Expression arg : ae.getExpressions()) { + arg.visit(controller.getAcg()); + if (arg instanceof CastExpression) { + operandStack.box(); + controller.getAcg().loadWrapper(arg); + sig.append(getTypeDescription(Wrapper.class)); + } else { + sig.append(getTypeDescription(operandStack.getTopOperand())); + } + numberOfArguments++; + } + } + + sig.append(")Ljava/lang/Object;"); + String callSiteName = METHOD.getCallSiteName(); + if (adapter==null) callSiteName = INIT.getCallSiteName(); + int flags = getMethodCallFlags(adapter, safe, containsSpreadExpression); + finishIndyCall(BSM, callSiteName, sig.toString(), numberOfArguments, methodName, flags); + } + + private static int getMethodCallFlags(MethodCallerMultiAdapter adapter, boolean safe, boolean spread) { + int ret = 0; + if (safe) ret |= SAFE_NAVIGATION; + if (adapter==invokeMethodOnCurrent) ret |= THIS_CALL; + if (spread) ret |= SPREAD_CALL; + return ret; + } + + @Override + public void makeSingleArgumentCall(Expression receiver, String message, Expression arguments, boolean safe) { + makeIndyCall(invokeMethod, receiver, false, safe, message, arguments); + } + + private static int getPropertyFlags(boolean safe, boolean implicitThis, boolean groovyObject) { + int flags = 0; + if (implicitThis) flags |= IMPLICIT_THIS; + if (groovyObject) flags |= GROOVY_OBJECT; + if (safe) flags |= SAFE_NAVIGATION; + return flags; + } + + protected void writeGetProperty(Expression receiver, String propertyName, boolean safe, boolean implicitThis, boolean groovyObject) { + String sig = prepareIndyCall(receiver, implicitThis); + sig += ")Ljava/lang/Object;"; + int flags = getPropertyFlags(safe,implicitThis,groovyObject); + finishIndyCall(BSM, GET.getCallSiteName(), sig, 1, propertyName, flags); + } + + @Override + protected void writeNormalConstructorCall(ConstructorCallExpression call) { + makeCall(call, new ClassExpression(call.getType()), new ConstantExpression("<init>"), call.getArguments(), null, false, false, false); + } + + @Override + public void coerce(ClassNode from, ClassNode target) { + ClassNode wrapper = ClassHelper.getWrapper(target); + makeIndyCall(invokeMethod, EmptyExpression.INSTANCE, false, false, "asType", new ClassExpression(wrapper)); + if (ClassHelper.boolean_TYPE.equals(target) || ClassHelper.Boolean_TYPE.equals(target)) { + writeIndyCast(ClassHelper.OBJECT_TYPE,target); + } else { + BytecodeHelper.doCast(controller.getMethodVisitor(), wrapper); + controller.getOperandStack().replace(wrapper); + controller.getOperandStack().doGroovyCast(target); + } + } + + @Override + public void castToNonPrimitiveIfNecessary(ClassNode sourceType, ClassNode targetType) { + ClassNode boxedType = ClassHelper.getWrapper(sourceType); + if (WideningCategories.implementsInterfaceOrSubclassOf(boxedType, targetType)) { + controller.getOperandStack().box(); + return; + } + writeIndyCast(sourceType, targetType); + } + + private void writeIndyCast(ClassNode sourceType, ClassNode targetType) { + StringBuilder sig = new StringBuilder(); + sig.append('('); + sig.append(getTypeDescription(sourceType)); + sig.append(')'); + sig.append(getTypeDescription(targetType)); + + controller.getMethodVisitor().visitInvokeDynamicInsn( + //TODO: maybe use a different bootstrap method since no arguments are needed here + CAST.getCallSiteName(), sig.toString(), BSM, "()", 0); + controller.getOperandStack().replace(targetType); + } + + @Override + public void castNonPrimitiveToBool(ClassNode sourceType) { + writeIndyCast(sourceType, ClassHelper.boolean_TYPE); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/indy/sc/IndyStaticTypesMultiTypeDispatcher.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/indy/sc/IndyStaticTypesMultiTypeDispatcher.java b/src/main/java/org/codehaus/groovy/classgen/asm/indy/sc/IndyStaticTypesMultiTypeDispatcher.java new file mode 100644 index 0000000..ebcf2a8 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/indy/sc/IndyStaticTypesMultiTypeDispatcher.java @@ -0,0 +1,94 @@ +/* + * 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.indy.sc; + +import org.codehaus.groovy.classgen.asm.BinaryExpressionWriter; +import org.codehaus.groovy.classgen.asm.MethodCaller; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.classgen.asm.sc.StaticTypesBinaryExpressionMultiTypeDispatcher; +import org.codehaus.groovy.vmplugin.v7.IndyInterface; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; + +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +/** + * Multi type dispatcher for binary expression backend combining indy and static compilation + * @author Jochen Theodorou + * @since 2.5.0 + */ +public class IndyStaticTypesMultiTypeDispatcher extends StaticTypesBinaryExpressionMultiTypeDispatcher { + public IndyStaticTypesMultiTypeDispatcher(WriterController wc) { + super(wc); + + } + + private static final String INDY_INTERFACE_NAME = IndyInterface.class.getName().replace('.', '/'); + private static final String BSM_METHOD_TYPE_DESCRIPTOR = + MethodType.methodType( + CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class + ).toMethodDescriptorString(); + private static final Handle BSM = + new Handle( + H_INVOKESTATIC, + INDY_INTERFACE_NAME, + "staticArrayAccess", + BSM_METHOD_TYPE_DESCRIPTOR); + private static class GenericArrayAccess extends MethodCaller { + private final String name, signature; + public GenericArrayAccess(String name, String signature) { + this.name = name; + this.signature = signature; + } + @Override public void call(MethodVisitor mv) { + mv.visitInvokeDynamicInsn(name, signature, BSM); + } + } + + protected BinaryExpressionWriter[] initializeDelegateHelpers() { + BinaryExpressionWriter[] bewArray = super.initializeDelegateHelpers(); + /* 1: int */ + bewArray[1].setArraySetAndGet( new GenericArrayAccess("set","([III)V"), + new GenericArrayAccess("get","([II)I")); + /* 2: long */ + bewArray[2].setArraySetAndGet( new GenericArrayAccess("set","([JIJ)V"), + new GenericArrayAccess("get","([JI)J")); + /* 3: double */ + bewArray[3].setArraySetAndGet( new GenericArrayAccess("set","([DID)V"), + new GenericArrayAccess("get","([DI)D")); + /* 4: char */ + bewArray[4].setArraySetAndGet( new GenericArrayAccess("set","([CIC)V"), + new GenericArrayAccess("get","([CI)C")); + /* 5: byte */ + bewArray[5].setArraySetAndGet( new GenericArrayAccess("set","([BIB)V"), + new GenericArrayAccess("get","([BI)B")); + /* 6: short */ + bewArray[6].setArraySetAndGet( new GenericArrayAccess("set","([SIS)V"), + new GenericArrayAccess("get","([SI)S")); + /* 7: float */ + bewArray[7].setArraySetAndGet( new GenericArrayAccess("get","([FIF)V"), + new GenericArrayAccess("set","([FI)F")); + /* 8: bool */ + bewArray[8].setArraySetAndGet( new GenericArrayAccess("get","([ZIZ)V"), + new GenericArrayAccess("set","([ZI)Z")); + return bewArray; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/package.html ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/package.html b/src/main/java/org/codehaus/groovy/classgen/asm/package.html new file mode 100644 index 0000000..cd40f7e --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/package.html @@ -0,0 +1,29 @@ +<!-- + + 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. + +--> +<html> + <head> + <title>package org.codehaus.groovy.classgen.asm.*</title> + </head> + <body> + <p>Helper classes for ASMClassGenerator. All classes in this package + are for internal usage only.</p> + </body> +</html> http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticCompilationMopWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticCompilationMopWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticCompilationMopWriter.java new file mode 100644 index 0000000..5a09829 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticCompilationMopWriter.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.classgen.asm.sc; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.classgen.asm.MopWriter; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; + +import java.util.LinkedList; + +/** + * A MOP Writer that skips the generation of MOP methods. This writer is used + * when a class is *fully* statically compiled. In mixed mode, MOP methods are + * still generated. + * + * @author Cédric Champeau + * @since 2.4.0 + */ +public class StaticCompilationMopWriter extends MopWriter { + + public static final MopWriter.Factory FACTORY = new MopWriter.Factory() { + @Override + public MopWriter create(final WriterController controller) { + return new StaticCompilationMopWriter(controller); + } + }; + + private final StaticTypesWriterController controller; + + public StaticCompilationMopWriter(final WriterController wc) { + super(wc); + this.controller = (StaticTypesWriterController) wc; + } + + + public void createMopMethods() { + ClassNode classNode = controller.getClassNode(); + LinkedList<MethodNode> requiredMopMethods = classNode.getNodeMetaData(StaticTypesMarker.SUPER_MOP_METHOD_REQUIRED); + if (requiredMopMethods!=null) { + generateMopCalls(requiredMopMethods, false); + } + } + +}
