jsx
Version:
a faster, safer, easier JavaScript
1,363 lines (1,227 loc) • 140 kB
JSX
/*
* Copyright (c) 2012 DeNA Co., Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import "./analysis.jsx";
import "./classdef.jsx";
import "./parser.jsx";
import "./expression.jsx";
import "./statement.jsx";
import "./type.jsx";
import "./util.jsx";
import "./compiler.jsx";
class _Util {
static function handleSubStatements (cb : function(:Statement[]):boolean, statement : Statement) : boolean {
var ret = false;
if (statement instanceof ContinuableStatement) {
if (cb((statement as ContinuableStatement).getStatements()))
ret = true;
} else if (statement instanceof IfStatement) {
if (cb((statement as IfStatement).getOnTrueStatements()))
ret = true;
if (cb((statement as IfStatement).getOnFalseStatements()))
ret = true;
} else if (statement instanceof SwitchStatement) {
if (cb((statement as SwitchStatement).getStatements()))
ret = true;
} else if (statement instanceof TryStatement) {
if (cb((statement as TryStatement).getTryStatements()))
ret = true;
if (cb((statement as TryStatement).getCatchStatements().map.<Statement>((s) -> { return s; })))
ret = true;
if (cb((statement as TryStatement).getFinallyStatements()))
ret = true;
} else if (statement instanceof CatchStatement) {
if (cb((statement as CatchStatement).getStatements()))
ret = true;
}
return ret;
}
static function classIsNative (classDef : ClassDefinition) : boolean {
return ! classDef.forEachClassToBase(function (classDef) {
if (classDef.className() == "Object"
|| (classDef.flags() & ClassDefinition.IS_NATIVE) == 0) {
return true;
}
return false;
});
}
static function exprHasSideEffects (expr : Expression) : boolean {
// FIXME native array access may have side effects
function onExpr (expr : Expression, _ : function(:Expression):void) : boolean {
if (expr instanceof FunctionExpression
|| expr instanceof NewExpression
|| expr instanceof AssignmentExpression
|| expr instanceof PreIncrementExpression
|| expr instanceof PostIncrementExpression
|| expr instanceof SuperExpression) {
return false;
} else if (expr instanceof CallExpression) {
var callingFuncDef = _DetermineCalleeCommand.getCallingFuncDef(expr);
if (callingFuncDef != null && (callingFuncDef.flags() & ClassDefinition.IS_PURE) != 0) {
// fall through (check receiver and arguments)
} else {
return false;
}
}
return expr.forEachExpression(onExpr);
}
return ! onExpr(expr, null);
}
static function optimizeBasicBlock (funcDef : MemberFunctionDefinition, optimizeExpressions : function(:Expression[]):void) : void {
function optimizeStatements(statements : Statement[]) : void {
var statementIndex = 0;
while (statementIndex < statements.length) {
var exprsToOptimize = new Expression[];
var setOptimizedExprs = new Array.<function(:Expression):void>;
while (statementIndex < statements.length) {
var statement = statements[statementIndex++];
if (statement instanceof ExpressionStatement) {
exprsToOptimize.push((statement as ExpressionStatement).getExpr());
setOptimizedExprs.push(function (statement : ExpressionStatement) : function(:Expression):void {
return function (expr) {
statement.setExpr(expr);
};
}(statement as ExpressionStatement));
} else if (statement instanceof ReturnStatement) {
var expr = (statement as ReturnStatement).getExpr();
if (expr != null) {
exprsToOptimize.push((statement as ReturnStatement).getExpr());
setOptimizedExprs.push(function (statement : ReturnStatement) : function(:Expression):void {
return function (expr) {
statement.setExpr(expr);
};
}(statement as ReturnStatement));
}
break;
} else {
statement.handleStatements(function (statements) {
optimizeStatements(statements);
return true;
});
if (statement instanceof IfStatement) {
exprsToOptimize.push((statement as IfStatement).getExpr());
setOptimizedExprs.push(function (statement : IfStatement) : function(:Expression):void {
return function (expr) {
statement.setExpr(expr);
};
}(statement as IfStatement));
} else if (statement instanceof SwitchStatement) {
exprsToOptimize.push((statement as SwitchStatement).getExpr());
setOptimizedExprs.push(function (statement : SwitchStatement) : function(:Expression):void {
return function (expr) {
statement.setExpr(expr);
};
}(statement as SwitchStatement));
} else {
// TODO implement
}
break;
}
}
// optimize basic block
if (exprsToOptimize.length != 0) {
optimizeExpressions(exprsToOptimize);
for (var i = 0; i < exprsToOptimize.length; ++i) {
setOptimizedExprs[i](exprsToOptimize[i]);
}
}
}
}
var statements = funcDef.getStatements();
if (statements != null) {
optimizeStatements(statements);
}
}
}
class Optimizer {
var _compiler : Compiler;
var _commands : _OptimizeCommand[];
var _log : string[];
var _dumpLogs : boolean;
var _enableRunTimeTypeCheck : boolean;
static function getReleaseOptimizationCommands() : string[] {
return [
"lto",
"no-assert",
"no-log",
"no-debug",
"staticize",
"fold-const",
"return-if",
"inline",
"dce",
"unbox",
"fold-const",
"lcse",
"dce",
"fold-const",
"array-length",
"unclassify"
];
}
function constructor () {
this._compiler = null;
this._commands = new _OptimizeCommand[];
this._log = new string[];
this._dumpLogs = false;
this._enableRunTimeTypeCheck = true;
}
function setup (cmds : string[]) : Nullable.<string> {
var calleesAreDetermined = false;
function determineCallee () : void {
if (! calleesAreDetermined) {
this._commands.push(new _DetermineCalleeCommand());
calleesAreDetermined = true;
}
}
for (var i = 0; i < cmds.length; ++i) {
var cmd = cmds[i];
if (cmd == "lto") {
this._commands.push(new _LinkTimeOptimizationCommand());
} else if (cmd == "no-assert") {
this._commands.push(new _NoAssertCommand());
} else if (cmd == "no-log") {
this._commands.push(new _NoLogCommand());
} else if (cmd == "no-debug") {
this._commands.push(new _NoDebugCommand());
} else if (cmd == "strip") {
this._commands.push(new _StripOptimizeCommand());
} else if (cmd == "staticize") {
this._commands.push(new _StaticizeOptimizeCommand());
calleesAreDetermined = false;
} else if (cmd == "unclassify") {
this._commands.push(new _UnclassifyOptimizationCommand());
calleesAreDetermined = false;
} else if (cmd == "fold-const") {
this._commands.push(new _FoldConstantCommand());
} else if (cmd == "dce") {
determineCallee();
this._commands.push(new _DeadCodeEliminationOptimizeCommand());
} else if (cmd == "inline") {
determineCallee();
this._commands.push(new _InlineOptimizeCommand());
} else if (cmd == "return-if") {
this._commands.push(new _ReturnIfOptimizeCommand());
} else if (cmd == "lcse") {
this._commands.push(new _LCSEOptimizeCommand());
} else if (cmd == "unbox") {
determineCallee();
this._commands.push(new _UnboxOptimizeCommand());
} else if (cmd == "array-length") {
this._commands.push(new _ArrayLengthOptimizeCommand());
} else if (cmd == "dump-logs") {
this._dumpLogs = true;
} else {
return "unknown optimization command: " + cmd;
}
}
// move lto to top
for (var i = 0; i < this._commands.length; ++i)
if (this._commands[i] instanceof _LinkTimeOptimizationCommand)
break;
if (i != this._commands.length)
this._commands.unshift(this._commands.splice(i, 1)[0]);
return null;
}
function enableRuntimeTypeCheck () : boolean {
return this._enableRunTimeTypeCheck;
}
function setEnableRunTimeTypeCheck (mode : boolean) : void {
this._enableRunTimeTypeCheck = mode;
}
function setCompiler (compiler : Compiler) : Optimizer {
this._compiler = compiler;
return this;
}
function getCompiler () : Compiler {
return this._compiler;
}
function performOptimization () : void {
for (var i = 0; i < this._commands.length; ++i) {
try {
this.log("starting optimizer: " + this._commands[i]._identifier);
this._commands[i].setup(this).performOptimization();
this.log("finished optimizer: " + this._commands[i]._identifier);
} catch (e : Error) {
var platform = this._compiler.getPlatform();
platform.error("fatal error: optimizer '" + this._commands[i]._identifier + "' died unexpectedly, dumping the logs");
this.dumpLogs();
throw e;
}
}
if (this._dumpLogs)
this.dumpLogs();
}
function log (message : string) : void {
this._log.push(message);
}
function dumpLogs () : void {
var platform = this._compiler.getPlatform();
for (var i = 0; i < this._log.length; ++i) {
platform.error(this._log[i]);
}
}
}
abstract class _OptimizeCommand {
var _identifier : string;
var _optimizer : Optimizer;
function constructor (identifier : string) {
this._identifier = identifier;
this._optimizer = null;
}
function setup (optimizer : Optimizer) : _OptimizeCommand {
this._optimizer = optimizer;
return this;
}
function getCompiler () : Compiler {
return this._optimizer.getCompiler();
}
abstract function performOptimization () : void;
function getStash (stashable : Stashable) : Stash {
var stash = stashable.getStash(this._identifier);
if (stash == null) {
stash = stashable.setStash(this._identifier, this._createStash());
}
return stash;
}
function _createStash () : Stash {
throw new Error("if you are going to use the stash, you need to override this function");
}
function resetStash (stashable : Stashable) : void {
stashable.setStash(this._identifier, null);
}
function createVar (funcDef : MemberFunctionDefinition, type : Type, baseName : string) : LocalVariable {
var locals = funcDef.getLocals();
function nameExists (n : string) : boolean {
for (var i = 0; i < locals.length; ++i)
if (locals[i].getName().getValue() == n)
return true;
return false;
}
for (var i = 0; nameExists(baseName + "$" + i as string); ++i)
;
var newLocal = new LocalVariable(new Token(baseName + "$" + i as string, false), type);
locals.push(newLocal);
this.log("rewriting " + baseName + " to " + newLocal.getName().getValue());
return newLocal;
}
function log (message : string) : void {
this._optimizer.log("[" + this._identifier + "] " + message);
}
function setupCommand (command : _OptimizeCommand) : _OptimizeCommand {
command.setup(this._optimizer);
return command;
}
}
abstract class _FunctionOptimizeCommand extends _OptimizeCommand {
var _excludeNative : boolean;
function constructor (identifier : string) {
super(identifier);
this._excludeNative = false;
}
override function performOptimization () : void {
function doit (funcDef : MemberFunctionDefinition) : void {
this.log("starting optimization of " + funcDef.getNotation());
this.optimizeFunction(funcDef);
this.log("finished optimization of " + funcDef.getNotation());
}
this.getCompiler().forEachClassDef(function (parser, classDef) {
classDef.forEachMember(function (member) {
if (member instanceof MemberFunctionDefinition) {
var funcDef = member as MemberFunctionDefinition;
if (funcDef.getStatements() != null) {
doit(funcDef);
}
}
member.forEachClosure(function (funcDef) {
doit(funcDef);
return true;
});
return true;
});
return true;
});
}
abstract function optimizeFunction (funcDef : MemberFunctionDefinition) : boolean ;
}
class _LinkTimeOptimizationCommand extends _OptimizeCommand {
static const IDENTIFIER = "lto";
class Stash extends Stash {
var extendedBy : ClassDefinition[];
function constructor () {
this.extendedBy = new ClassDefinition[];
}
override function clone () : Stash {
throw new Error("not supported");
}
}
function constructor () {
super(_LinkTimeOptimizationCommand.IDENTIFIER);
}
override function _createStash () : Stash {
return new _LinkTimeOptimizationCommand.Stash();
}
override function performOptimization () : void {
// set extendedBy for every class
this.getCompiler().forEachClassDef(function (parser, classDef) {
if (classDef.extendType() != null)
(this.getStash(classDef.extendType().getClassDef()) as _LinkTimeOptimizationCommand.Stash).extendedBy.push(classDef);
for (var i = 0; i < classDef.implementTypes().length; ++i)
(this.getStash(classDef.implementTypes()[i].getClassDef()) as _LinkTimeOptimizationCommand.Stash).extendedBy.push(classDef);
return true;
});
// mark classes / functions that are not derived / overridden / exported as final
this.getCompiler().forEachClassDef(function (parser, classDef) {
if ((classDef.flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN | ClassDefinition.IS_NATIVE | ClassDefinition.IS_FINAL | ClassDefinition.IS_EXPORT)) == 0
&& (this.getStash(classDef) as _LinkTimeOptimizationCommand.Stash).extendedBy.length == 0) {
// found a class that is not extended, mark it and its functions as final
this.log("marking class as final: " + classDef.className());
classDef.setFlags(classDef.flags() | ClassDefinition.IS_FINAL);
classDef.forEachMemberFunction(function (funcDef) {
if ((funcDef.flags() & (ClassDefinition.IS_STATIC | ClassDefinition.IS_FINAL)) == 0)
funcDef.setFlags(funcDef.flags() | ClassDefinition.IS_FINAL);
return true;
});
} else if ((classDef.flags() & (ClassDefinition.IS_NATIVE | ClassDefinition.IS_FINAL)) == 0) {
// adjust flags of functions
classDef.forEachMemberFunction(function (funcDef) {
if ((funcDef.flags() & (ClassDefinition.IS_STATIC | ClassDefinition.IS_NATIVE | ClassDefinition.IS_FINAL)) != 0) {
// ignore static, native, or final functions
} else if ((funcDef.flags() & ClassDefinition.IS_ABSTRACT) == 0) {
// mark functions that are not being overridden as final
if (funcDef.getStatements() == null)
throw new Error("a non-native, non-abstract function with out function body?");
var overrides = this._getOverrides(classDef, (this.getStash(classDef) as _LinkTimeOptimizationCommand.Stash).extendedBy, funcDef.name(), funcDef.getArgumentTypes());
if (overrides.length == 0) {
this.log("marking function as final: " + funcDef.getNotation());
funcDef.setFlags(funcDef.flags() | ClassDefinition.IS_FINAL);
} else {
this.log("function has overrides, not marking as final: " + funcDef.getNotation());
}
} else if ((funcDef.flags() & ClassDefinition.IS_ABSTRACT) != 0) {
/*
FIXME determine if there is only one implementation, and if so, inline the calls to the function.
Note that the implementation of the function may exist in the base classes of one of the classes that
implement the interface, or in the mixins that are implemented by the extending class.
*/
}
return true;
});
}
return true;
});
}
function _getOverrides (srcClassDef : ClassDefinition, classDefs : ClassDefinition[], name : string, argTypes : Type[]) : MemberFunctionDefinition[] {
var overrides = new MemberFunctionDefinition[];
for (var i = 0; i < classDefs.length; ++i)
overrides = overrides.concat(this._getOverridesByClass(srcClassDef, classDefs[i], name, argTypes));
return overrides;
}
function _getOverridesByClass (srcClassDef : ClassDefinition, classDef : ClassDefinition, name : string, argTypes : Type[]) : MemberFunctionDefinition[] {
var overrides = this._getOverrides(srcClassDef, (this.getStash(classDef) as _LinkTimeOptimizationCommand.Stash).extendedBy, name, argTypes);
function addOverride (funcDef : MemberFunctionDefinition) : boolean {
if (funcDef.name() == name
&& (funcDef.flags() & ClassDefinition.IS_ABSTRACT) == 0
&& Util.typesAreEqual(funcDef.getArgumentTypes(), argTypes)) {
overrides.push(funcDef);
return false; // finish looking into the class
}
return true;
}
classDef.forEachMemberFunction(addOverride);
var implementClassDefs = classDef.implementTypes().map.<ClassDefinition>(function (type) { return type.getClassDef(); });
for (var i = 0; i < implementClassDefs.length; ++i) {
if (srcClassDef != implementClassDefs[i]) {
implementClassDefs[i].forEachClassToBase(function (classDef) {
return classDef.forEachMemberFunction(addOverride);
});
}
}
return overrides;
}
}
class _StripOptimizeCommand extends _OptimizeCommand {
static const IDENTIFIER = "strip";
class _Stash extends Stash {
var touched = false;
override function clone() : Stash {
throw new Error("not supported");
}
}
var _classesInstantiated = new ClassDefinition[];
var _methodsAlive = new Map.<Type[][]>;
var _membersToWalk = new MemberDefinition[];
function constructor() {
super(_StripOptimizeCommand.IDENTIFIER);
}
override function _createStash() : Stash {
return new _StripOptimizeCommand._Stash();
}
function _touchStatic(member : MemberDefinition) : void {
assert (member.flags() & ClassDefinition.IS_STATIC) != 0;
var stash = this.getStash(member) as _StripOptimizeCommand._Stash;
if (stash.touched)
return;
this.log("touched " + member.getNotation());
stash.touched = true;
this._membersToWalk.push(member);
}
function _touchInstance(classDef : ClassDefinition) : void {
var stash = this.getStash(classDef) as _StripOptimizeCommand._Stash;
if (stash.touched)
return;
this.log("touched " + classDef.className());
stash.touched = true;
this._classesInstantiated.push(classDef);
for (var name in this._methodsAlive) {
var listOfArgTypes = this._methodsAlive[name];
for (var i = 0; i != listOfArgTypes.length; ++i) {
var funcDef = Util.findFunctionInClass(classDef, name, listOfArgTypes[i], false);
if (funcDef != null) {
this._membersToWalk.push(funcDef);
}
}
}
if (classDef.extendType() != null) {
this._touchInstance(classDef.extendType().getClassDef());
}
classDef.implementTypes().forEach(function (implementType) {
this._touchInstance(implementType.getClassDef());
});
}
function _touchConstructor(funcDef : MemberFunctionDefinition) : void {
assert funcDef.name() == "constructor";
assert (funcDef.flags() & ClassDefinition.IS_STATIC) == 0;
var stash = this.getStash(funcDef) as _StripOptimizeCommand._Stash;
if (stash.touched)
return;
this.log("touched " + funcDef.getNotation());
stash.touched = true;
this._membersToWalk.push(funcDef);
this._touchInstance(funcDef.getClassDef());
}
function _touchMethod(name : string, argTypes : Type[]) : void {
if (this._methodsAlive.hasOwnProperty(name)) {
var listOfArgTypes = this._methodsAlive[name];
} else {
listOfArgTypes = this._methodsAlive[name] = new Type[][];
}
for (var i = 0; i < listOfArgTypes.length; ++i) {
if (Util.typesAreEqual(listOfArgTypes[i], argTypes)) {
return; // already touched
}
}
// push
this.log("touched #" + name);
listOfArgTypes.push(argTypes.concat());
for (var i = 0; i < this._classesInstantiated.length; ++i) {
var funcDef = Util.findFunctionInClass(this._classesInstantiated[i], name, argTypes, false);
if (funcDef != null) {
this._membersToWalk.push(funcDef);
}
}
}
override function performOptimization() : void {
function isEmittedClass(classDef : ClassDefinition) : boolean {
if (classDef instanceof TemplateClassDefinition) {
return false;
}
if ((classDef.flags() & ClassDefinition.IS_NATIVE) != 0) {
return false;
}
return true;
}
// reset stash
this.getCompiler().forEachClassDef(function (parser, classDef) {
this.resetStash(classDef);
return classDef.forEachMember(function (member) {
this.resetStash(member);
return true;
});
});
// all non-final native methods should be preserved in the instantiated classes
this.getCompiler().forEachClassDef(function (parser, classDef) {
if (! (classDef instanceof TemplateClassDefinition)
&& (classDef.flags() & ClassDefinition.IS_NATIVE) != 0) {
classDef.forEachMemberFunction(function (funcDef) {
if (funcDef.name() == "constructor") {
// skip
} else if ((funcDef.flags() & ClassDefinition.IS_FINAL) != 0) {
// skip
} else {
this._touchMethod(funcDef.name(), funcDef.getArgumentTypes());
}
return true;
});
}
return true;
});
// push all exported members
this.getCompiler().forEachClassDef(function (parser, classDef) {
if (isEmittedClass(classDef)) {
if ((classDef.flags() & ClassDefinition.IS_EXPORT) != 0) {
this._touchInstance(classDef);
}
classDef.forEachMember(function (member) {
if ((member.flags() & ClassDefinition.IS_EXPORT) != 0) {
if ((member.flags() & ClassDefinition.IS_STATIC) != 0) {
this._touchStatic(member);
} else if (member instanceof MemberFunctionDefinition) {
var funcDef = member as MemberFunctionDefinition;
if (funcDef.name() == "constructor") {
this._touchConstructor(funcDef);
} else {
this._touchMethod(funcDef.name(), funcDef.getArgumentTypes());
}
}
}
return true;
});
}
return true;
});
// check all members
while (this._membersToWalk.length != 0) {
var member = this._membersToWalk.shift();
this.log("walking " + member.getNotation());
if (member instanceof MemberFunctionDefinition) {
this._walkFunctionDefinition(member as MemberFunctionDefinition);
} else {
this._walkVariableDefinition(member as MemberVariableDefinition);
}
}
// remove things that aren't used
function memberShouldPreserve(member : MemberDefinition) : boolean {
if ((member.flags() & ClassDefinition.IS_EXPORT) != 0) {
return true;
}
var isTouched = (this.getStash(member) as _StripOptimizeCommand._Stash).touched;
if ((member.flags() & ClassDefinition.IS_STATIC) != 0) {
return isTouched;
} else if (member instanceof MemberFunctionDefinition) {
if (member.name() == "constructor") {
return isTouched;
} else {
if ((this.getStash(member.getClassDef()) as _StripOptimizeCommand._Stash).touched
&& this._methodsAlive.hasOwnProperty(member.name())) {
var listOfArgTypes = this._methodsAlive[member.name()];
for (var i = 0; i != listOfArgTypes.length; ++i) {
if (Util.typesAreEqual(listOfArgTypes[i], (member as MemberFunctionDefinition).getArgumentTypes())) {
return true;
}
}
}
return false;
}
}
return true;
}
this.getCompiler().forEachClassDef(function (parser, classDef) {
if (isEmittedClass(classDef)) {
var numConstructors = 0;
var members = classDef.members();
for (var memberIndex = 0; memberIndex != members.length;) {
var member = members[memberIndex];
if (memberShouldPreserve(member)) {
if (member instanceof MemberFunctionDefinition
&& (member.flags() & ClassDefinition.IS_STATIC) == 0
&& member.name() == "constructor") {
++numConstructors;
}
++memberIndex;
this.log("preserving used: " + member.getNotation());
} else {
this.log("removing unused: " + member.getNotation());
members.splice(memberIndex, 1);
}
}
if (numConstructors == 0) {
// create a fake constructor (should never get called; TODO leave as is, emit "{}" in jsemitter upon facing a class wo. any ctors)
// the reason for not deleting the entire class is because it could be referred to by "instanceof"
this.log("substituting fake constructor for class: " + classDef.className());
var ctor = new MemberFunctionDefinition(
null,
new Token("constructor", true),
ClassDefinition.IS_FINAL | (classDef.flags() & ClassDefinition.IS_EXPORT),
Type.voidType,
new ArgumentDeclaration[],
new LocalVariable[],
new Statement[],
new MemberFunctionDefinition[],
classDef.getToken(), /* FIXME */
null);
ctor.setClassDef(classDef);
members.push(ctor);
}
}
return true;
});
// remove unused native classes (with nativeSource)
this.getCompiler().getParsers().forEach(function (parser) {
var classDefs = parser.getClassDefs();
for (var i = 0; i != classDefs.length;) {
var preserve = true;
if ((classDefs[i].flags() & ClassDefinition.IS_NATIVE) != 0
&& classDefs[i].getNativeSource() != null
&& ! (this.getStash(classDefs[i]) as _StripOptimizeCommand._Stash).touched
&& classDefs[i].forEachMember(function (member) {
if ((member.flags() & ClassDefinition.IS_STATIC) == 0)
return true;
return ! (this.getStash(member) as _StripOptimizeCommand._Stash).touched;
})) {
preserve = false;
}
if (preserve) {
++i;
} else {
this.log("removing unused native class: " + classDefs[i].className());
classDefs.splice(i, 1);
}
}
});
}
function _walkExpression(expr : Expression) : boolean {
function onExpr(expr : Expression) : boolean {
if (expr instanceof NewExpression) {
var callee = Util.findFunctionInClass(expr.getType().getClassDef(), "constructor", (expr as NewExpression).getConstructor().getArgumentTypes(), false);
assert callee != null;
this._touchConstructor(callee);
} else if (expr instanceof InstanceofExpression) {
this._touchInstance((expr as InstanceofExpression).getExpectedType().getClassDef());
} else if (expr instanceof AsExpression) {
if (expr.getType() instanceof ObjectType) {
this._touchInstance(expr.getType().getClassDef());
}
} else if (expr instanceof AsNoConvertExpression) {
if (expr.getType() instanceof ObjectType) {
this._touchInstance(expr.getType().getClassDef());
}
} else if (expr instanceof PropertyExpression) {
var propertyExpr = expr as PropertyExpression;
var name = propertyExpr.getIdentifierToken().getValue();
if (propertyExpr.getExpr() instanceof ClassExpression) {
if (Util.isReferringToFunctionDefinition(propertyExpr)) {
var member : MemberDefinition = Util.findFunctionInClass(
propertyExpr.getHolderType().getClassDef(),
name,
(expr.getType() as ResolvedFunctionType).getArgumentTypes(),
true);
assert member != null;
} else {
member = Util.findVariableInClass(propertyExpr.getHolderType().getClassDef(), name, true);
assert member != null;
}
this._touchStatic(member);
} else {
if (Util.isReferringToFunctionDefinition(propertyExpr)) {
this._touchMethod(name, (expr.getType() as ResolvedFunctionType).getArgumentTypes());
}
}
} else if (expr instanceof SuperExpression) {
var superExpr = expr as SuperExpression;
this._touchMethod(superExpr.getName().getValue(), (superExpr.getFunctionType() as ResolvedFunctionType).getArgumentTypes());
}
return expr.forEachExpression(onExpr);
}
return onExpr(expr);
}
function _walkStatement(statement : Statement) : boolean {
function onStatement(statement : Statement) : boolean {
if (statement instanceof ConstructorInvocationStatement) {
var ctorStatement = statement as ConstructorInvocationStatement;
var callee = Util.findFunctionInClass(
ctorStatement.getConstructingClassDef(),
"constructor",
(ctorStatement.getConstructorType() as ResolvedFunctionType).getArgumentTypes(),
false);
assert callee != null;
this._touchConstructor(callee);
}
statement.forEachExpression(function (expr) {
return this._walkExpression(expr);
});
return statement.forEachStatement(onStatement);
}
return onStatement(statement);
}
function _walkFunctionDefinition(funcDef : MemberFunctionDefinition) : boolean {
if (funcDef.getStatements() != null) {
funcDef.forEachStatement(function onStatement(statement) {
return this._walkStatement(statement);
});
}
return funcDef.forEachClosure(function (funcDef) {
return this._walkFunctionDefinition(funcDef);
});
}
function _walkVariableDefinition(varDef : MemberVariableDefinition) : boolean {
var initialValue = varDef.getInitialValue();
if (initialValue != null) {
this._walkExpression(initialValue);
}
return varDef.forEachClosure(function (funcDef) {
return this._walkFunctionDefinition(funcDef);
});
}
}
class _NoAssertCommand extends _FunctionOptimizeCommand {
static const IDENTIFIER = "no-assert";
function constructor () {
super(_NoAssertCommand.IDENTIFIER);
}
override function optimizeFunction (funcDef : MemberFunctionDefinition) : boolean {
this._optimizeStatements(funcDef.getStatements());
return true;
}
function _optimizeStatements (statements : Statement[]) : void {
function optimize (statements : Statement[]) : boolean {
for (var i = 0; i < statements.length;) {
if (statements[i] instanceof AssertStatement) {
statements.splice(i, 1);
} else {
_Util.handleSubStatements(optimize, statements[i]);
++i;
}
}
return false;
}
optimize(statements);
}
}
// CONVERSION ERROR: wrong name for exporting: _NoLogCommand and _NoAssertCommand
class _NoLogCommand extends _FunctionOptimizeCommand {
static const IDENTIFIER = "no-log";
function constructor () {
super(_NoLogCommand.IDENTIFIER);
}
override function optimizeFunction (funcDef : MemberFunctionDefinition) : boolean {
this._optimizeStatements(funcDef.getStatements());
return true;
}
function _optimizeStatements (statements : Statement[]) : void {
function optimize (statements : Statement[]) : boolean {
for (var i = 0; i < statements.length;) {
if (statements[i] instanceof LogStatement) {
statements.splice(i, 1);
} else {
_Util.handleSubStatements(optimize, statements[i]);
++i;
}
}
return false;
}
optimize(statements);
}
}
class _DetermineCalleeCommand extends _FunctionOptimizeCommand {
static const IDENTIFIER = "determine-callee";
class Stash extends Stash {
var callingFuncDef : MemberFunctionDefinition;
function constructor () {
this.callingFuncDef = null;
}
function constructor (that : _DetermineCalleeCommand.Stash) {
this.callingFuncDef = that.callingFuncDef;
}
override function clone () : Stash {
return new _DetermineCalleeCommand.Stash(this);
}
}
function constructor () {
super(_DetermineCalleeCommand.IDENTIFIER);
}
override function _createStash () : Stash {
return new _DetermineCalleeCommand.Stash();
}
override function optimizeFunction (funcDef : MemberFunctionDefinition) : boolean {
funcDef.forEachStatement(function onStatement(statement : Statement) : boolean {
if (statement instanceof ConstructorInvocationStatement) {
// invocation of super-class ctor
var callingFuncDef = _DetermineCalleeCommand.findCallingFunctionInClass(
(statement as ConstructorInvocationStatement).getConstructingClassDef(),
"constructor",
((statement as ConstructorInvocationStatement).getConstructorType() as ResolvedFunctionType).getArgumentTypes(),
false);
if (callingFuncDef == null)
throw new Error("could not determine the associated parent ctor");
this._setCallingFuncDef(statement, callingFuncDef);
} else if (statement instanceof FunctionStatement) {
(statement as FunctionStatement).getFuncDef().forEachStatement(onStatement);
}
statement.forEachExpression(function onExpr(expr : Expression) : boolean {
if (expr instanceof CallExpression) {
// call expression
var calleeExpr = (expr as CallExpression).getExpr();
if (calleeExpr instanceof PropertyExpression && ! (calleeExpr as PropertyExpression).getType().isAssignable()) {
var propertyExpr = calleeExpr as PropertyExpression;
// is referring to function (not a value of function type)
var holderType = propertyExpr.getHolderType();
var callingFuncDef = _DetermineCalleeCommand.findCallingFunction(
holderType.getClassDef(),
propertyExpr.getIdentifierToken().getValue(),
(propertyExpr.getType() as ResolvedFunctionType).getArgumentTypes(),
propertyExpr.getExpr() instanceof ClassExpression);
this._setCallingFuncDef(expr, callingFuncDef);
} else if (calleeExpr instanceof FunctionExpression) {
this._setCallingFuncDef(expr, (calleeExpr as FunctionExpression).getFuncDef());
} else {
this._setCallingFuncDef(expr, null);
}
} else if (expr instanceof NewExpression) {
var callingFuncDef = _DetermineCalleeCommand.findCallingFunctionInClass(
(expr as NewExpression).getType().getClassDef(), "constructor", (expr as NewExpression).getConstructor().getArgumentTypes(), false);
if (callingFuncDef == null) {
throw new Error("could not find matching constructor for " + (expr as NewExpression).getConstructor().toString());
}
this._setCallingFuncDef(expr as NewExpression, callingFuncDef);
}
if (expr instanceof FunctionExpression) {
return (expr as FunctionExpression).getFuncDef().forEachStatement(onStatement);
} else {
return expr.forEachExpression(onExpr);
}
});
return statement.forEachStatement(onStatement);
});
return true;
}
function _setCallingFuncDef (stashable : Stashable, funcDef : MemberFunctionDefinition) : void {
(this.getStash(stashable) as _DetermineCalleeCommand.Stash).callingFuncDef = funcDef;
}
static function findCallingFunctionInClass (classDef : ClassDefinition, funcName : string, argTypes : Type[], isStatic : boolean) : MemberFunctionDefinition {
var found = Util.findFunctionInClass(classDef, funcName, argTypes, isStatic);
// only return if the found function is final
if (found != null) {
if ((found.flags() & (ClassDefinition.IS_STATIC | ClassDefinition.IS_FINAL)) == 0)
found = null;
}
return found;
}
static function findCallingFunction (classDef : ClassDefinition, funcName : string, argTypes : Type[], isStatic : boolean) : MemberFunctionDefinition {
var found = null : MemberFunctionDefinition;
// find the first declaration
classDef.forEachClassToBase(function (classDef) {
if ((found = _DetermineCalleeCommand.findCallingFunctionInClass(classDef, funcName, argTypes, isStatic)) != null)
return false;
return true;
});
return found;
}
static function getCallingFuncDef (stashable : Stashable) : MemberFunctionDefinition {
var stash = stashable.getStash(_DetermineCalleeCommand.IDENTIFIER) as _DetermineCalleeCommand.Stash;
if (stash == null)
throw new Error("callee not searched");
return stash.callingFuncDef;
}
}
class _StaticizeOptimizeCommand extends _OptimizeCommand {
static const IDENTIFIER = "staticize";
function constructor () {
super(_StaticizeOptimizeCommand.IDENTIFIER);
}
class Stash extends Stash {
var altName : Nullable.<string>;
var altLocal : LocalVariable;
var altFuncDef : MemberFunctionDefinition;
function constructor () {
this.altName = null;
this.altLocal = null;
this.altFuncDef = null;
}
function constructor (that : _StaticizeOptimizeCommand.Stash) {
this.altName = that.altName;
this.altLocal = that.altLocal;
this.altFuncDef = that.altFuncDef;
}
override function clone () : _StaticizeOptimizeCommand.Stash {
return new _StaticizeOptimizeCommand.Stash(this);
}
}
override function _createStash () : Stash {
return new _StaticizeOptimizeCommand.Stash();
}
override function performOptimization () : void {
function memberCanBeStaticized(funcDef : MemberFunctionDefinition) : boolean {
return (funcDef.flags() & (ClassDefinition.IS_OVERRIDE | ClassDefinition.IS_ABSTRACT | ClassDefinition.IS_FINAL | ClassDefinition.IS_STATIC | ClassDefinition.IS_NATIVE)) == ClassDefinition.IS_FINAL
&& funcDef.name() != "constructor";
}
this.getCompiler().forEachClassDef(function (parser, classDef) {
// skip interfaces and mixins
if ((classDef.flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) != 0)
return true;
// convert function definitions (expect constructor) to static
classDef.forEachMemberFunction(function onFunction(funcDef : MemberFunctionDefinition) : boolean {
if (memberCanBeStaticized(funcDef)) {
this.log("staticizing method: " + funcDef.name());
this._staticizeMethod(funcDef);
}
return true;
});
return true;
});
// rewrite member method invocations to static function calls
this.getCompiler().forEachClassDef(function (parser, classDef) {
this.log("rewriting member method calls in class: " + classDef.className());
// rewrite member variables
classDef.forEachMemberVariable(function (varDef) {
if (varDef.getInitialValue() == null)
return true;
this._rewriteMethodCallsToStatic(varDef.getInitialValue(), function (expr) {
varDef.setInitialValue(expr);
}, null);
return true;
});
// rewrite member functions
function onFunction (funcDef : MemberFunctionDefinition) : boolean {
function onStatement (statement : Statement) : boolean {
statement.forEachExpression(function (expr, replaceCb) {
this._rewriteMethodCallsToStatic(expr, replaceCb, funcDef);
return true;
});
return statement.forEachStatement(onStatement);
}
funcDef.forEachStatement(onStatement);
return funcDef.forEachClosure(onFunction);
}
classDef.forEachMemberFunction(onFunction);
return true;
});
}
function _staticizeMethod (funcDef : MemberFunctionDefinition) : void {
var staticFuncDef = this._cloneFuncDef(funcDef);
// register to the classDef
var classDef = funcDef.getClassDef();
staticFuncDef.setClassDef(classDef);
classDef._members.splice(classDef._members.indexOf(funcDef)+1, 0, staticFuncDef); // insert right after the original function
// rename
var newName = this._findFrechFunctionName(classDef, funcDef.name(), ([ new ObjectType(classDef) ] : Type[]).concat((funcDef.getType() as ResolvedFunctionType).getArgumentTypes()), true);
(this.getStash(funcDef) as _StaticizeOptimizeCommand.Stash).altName = newName;
staticFuncDef._nameToken = new Token(newName, true);
// update flags
staticFuncDef.setFlags(funcDef.flags() | ClassDefinition.IS_STATIC);
// first argument should be this
var thisArg = new ArgumentDeclaration(new Token("$this", false), new ObjectType(classDef));
staticFuncDef.getArguments().unshift(thisArg);
// rewrite this
staticFuncDef.forEachStatement(function onStatement(statement : Statement) : boolean {
if (statement instanceof FunctionStatement) {
(statement as FunctionStatement).getFuncDef().forEachStatement(onStatement);
}
return statement.forEachExpression(function onExpr(expr : Expression, replaceCb : function(:Expression):void) : boolean {
if (expr instanceof ThisExpression) {
replaceCb(new LocalExpression(thisArg.getName(), thisArg));
} else if (expr instanceof FunctionExpression) {
return (expr as FunctionExpression).getFuncDef().forEachStatement(onStatement);
}
return expr.forEachExpression(onExpr);
}) && statement.forEachStatement(onStatement);
});
}
function _cloneFuncDef (funcDef : MemberFunctionDefinition) : MemberFunctionDefinition {
function cloneFuncDef (funcDef : MemberFunctionDefinition) : MemberFunctionDefinition {
// at this moment, all locals and closures are not cloned yet
var statements = Cloner.<Statement>.cloneArray(funcDef.getStatements());
var closures = funcDef.getClosures().map.<MemberFunctionDefinition>((funcDef) -> {
var newFuncDef = cloneFuncDef(funcDef);
(this.getStash(funcDef) as _StaticizeOptimizeCommand.Stash).altFuncDef = newFuncDef;
return newFuncDef;
});
// rewrite funcDefs
Util.forEachStatement(function onStatement(statement : Statement) : boolean {
if (statement instanceof FunctionStatement) {
var altFuncDef;
if ((altFuncDef = (this.getStash((statement as FunctionStatement).getFuncDef()) as _StaticizeOptimizeCommand.Stash).altFuncDef) != null) {
(statement as FunctionStatement).setFuncDef(altFuncDef);
}
return true;
}
return statement.forEachExpression(function onExpr(expr : Expression, replaceCb : function(:Expression):void) : boolean {
if (expr instanceof FunctionExpression) {
var altFuncDef;
if ((altFuncDef = (this.getStash((expr as FunctionExpression).getFuncDef()) as _StaticizeOptimizeCommand.Stash).altFuncDef) != null) {
(expr as FunctionExpression).setFuncDef(altFuncDef);
}
return true;
}
return expr.forEachExpression(onExpr);
}) && statement.forEachStatement(onStatement);
}, statements);
var funcLocal = funcDef.getFuncLocal();
if (funcLocal != null) {
var newFuncLocal;
if ((newFuncLocal = (this.getStash(funcLocal) as _StaticizeOptimizeCommand.Stash).altLocal) != null) { // funcDef is defined as a function statement
// ok
} else {
// clone
newFuncLocal = new LocalVariable(funcLocal.getName(), funcLocal.getType());
(this.getStash(funcLocal) as _StaticizeOptimizeCommand.Stash).altLocal = newFuncLocal;
}
funcLocal = newFuncLocal;
}
var args = funcDef.getArguments().map.<ArgumentDeclaration>((arg) -> {
var newArg = arg.clone();
(this.getStash(arg) as _StaticizeOptimizeCommand.Stash).altLocal = newArg;
return newArg;
});
var locals = funcDef.getLocals().map.<LocalVariable>((local) -> {
var newLocal;
if ((newLocal = (this.getStash(local) as _StaticizeOptimizeCommand.Stash).altLocal) != null) {
// in case local is a name of a function statement and the function statement already cloned
return newLocal;
}
newLocal = new LocalVariable(local.getName(), local.getType());
(this.getStash(local) as _StaticizeOptimizeCommand.Stash).altLocal = newLocal;
return newLocal;
});
// FIXME special hack: CatchStatement#clone does not clone and rewrite its caught variable
Util.forEachStatement(function onStatement(statement : Statement) : boolean {
if (statement instanceof CatchStatement) {
var caughtVar = (statement as CatchStatement).getLocal().clone();
(this.getStash((statement as CatchStatement).getLocal()) as _StaticizeOptimizeCommand.Stash).altLocal = caughtVar;
(statement as CatchStatement).setLocal(caughtVar);
} else if (statement instanceof FunctionStatement) {
(statement as FunctionStatement).getFuncDef().forEachStatement(onStatement);
}
return statement.forEachExpression(function onExpr(expr, replaceCb) {
if (expr instanceof FunctionExpression) {
return (expr as FunctionExpression).getFuncDef().forEachStatement(onStatement);
}
return expr.forEachExpression(onExpr);
}) && statement.forEachStatement(onStatement);
}, statements);
// rewrite locals
Util.forEachStatement(function onStatement(statement : Statement) : boolean {
if (statement instanceof FunctionStatement) {
(statement as FunctionStatement).getFuncDef().forEachStatement(onStatement);
}
return statement.forEachExpression(function onExpr(expr : Expression, replaceCb : function(:Expression):void) : boolean {
if (expr instanceof LocalExpression) {
var altLocal;
if ((altLocal = (this.getStash((expr as LocalExpression).getLocal()) as _StaticizeOptimizeCommand.Stash).altLocal) != null) {
(expr as LocalExpression).setLocal(altLocal);
}
} else if (expr instanceof FunctionExpression) {
return (expr as FunctionExpression).getFuncDef().forEachStatement(onStatement);
}
return expr.forEachExpression(onExpr);
}) && statement.forEachStatement(onStatement);
}, statements);
var clonedFuncDef = new MemberFunctionDefinition(
funcDef.getToken(),
funcDef.getNameToken(),
funcDef.flags(),
funcDef.getReturnType(),
args,
locals,
statements,
closures,
funcDef._lastTokenOfBody,
null
);
clonedFuncDef.setFuncLocal(funcLocal);
return clonedFuncDef;
}
return cloneFuncDef(funcDef);
}
function _findFrechFunctionName (classDef : ClassDefinition, baseName : string, argTypes : Type[], isStatic : boolean) : string {
var index = 0;
// search for a name which does not conflict with existing function
do {
var newName = Util.format("%1_%2", [ baseName, index as string ]);
++index;
} while (Util.findFunctionInClass(classDef, newName, argTypes, isStatic) != null);
return newName;
}
function _rewriteMethodCallsToStatic (expr : Expression, replaceCb : function(:Expression):void, rewritingFuncDef : MemberFunctionDefinition) : void {
function onExpr(expr : Expression, replaceCb : function(:Expression):void) : boolean {
if (expr instanceof CallExpression) {
var calleeExpr = (expr as CallExpression).getExpr();
if (calleeExpr instanceof PropertyExpression
&& ! ((calleeExpr as PropertyExpression).getExpr() instanceof ClassExpression)
&& ! (calleeExpr as PropertyExpression).getType().isAssignable()) {
var propertyExpr = calleeExpr as PropertyExpression;
// is a member method call
var receiverType = propertyExpr.getExpr().getType().resolveIfNullable();
// skip interfaces and mixins
if ((receiverType.getClassDef().flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) == 0) {
var funcDef = this._findFunctionInClassTree(receiverType.getClassDef(), propertyExpr.getIdentifierToken().getValue(), (propertyExpr.getType() as ResolvedFunctionType).getArgumentTypes(), false);
// funcDef is staticized
var newName;
if (funcDef != null && (newName = (this.getStash(funcDef) as _StaticizeOptimizeCommand.Stash).altName) != null) {
// found, rewrite
onExpr(propertyExpr.getExpr(), function (expr) {
propertyExpr.setExpr(expr);
});
Util.forEachExpression(onExpr, (expr as CallExpression).getArguments());
replaceCb(
new CallExpression(
expr.getToken(),
new PropertyExpression(
propertyExpr.getToken(),
new ClassExpression(new Token(funcDef.getClassDef().className(), true), new ObjectType(funcDef.getClassDef())),
new Token(newName, true),
propertyExpr.getTypeArguments(),
new StaticFunctionType(null, (funcDef.getType() as ResolvedFunctionType).getReturnType(), ([ new ObjectType(funcDef.getClassDef()) ] : Type[]).concat((funcDef.getType() as ResolvedFunctionType).getArgumentTypes()), false)),
[ propertyExpr.getExpr() ].concat((expr as CallExpression).getArguments())));
return true;
}
}
}
} else if (expr instanceof SuperExpression) {
var superExpr = expr as SuperExpression;
var classDef = superExpr.getFunctionType().getObjectType().getClassDef();
funcDef = this._findFunctionInClassTree(classDef, superExpr.getName().getValue(), (superExpr.getFunctionType() as ResolvedFunctionType).getArgumentTypes(), false);
// funcDef is staticized
if (funcDef != null && (newName = (this.getStash(funcDef) as _StaticizeOptimizeCommand.Stash).altName) != null) {
// found, rewrite
Util.forEachExpression(onExpr, superExpr.getArguments());
var thisVar : Expression;
if ((rewritingFuncDef.flags() & ClassDefinition.IS_STATIC) != 0) {
// super expression in static function means that the function has been staticized
var thisArg = rewritingFuncDef.getArguments()[0];
thisVar = new LocalExpression(thisArg.getName(), thisArg);
} else {
thisVar = new ThisExpression(new Token("this", false), funcDef.getClassDef());
}
replaceCb(
new CallExpression(
expr.getToken(),
new PropertyExpression(
superExpr.getToken(),
new ClassExpression(new Token(funcDef.getClassDef().className(), true), new ObjectType(funcDef.getClassDef())),
new Token(newName, true),
[], // type args
new StaticFunctionType(null, (funcDef.getType() as ResolvedFunctionType).getReturnType(), ([ new ObjectType(funcDef.getClassDef()) ] : Type[]).concat((funcDef.getType() as ResolvedFunctionType).getArgumentTypes()), false)),
[ thisVar ].concat(superExpr.getArguments())));
return true;
}
}
return expr.forEachExpression(onExpr);
}
onExpr(expr, replaceCb);
}
function _findFunctionInClassTree (classDef : ClassDefinition, name : string, argTypes : Type[], isStatic : boolean) : MemberFunctionDefinition {
var funcDef;
while (classDef.className() != "Object") {
if ((funcDef = Util.findFunctionInClass(classDef, name, argTypes, isStatic)) != null) {
return funcDef;
}
classDef = classDef.extendType().get