Skip to content
This repository has been archived by the owner on May 15, 2018. It is now read-only.

Commit

Permalink
Added many comments.
Browse files Browse the repository at this point in the history
Prepared VariableAccessReplacer for CompileStatic
  • Loading branch information
jlink committed Feb 6, 2014
1 parent 823fc2b commit 3d2031b
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;

/**
* Collect all recursive calls within method
*
* @author Johannes Link
*/
@CompileStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,43 @@ import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.CodeVisitorSupport
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression

/**
*
* Check if there are any recursive calls in a method
*
* @author Johannes Link
*/
@CompileStatic
class HasRecursiveCalls extends CodeVisitorSupport {
MethodNode method
boolean hasRecursiveCalls = false
MethodNode method
boolean hasRecursiveCalls = false

public void visitMethodCallExpression(MethodCallExpression call) {
if (isRecursive(call)) {
hasRecursiveCalls = true
} else {
super.visitMethodCallExpression(call)
}
}

public void visitStaticMethodCallExpression(StaticMethodCallExpression call) {
if (isRecursive(call)) {
hasRecursiveCalls = true
} else {
super.visitStaticMethodCallExpression(call)
}
}

public void visitMethodCallExpression(MethodCallExpression call) {
if (isRecursive(call)) {
hasRecursiveCalls = true
} else {
super.visitMethodCallExpression(call)
}
}
private boolean isRecursive(call) {
new RecursivenessTester().isRecursive(method: method, call: call)
}

public void visitStaticMethodCallExpression(StaticMethodCallExpression call) {
if (isRecursive(call)) {
hasRecursiveCalls = true
} else {
super.visitStaticMethodCallExpression(call)
}
}

private boolean isRecursive(call) {
new RecursivenessTester().isRecursive(method: method, call: call)
}

synchronized boolean test(MethodNode method) {
hasRecursiveCalls = false
this.method = method
this.method.code.visit(this)
hasRecursiveCalls
}
}
synchronized boolean test(MethodNode method) {
hasRecursiveCalls = false
this.method = method
this.method.code.visit(this)
hasRecursiveCalls
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ import org.codehaus.groovy.ast.stmt.TryCatchStatement
import org.codehaus.groovy.ast.stmt.WhileStatement

/**
* Wrap the body of a method in a while loop, nested in a try-catch.
* This is the first step in making a tail recursive method iterative.
*
* There are two ways to invoke the next iteration step:
* 1. "continue _RECURE_HERE_" is used by recursive calls outside of closures
* 2. "throw LOOP_EXCEPTION" is used by recursive calls within closures b/c you cannot invoke "continue" from there
*
* @author Johannes Link
*/
@CompileStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import org.codehaus.groovy.ast.expr.VariableExpression
/**
*
* Test if a method call is recursive if called within a given method node.
* Tries to handle static calls as well.
* Handles static calls as well.
*
* Currently known simplifications:
* Does not check for matching argument types but only considers the number of arguments.
* Does not check for matching return types; even void and any object type are considered to be compatible.
* - Does not check for method overloading or overridden methods.
* - Does not check for matching return types; even void and any object type are considered to be compatible.
* - Argument type matching could be more specific in case of static compilation.
* - Method names via a GString are never considered to be recursive
*
* @author Johannes Link
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.classgen.BytecodeSequence

/**
* @author Johannes Link
* Adds explicit return statements to implicit return points in a closure. This is necessary since
* tail-recursion is detected by having the recursive call within the return statement.
*
* Copied a lot of code from package org.codehaus.groovy.classgen.ReturnAdder which can only be used for Methods
*
* @author Johannes Link
*/
class ReturnAdderForClosures extends CodeVisitorSupport {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@
*/
package org.codehaus.groovy.transform.tailrec

import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.expr.BinaryExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.stmt.ReturnStatement
import org.codehaus.groovy.ast.stmt.Statement

/**
* Translates all return statements into an invocation of the next iteration. This can be either
* - "continue LOOP_LABEL": Outside closures
* - "throw LOOP_EXCEPTION": Inside closures
*
* Moreover, before adding the recur statement the iteration parameters (originally the method args)
* are set to their new value. To prevent variable aliasing parameters will be copied into temp vars
* before they are changes so that their current iteration value can be used when setting other params.
*
* There's probably place for optimizing the amount of variable copying being done, e.g.
* parameters that are only handed through must not be copied at all.
*
* @author Johannes Link
*/
class ReturnStatementToIterationConverter {
Expand Down Expand Up @@ -68,23 +74,29 @@ class ReturnStatementToIterationConverter {
return result
}

private replaceAllArgUsages(List<ExpressionStatement> nodes, tempMapping) {
def unusedTempNames = new HashSet(tempMapping.values()*.name)
private replaceAllArgUsages(List<ExpressionStatement> nodes, Map tempMapping) {
Set<String> unusedTempNames = new HashSet(tempMapping.values()*.name)
VariableReplacedListener tracker = new UsedVariableTracker()
for (ExpressionStatement statement : nodes) {
unusedTempNames.removeAll(replaceArgUsageByTempUsage(statement.expression, tempMapping))
replaceArgUsageByTempUsage(statement.expression, tempMapping, tracker)
}
unusedTempNames = unusedTempNames - tracker.usedVariableNames
return unusedTempNames
}

private replaceArgUsageByTempUsage(BinaryExpression binary, Map tempMapping) {
Set usedTempNames = [] as Set
def useTempInstead = { Map tempNameAndType ->
usedTempNames << tempNameAndType.name
AstHelper.createVariableReference(tempNameAndType)
}
VariableAccessReplacer replacer = new VariableAccessReplacer(nameAndTypeMapping: tempMapping, replaceBy: useTempInstead)
private void replaceArgUsageByTempUsage(BinaryExpression binary, Map tempMapping, UsedVariableTracker tracker) {
VariableAccessReplacer replacer = new VariableAccessReplacer(nameAndTypeMapping: tempMapping, listener: tracker)
// Replacement must only happen in binary.rightExpression. It's a hack in VariableExpressionReplacer which takes care of that.
replacer.replaceIn(binary)
return usedTempNames
}
}

class UsedVariableTracker implements VariableReplacedListener {

final Set<String> usedVariableNames = [] as Set

@Override
void variableReplaced(VariableExpression oldVar, VariableExpression newVar) {
usedVariableNames.add(newVar.name)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.codehaus.groovy.ast.stmt.ReturnStatement
import org.codehaus.groovy.ast.stmt.Statement

/**
* Since a ternary statement has more than one exit point tail-recursiveness testing cannot be easily done.
* Therefore this class translates a ternary statement (or Elvis operator) into the equivalent if-else statement.
* @author Johannes Link
*/
class TernaryToIfStatementConverter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,40 @@ import org.codehaus.groovy.ast.expr.VariableExpression
* The variable names to replace as well as their replacement name and type have to be configured
* in nameAndTypeMapping before calling replaceIn().
*
* The closure replaceBy can be changed if clients want to do more in case of replacement.
* The VariableReplacedListener can be set if clients want to react to variable replacement.
*
* @author Johannes Link
*/
//@CompileStatic
class VariableAccessReplacer {

Map<String, Map> nameAndTypeMapping = [:] //e.g.: ['varToReplace': [name: 'newVar', type: TypeOfVar]]
Closure<VariableExpression> replaceBy = { Map nameAndType -> AstHelper.createVariableReference(nameAndType) }

// VariableReplacedListener listener = {VariableExpression oldVar, VariableExpression newVar ->} as VariableReplacedListener
VariableReplacedListener listener = VariableReplacedListener.NULL

void replaceIn(ASTNode root) {
Closure<Boolean> whenParam = { VariableExpression expr ->
return nameAndTypeMapping.containsKey(expr.name)
}
Closure<VariableExpression> replaceWithLocalVariable = { VariableExpression expr ->
Map nameAndType = nameAndTypeMapping[expr.name]
return replaceBy(nameAndType)
VariableExpression newVar = AstHelper.createVariableReference(nameAndType)
listener.variableReplaced(expr, newVar)
return newVar
}
new VariableExpressionReplacer(when: whenParam, replaceWith: replaceWithLocalVariable).replaceIn(root)
}

}

//@CompileStatic
//interface VariableReplacedListener {
// void variableReplaced(VariableExpression oldVar, VariableExpression newVar)
//}
@CompileStatic
interface VariableReplacedListener {
void variableReplaced(VariableExpression oldVar, VariableExpression newVar)

static VariableReplacedListener NULL = new VariableReplacedListener() {
@Override
void variableReplaced(VariableExpression oldVar, VariableExpression newVar) {
//do nothing
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ import java.lang.reflect.Method
/**
* Tool for replacing VariableExpression instances in an AST by other VariableExpression instances.
* Regardless of a real change taking place in nested expressions, all considered expression (trees) will be replaced.
* This could be optimized to accelerate compilation.
*
* Within @TailRecursive it is used to swap the access of arg with the access of temp vars
* Within @TailRecursive it is used
* - to swap the access of method args with the access to iteration variables
* - to swap the access of iteration variables with the access of temp vars
*
* @author Johannes Link
*/
Expand Down Expand Up @@ -64,7 +67,8 @@ class VariableExpressionReplacer extends CodeVisitorSupport {
}

/**
* It's the only Expression in which replacing is considered.
* It's the only Expression type in which replacing is considered.
* That's an abuse of the class, but I couldn't think of a better way.
*/
public void visitBinaryExpression(BinaryExpression expression) {
//A hack: Only replace right expression b/c ReturnStatementToIterationConverter needs it that way :-/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed 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.transform.tailrec

import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.ExpressionTransformer
import org.codehaus.groovy.ast.expr.VariableExpression

/**
* An expression transformer used in the process of replacing the access to variables
*
* @author Johannes Link
*/
@CompileStatic
class VariableExpressionTransformer implements ExpressionTransformer {

Expand Down

0 comments on commit 3d2031b

Please sign in to comment.