From c090648d88321bb6de08550d665a5704938de899 Mon Sep 17 00:00:00 2001 From: Yuichi Nishiwaki Date: Tue, 10 Sep 2013 18:06:04 +0900 Subject: [PATCH 1/4] initial commit of lambda-lifting optimize command --- src/optimizer.jsx | 245 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 244 insertions(+), 1 deletion(-) diff --git a/src/optimizer.jsx b/src/optimizer.jsx index eabfd861..199b0407 100644 --- a/src/optimizer.jsx +++ b/src/optimizer.jsx @@ -289,6 +289,9 @@ class Optimizer { } else if (cmd == "staticize") { this._commands.push(new _StaticizeOptimizeCommand()); calleesAreDetermined = false; + } else if (cmd == "lambda-lifting") { + this._commands.push(new _LambdaLiftingOptimizeCommand()); + calleesAreDetermined = false; } else if (cmd == "unclassify") { this._commands.push(new _UnclassifyOptimizationCommand()); calleesAreDetermined = false; @@ -3972,7 +3975,6 @@ class _NoDebugCommand extends _OptimizeCommand { } } - class _TailRecursionOptimizeCommand extends _FunctionOptimizeCommand { static const IDENTIFIER = "tail-rec"; @@ -4067,3 +4069,244 @@ class _TailRecursionOptimizeCommand extends _FunctionOptimizeCommand { } } } + +class _LambdaLiftingOptimizeCommand extends _OptimizeCommand { + static const IDENTIFIER = "lambda-lifting"; + + function constructor() { + super(_LambdaLiftingOptimizeCommand.IDENTIFIER); + } + + override function _createStash () : Stash { + return new _LambdaLiftingOptimizeCommand._Stash(); + } + + override function performOptimization () : void { + + /** TODOs + * - function expressions + * - nested function statements + * - function statement containing some "this" expressions + */ + + function onMethod(method : MemberFunctionDefinition) : boolean { + if (method.getStatements() == null) + return true; + var statements = method.getStatements(); + (function onSubStatements (statements : Statement[]) : boolean { + for (var i = 0; i < statements.length; ++i) { + var left = statements.length - i; + if (statements[i] instanceof FunctionStatement) { + var funcDef = (statements[i] as FunctionStatement).getFuncDef(); + if (this._closureIsLiftable(funcDef) && this._closureIsStatic(funcDef)) { + this.log("staticizing closure: " + funcDef.name()); + this._staticizeClosure(funcDef, statements, i); + } + } else { + _Util.handleSubStatements(onSubStatements, statements[i]); + } + i = statements.length - left; + } + return true; + }(statements)); + return true; + } + + // lift up (staticize) closures + this.getCompiler().forEachClassDef(function (parser, classDef) { + // skip interfaces and mixins + if ((classDef.flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) != 0) + return true; + classDef.forEachMemberFunction(onMethod); + return true; + }); + + // rewrite closure invocations to method calls + this.getCompiler().forEachClassDef(function (parser, classDef) { + this.log("rewriting closure calls in class: " + classDef.className()); + // rewrite member variables + classDef.forEachMemberVariable(function (varDef) { + if (varDef.getInitialValue() == null) + return true; + + this._rewriteClosureCallsToMethod(varDef.getInitialValue(), function (expr) { + varDef.setInitialValue(expr); + }, null); + return true; + }); + // rewrite member functions + classDef.forEachMemberFunction(function onFunction (funcDef : MemberFunctionDefinition) : boolean { + return funcDef.forEachStatement(function onStatement (statement : Statement) : boolean { + return statement.forEachExpression(function (expr, replaceCb) { + this._rewriteClosureCallsToMethod(expr, replaceCb, funcDef); + return true; + }) && statement.forEachStatement(onStatement); + }) && funcDef.forEachClosure(onFunction); + }); + return true; + }); + } + + class _Mark extends Stash { + + var mark : boolean; + + function constructor () { + this.mark = false; + } + + function constructor (that: _LambdaLiftingOptimizeCommand._Mark) { + this.mark = that.mark; + } + + override function clone () : Stash { + return new _LambdaLiftingOptimizeCommand._Mark(this); + } + + } + + function _closureIsLiftable(funcDef : MemberFunctionDefinition) : boolean { + function getMark (local : LocalVariable) : _LambdaLiftingOptimizeCommand._Mark { + var stash = local.getStash("lambda-lifting-mark"); + if (stash == null) { + stash = local.setStash("lambda-lifting-mark", new _LambdaLiftingOptimizeCommand._Mark); + } + return stash as _LambdaLiftingOptimizeCommand._Mark; + } + + function markUpperVariables (funcDef : MemberFunctionDefinition, mark : boolean) : void { + var parent = funcDef.getParent(); + while (parent != null) { + var locals = parent.getLocals(); + for (var i = 0; i < locals.length; ++i) { + getMark(locals[i]).mark = mark; + } + var args = parent.getArguments(); + for (var i = 0; i < args.length; ++i) { + getMark(args[i]).mark = mark; + } + parent = parent.getParent(); + } + } + + // mark all upper locals/arguments + markUpperVariables(funcDef, true); + try { + + return Util.forEachStatement(function onStatement (statement) { + if (statement instanceof FunctionStatement) { + var funcDef = (statement as FunctionStatement).getFuncDef(); + return Util.forEachStatement(onStatement, funcDef.getStatements()); + } + return statement.forEachExpression(function onExpr (expr) { + if (expr instanceof FunctionExpression) { + var funcDef = (expr as FunctionExpression).getFuncDef(); + return Util.forEachStatement(onStatement, funcDef.getStatements()); + } else if (expr instanceof LocalExpression) { + if (getMark((expr as LocalExpression).getLocal()).mark == true) + return false; + } + return expr.forEachExpression(onExpr); + }) && statement.forEachStatement(onStatement); + }, funcDef.getStatements()); + + } finally { + // unset marks + markUpperVariables(funcDef, false); + } + } + + function _closureIsStatic(funcDef : MemberFunctionDefinition) : boolean { + return Util.forEachStatement(function onStatement(statement) { + return statement.forEachExpression(function onExpr (expr) { + if (expr instanceof ThisExpression) { + return false; + } + return expr.forEachExpression(onExpr); + }) && statement.forEachStatement(onStatement); + }, funcDef.getStatements()); + } + + class _Stash extends Stash { + + var altName : Nullable.; + + function constructor () { + this.altName = null; + } + + function constructor (that : _LambdaLiftingOptimizeCommand._Stash) { + this.altName = that.altName; + } + + override function clone () : Stash { + return new _LambdaLiftingOptimizeCommand._Stash(this); + } + + } + + function _staticizeClosure (funcDef : MemberFunctionDefinition, statements : Statement[], index : int) : void { + assert statements[index] instanceof FunctionStatement; + assert funcDef == (statements[index] as FunctionStatement).getFuncDef(); + assert funcDef.getParent() != null; + + var classDef = funcDef.getClassDef(); + + // rename + var newName = this._newMethodName(classDef, funcDef.name(), (funcDef.getType() as ResolvedFunctionType).getArgumentTypes(), true); + (this.getStash(funcDef.getFuncLocal()) as _LambdaLiftingOptimizeCommand._Stash).altName = newName; + funcDef._nameToken = new Token(newName, true); + + var locals = funcDef.getParent().getLocals(); + for (var i = 0, length = locals.length; i < length; ++i) { + if (locals[i] == funcDef.getFuncLocal()) { + locals.splice(i, 1); + break; + } + } + assert i != length; + funcDef.setFuncLocal(null); + + // update flags + funcDef.setFlags(funcDef.flags() & ~ClassDefinition.IS_EXPORT); + + // detach from the parent, attach to the classDef + Util.unlinkFunction(funcDef, funcDef.getParent()); + classDef.members().push(funcDef); + + statements.splice(index, 1); + } + + function _newMethodName (classDef : ClassDefinition, baseName : string, argTypes : Type[], isStatic : boolean) : string { + var index = 0; + var newName = baseName; + // search for a name which does not conflict with existing function + while (Util.findFunctionInClass(classDef, newName, argTypes, isStatic) != null) { + var newName = Util.format("%1_%2", [ baseName, index as string ]); + ++index; + } + return newName; + } + + function _rewriteClosureCallsToMethod (expr : Expression, replaceCb : function(:Expression):void, funcDef : MemberFunctionDefinition) : void { + function onExpr(expr : Expression, replaceCb : function(:Expression):void) : boolean { + if (expr instanceof LocalExpression) { + var localExpr = expr as LocalExpression; + + var newName; + if ((newName = (this.getStash(localExpr.getLocal()) as _LambdaLiftingOptimizeCommand._Stash).altName) != null) { + // found, rewrite + replaceCb(new PropertyExpression( + new Token(".", false), + new ClassExpression(new Token(funcDef.getClassDef().className(), true), new ObjectType(funcDef.getClassDef())), + new Token(newName, true), + [], // typeArgs + localExpr.getType())); + } + } + return expr.forEachExpression(onExpr); + } + onExpr(expr, replaceCb); + } + +} From c59670c174c7847a0843cc1181ee2f03f0512d29 Mon Sep 17 00:00:00 2001 From: Yuichi Nishiwaki Date: Tue, 10 Sep 2013 18:10:41 +0900 Subject: [PATCH 2/4] add a test for lambda-lifting --- t/optimize/091.lambda-lifting.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 t/optimize/091.lambda-lifting.jsx diff --git a/t/optimize/091.lambda-lifting.jsx b/t/optimize/091.lambda-lifting.jsx new file mode 100644 index 00000000..f872ae80 --- /dev/null +++ b/t/optimize/091.lambda-lifting.jsx @@ -0,0 +1,14 @@ +/*JSX_OPTS +--optimize lambda-lifting +*/ +/*EXPECTED +4 +*/ +class _Main { + static function main (args : string[]) : void { + function square (n : number) : number { + return n * n; + } + log square(2); + } +} From db7d4008b6bee0e1a06a42f08593d652766b6638 Mon Sep 17 00:00:00 2001 From: Yuichi Nishiwaki Date: Tue, 10 Sep 2013 21:02:50 +0900 Subject: [PATCH 3/4] deep search for "this" expression when determining if a given funcDef is static or not --- src/optimizer.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/optimizer.jsx b/src/optimizer.jsx index 540b734d..8bc377e7 100644 --- a/src/optimizer.jsx +++ b/src/optimizer.jsx @@ -4232,8 +4232,15 @@ class _LambdaLiftingOptimizeCommand extends _OptimizeCommand { function _closureIsStatic(funcDef : MemberFunctionDefinition) : boolean { return Util.forEachStatement(function onStatement(statement) { + if (statement instanceof FunctionStatement) { + var funcDef = (statement as FunctionStatement).getFuncDef(); + return Util.forEachStatement(onStatement, funcDef.getStatements()); + } return statement.forEachExpression(function onExpr (expr) { - if (expr instanceof ThisExpression) { + if (expr instanceof FunctionExpression) { + var funcDef = (expr as FunctionExpression).getFuncDef(); + return Util.forEachStatement(onStatement, funcDef.getStatements()); + } else if (expr instanceof ThisExpression) { return false; } return expr.forEachExpression(onExpr); @@ -4317,6 +4324,7 @@ class _LambdaLiftingOptimizeCommand extends _OptimizeCommand { [], // typeArgs localExpr.getType())); } + return true; } return expr.forEachExpression(onExpr); } From c6c4399ca659c3861755ce906c3b347e9ce646e6 Mon Sep 17 00:00:00 2001 From: Yuichi Nishiwaki Date: Tue, 10 Sep 2013 21:03:19 +0900 Subject: [PATCH 4/4] add "lambda-lifting" to release optimize command sequence --- src/optimizer.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/optimizer.jsx b/src/optimizer.jsx index 8bc377e7..27912133 100644 --- a/src/optimizer.jsx +++ b/src/optimizer.jsx @@ -242,6 +242,7 @@ class Optimizer { "no-debug", "fold-const", "tail-rec", + "lambda-lifting", "return-if", "inline", "dce",