diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/eliminator.js | 523 | ||||
-rw-r--r-- | tools/eliminator/eliminator.coffee | 429 | ||||
-rw-r--r-- | tools/shared.py | 17 |
3 files changed, 0 insertions, 969 deletions
diff --git a/tools/eliminator.js b/tools/eliminator.js deleted file mode 100644 index e6c9f808..00000000 --- a/tools/eliminator.js +++ /dev/null @@ -1,523 +0,0 @@ -/* - A script to eliminate redundant variables common in Emscripted code. - - A variable === eliminateable if (it matches a leaf of that condition tree: - - Single-def - Uses only side-effect-free nodes - Unused - * - Has at most MAX_USES uses - No mutations to any dependencies between def && last use - No global dependencies or no indirect accesses between def && use - * - - TODO(max99x): Eliminate single-def undefined-initialized vars with no uses - between declaration && definition. -*/ - -// Imports. -var uglify = require('../tools/eliminator/node_modules/uglify-js'); -var fs = require('fs'); -var os = require('os'); - -function set() { - var args = typeof arguments[0] === 'object' ? arguments[0] : arguments; - var ret = {}; - for (var i = 0; i < args.length; i++) { - ret[args[i]] = 0; - } - return ret; -} - -// Functions which have been generated by Emscripten. We optimize only those. -var generatedFunctions = {}; -var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS:'; -function isGenerated (ident) { - return ident in generatedFunctions; -} - -// Maximum number of uses to consider a variable not worth eliminating. -// The risk with using > 1 === that a variable used by another can have -// a chain that leads to exponential uses -var MAX_USES = 1; - -// The UglifyJs code generator settings to use. -var GEN_OPTIONS = { - ascii_only: true, - beautify: true, - indent_level: 2 -}; - -// Node types which can be evaluated without side effects. -var NODES_WITHOUT_SIDE_EFFECTS = { - name: true, - num: true, - string: true, - binary: true, - sub: true, - 'unary-prefix': true // ++x can have side effects, but we never have that in generated code -}; - -// Nodes which may break control flow. Moving a variable beyond them may have -// side effects. -var CONTROL_FLOW_NODES = { - new: true, - throw: true, - call: true, - label: true, - debugger: true -}; - -var ANALYZE_BLOCK_TYPES = { - 'switch': true, - 'if': true, - 'try': true, - 'do': true, - 'while': true, - 'for': true, - 'for-in': true -}; - -// Traverses a JavaScript syntax tree rooted at the given node calling the given -// callback for each node. -// that.arg node: The root of the AST. -// that.arg callback: The callback to call for each node. This will be called with -// the node as the first argument && its type as the second. If false is -// returned, the traversal === stopped. If a non-undefined value === returned, -// it replaces the passed node in the tree. -// that.returns: If the root node was replaced, the new root node. If the traversal -// was stopped, false. Otherwise undefined. -function traverse(node, callback) { - var type = node[0]; - if (typeof type == 'string') { - result = callback(node, type); - if (result) return result; - } - for (var index = 0; index < node.length; index++) { - var subnode = node[index]; - if (subnode && typeof subnode == 'object' && subnode.length) { - // NOTE: For-in nodes have unspecified var mutations. Skip them. - if (type == 'for-in' && subnode[0] == 'var') continue; - var subresult = traverse(subnode, callback); - if (subresult === false) { - return false; - } else if (subresult) { - node[index] = subresult; - } - } - } -} - -// A class for eliminating redundant variables from JavaScript. Give it an AST -// function/defun node && call run() to apply the optimization (in-place). -var that = that; // there is always a single global Eliminator instance -function Eliminator(func) { - that = this; - // The statements of the function to analyze. - this.body = func[3]; - - // Identifier stats. Each of these objects === indexed by the identifier name. - // Whether the identifier === a local variable. - this.isLocal = {}; - // Whether the identifier === never modified after initialization. - this.isSingleDef = {}; - // How many times the identifier === used. - this.useCount = {}; - // Whether the initial value of a single-def identifier uses only nodes - // evaluating which has no side effects. - this.usesOnlySimpleNodes = {}; - // Whether the identifier depends on any non-local name, perhaps indirectly. - this.dependsOnAGlobal = {}; - // Whether the dependencies of the single-def identifier may be mutated - // within its live range. - this.depsMutatedInLiveRange = {}; - // Maps a given single-def variable to the AST expression of its initial value. - this.initialValue = {}; - // Maps identifiers to single-def variables which reference it in their - // initial value, i.e., which other variables it affects. - this.affects = {}; - - // Runs the eliminator on a given function body updating the AST in-place. - // that.returns: The number of variables eliminated, or undefined if (skipped. - this.run = function() { - this.calculateBasicVarStats(); - this.analyzeInitialValues(); - this.calculateTransitiveDependencies(); - this.analyzeLiveRanges(); - - var toReplace = {}; - var eliminated = 0; - for (var varName in this.isSingleDef) { - if (this.isEliminateable(varName)) { - toReplace[varName] = this.initialValue[varName]; - eliminated++; - } - } - - this.removeDeclarations(toReplace); - this.collapseValues(toReplace); - this.updateUses(toReplace); - - return eliminated; - }; - - // Runs the basic variable scan pass. Fills the following member variables: - // isLocal - // isSingleDef - // useCount - // initialValue - this.calculateBasicVarStats = function() { - traverse(this.body, function(node, type) { - if (type === 'var') { - var node1 = node[1]; - for (var i = 0; i < node1.length; i++) { - var node1i = node1[i]; - var varName = node1i[0]; - var varValue = node1i[1]; - that.isLocal[varName] = true; - if (!varValue) varValue = ['name', 'undefined']; // XXX share? - that.isSingleDef[varName] = !that.isSingleDef.hasOwnProperty(varName); - that.initialValue[varName] = varValue; - that.useCount[varName] = 0; - } - } else if (type === 'name') { - varName = node[1]; - if (that.useCount.hasOwnProperty(varName)) that.useCount[varName]++; - else that.isSingleDef[varName] = false; - } else if (type == 'assign') { - varName = node[2][1]; - if (that.isSingleDef[varName]) that.isSingleDef[varName] = false; - } - }); - }; - - // Analyzes the initial values of single-def variables. Requires basic variable - // stats to have been calculated. Fills the following member variables: - // affects - // dependsOnAGlobal - // usesOnlySimpleNodes - this.analyzeInitialValues = function() { - for (var varName in this.isSingleDef) { - if (!this.isSingleDef[varName]) continue; - this.usesOnlySimpleNodes[varName] = true; - traverse(this.initialValue[varName], function(node, type) { - if (!(type in NODES_WITHOUT_SIDE_EFFECTS)) { - that.usesOnlySimpleNodes[varName] = false; - } else if (type === 'name') { - var reference = node[1]; - if (reference != 'undefined') { - if (!that.affects[reference]) that.affects[reference] = {}; - if (!that.isLocal[reference]) that.dependsOnAGlobal[varName] = true; - that.affects[reference][varName] = true; - } - } - }); - } - }; - - // Updates the dependency graph (@affects) to its transitive closure && - // synchronizes this.dependsOnAGlobal to the new dependencies. - this.calculateTransitiveDependencies = function() { - var incomplete = true; - var todo = {}; - for (var element in this.affects) { - todo[element] = 1; - } - - //process.stdout.write 'pre ' + JSON.stringify(@affects, null, ' ') + '\n' - - while (incomplete) { - incomplete = false; - var nextTodo = {}; - for (var source in this.affects) { - var targets = this.affects[source]; - for (var target in targets) { - if (todo[target]) { - var this_affects_target = this.affects[target]; - for (target2 in this_affects_target) { - if (!targets[target2]) { - if (!this.isLocal[source]) this.dependsOnAGlobal[target2] = true; - targets[target2] = true; - nextTodo[source] = 1; - incomplete = true; - } - } - } - } - } - todo = nextTodo; - } - - //process.stdout.write 'post ' + JSON.stringify(@affects, null, ' ') + '\n' - }; - - // Analyzes the live ranges of single-def variables. Requires dependencies to - // have been calculated. Fills the following member variables: - // depsMutatedInLiveRange - this.analyzeLiveRanges = function() { - var isLive = {}; - - // Checks if (a given node may mutate any of the currently live variables. - function checkForMutations(node, type) { - var usedInThisStatement = {}; - if (type == 'assign' || type == 'call') { - traverse(node.slice(2, 4), function(node, type) { - if (type === 'name') usedInThisStatement[node[1]] = true; - }); - } - - if (type == 'assign' || type == 'unary-prefix' || type == 'unary-postfix') { - if (type === 'assign' || node[1] == '--' || node[1] == '++') { - var reference = node[2]; - while (reference[0] != 'name') { - reference = reference[1]; - } - reference = reference[1]; - var aff = that.affects[reference] - if (aff) { - for (var varName in aff) { - if (isLive[varName]) { - isLive[varName] = false; - } - } - } - } - } - - if (type in CONTROL_FLOW_NODES) { - for (var varName in isLive) { - if (that.dependsOnAGlobal[varName] || !usedInThisStatement[varName]) { - isLive[varName] = false; - } - } - } else if (type === 'assign') { - for (var varName in isLive) { - if (that.dependsOnAGlobal[varName] && !usedInThisStatement[varName]) { - isLive[varName] = false; - } - } - } else if (type === 'name') { - var reference = node[1]; - if (that.isSingleDef[reference]) { - if (!isLive[reference]) { - that.depsMutatedInLiveRange[reference] = true; - } - } - } - } - - // Analyzes a block && all its children for variable ranges. Makes sure to - // account for the worst case of possible mutations. - function analyzeBlock(node, type) { - if (type in ANALYZE_BLOCK_TYPES) { - function traverseChild(child) { - if (child && typeof child == 'object' && child.length) { - var savedLive = {}; - for (var name in isLive) savedLive[name] = true; - traverse(child, analyzeBlock); - for (var name in isLive) { - if (!isLive[name]) savedLive[name] = false; - } - isLive = savedLive; - } - } - if (type === 'switch') { - traverseChild(node[1]); - var node2 = node[2]; - for (var i = 0; i < node2.length; i++) { - traverseChild(node2[i]); - } - } else if (type == 'if' || type == 'try') { - for (var i = 0; i < node.length; i++) { - traverseChild(node[i]); - } - } else { - // Don't put anything from outside into the body of a loop. - isLive = {}; - node.forEach(traverseChild); - // Don't keep anything alive through a loop - isLive = {}; - } - return node; - } else if (type === 'var') { - var node1 = node[1]; - for (var i = 0; i < node1.length; i++) { - var node1i = node1[i]; - var varName = node1i[0]; - var varValue = node1i[1]; - if (varValue) traverse(varValue, checkForMutations); - // Mark the variable as live - if (that.isSingleDef[varName]) { - isLive[varName] = true; - } - // Mark variables that depend on it as no longer live - if (that.affects[varName]) { - var aff = that.affects[varName]; - for (var varNameDep in aff) { - if (isLive[varNameDep]) { - isLive[varNameDep] = false; - } - } - } - } - return node; - } else { - checkForMutations(node, type); - } - } - - traverse(this.body, analyzeBlock); - }; - - // Determines whether a given variable can be safely eliminated. Requires all - // analysis passes to have been run. - this.isEliminateable = function(varName) { - if (this.isSingleDef[varName] && this.usesOnlySimpleNodes[varName]) { - if (this.useCount[varName] == 0) { - return true; - } else if (this.useCount[varName] <= MAX_USES) { - return !this.depsMutatedInLiveRange[varName]; - } - } - return false; - }; - - // Removes all var declarations for the specified variables. - // this.arg toRemove: An object whose keys are the variable names to remove. - this.removeDeclarations = function(toRemove) { - traverse(this.body, function(node, type) { - if (type === 'var') { - var intactVars = node[1].filter(function(i) { return !toRemove.hasOwnProperty(i[0]) }); - if (intactVars.length) { - node[1] = intactVars; - return node; - } else { - return ['toplevel', []]; - } - } - }); - }; - - // Updates all the values for the given variables to eliminate reference to any - // of the other variables in the group. - // this.arg values: A map from variable names to their values as AST expressions. - this.collapseValues = function(values) { - var incomplete = true; - while (incomplete) { - incomplete = false; - for (var varName in values) { - var varValue = values[varName]; - var result = traverse(varValue, function(node, type) { - if (type == 'name' && values.hasOwnProperty(node[1]) && node[1] != varName) { - incomplete = true; - return values[node[1]]; - } - }); - if (result) values[varName] = result; - } - } - }; - - // Replaces all uses of the specified variables with their respective - // expressions. - // this.arg replacements: A map from variable names to AST expressions. - this.updateUses = function(replacements) { - traverse(this.body, function(node, type) { - if (type === 'name' && replacements.hasOwnProperty(node[1])) { - return replacements[node[1]]; - } - }); - }; -} - -// A class for optimizing expressions. We know that it === legitimate to collapse -// 5+7 in the generated code, as it will always be numerical, for example. XXX do we need this? here? -function ExpressionOptimizer(node) { - this.node = node; - - this.run = function() { - traverse(this.node, function(node, type) { - if (type === 'binary' && node[1] == '+') { - var names = []; - var num = 0; - var has_num = false; - var fail = false; - traverse(node, function(subNode, subType) { - if (subType === 'binary') { - if (subNode[1] != '+') { - fail = true; - return false; - } - } else if (subType === 'name') { - names.push(subNode[1]); - return; - } else if (subType === 'num') { - num += subNode[1]; - has_num = true; - return; - } else { - fail = true; - return false; - } - }); - if (!fail && has_num) { - var ret = ['num', num]; - for (var i = 0; i < names.length; i++) { - ret = ['binary', '+', ['name', names[i]], ret]; - } - return ret; - } - } - }); - }; -} - -// The main entry point. Reads JavaScript from stdin, runs the eliminator on each -// function, then writes the optimized result to stdout. -function main() { - // Get the parse tree. - //process.stderr.write(JSON.stringify(process.argv[2]) + '\n') - var src = fs.readFileSync(process.argv[2]).toString(); - - var generatedFunctionsLine = src.split('\n').filter(function(line) { - return line.indexOf(GENERATED_FUNCTIONS_MARKER) == 0; - }); - generatedFunctions = set(eval(generatedFunctionsLine[0].replace(GENERATED_FUNCTIONS_MARKER, ''))); - - var ast = uglify.parser.parse(src); - - //process.stderr.write('1 ' + JSON.stringify(ast, null, 2) + '\n') - - // Run on all functions. - traverse(ast, function(node, type) { - if ((type == 'defun' || type == 'function') && isGenerated(node[1])) { - // Run the eliminator - //process.stderr.write (node[1] || '(anonymous)') + '\n' - var eliminated = new Eliminator(node).run(); - // Run the expression optimizer - new ExpressionOptimizer(node[3]).run(); - } - }); - - // Write out the optimized code. - // NOTE: For large file, can't generate code for the whole file in a single - // call due to the v8 memory limit. Writing out root children instead. - var ast1 = ast[1]; - for (var i = 0; i < ast1.length; i++) { - var node = ast1[i]; - - var js = uglify.uglify.gen_code(node, GEN_OPTIONS), old; - // remove unneeded newlines+spaces - do { - old = js; - js = js.replace(/\n *\n/g, '\n'); - } while (js != old); - process.stdout.write(js); - process.stdout.write('\n'); - } - process.stdout.write(generatedFunctionsLine + '\n'); -} - -main(); - diff --git a/tools/eliminator/eliminator.coffee b/tools/eliminator/eliminator.coffee deleted file mode 100644 index 05e3788b..00000000 --- a/tools/eliminator/eliminator.coffee +++ /dev/null @@ -1,429 +0,0 @@ -### - A script to eliminate redundant variables common in Emscripted code. - - A variable is eliminateable if it matches a leaf of this condition tree: - - Single-def - Uses only side-effect-free nodes - Unused - * - Has at most MAX_USES uses - No mutations to any dependencies between def and last use - No global dependencies or no indirect accesses between def and use - * - - TODO(max99x): Eliminate single-def undefined-initialized vars with no uses - between declaration and definition. -### - -# Imports. -uglify = require 'uglify-js' -fs = require 'fs' -os = require 'os' - -# Functions which have been generated by Emscripten. We optimize only those. -generatedFunctions = [] -GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS:' -isGenerated = (ident) -> - ident in generatedFunctions - -# Maximum number of uses to consider a variable not worth eliminating. -# The risk with using > 1 is that a variable used by another can have -# a chain that leads to exponential uses -MAX_USES = 1 - -# The UglifyJs code generator settings to use. -GEN_OPTIONS = - ascii_only: true - beautify: true - indent_level: 2 - -# Node types which can be evaluated without side effects. -NODES_WITHOUT_SIDE_EFFECTS = - name: true - num: true - string: true - binary: true - sub: true - 'unary-prefix': true # ++x can have side effects, but we never have that in generated code - -# Nodes which may break control flow. Moving a variable beyond them may have -# side effects. -CONTROL_FLOW_NODES = - new: true - throw: true - call: true - label: true - debugger: true - -# Traverses a JavaScript syntax tree rooted at the given node calling the given -# callback for each node. -# @arg node: The root of the AST. -# @arg callback: The callback to call for each node. This will be called with -# the node as the first argument and its type as the second. If false is -# returned, the traversal is stopped. If a non-undefined value is returned, -# it replaces the passed node in the tree. -# @returns: If the root node was replaced, the new root node. If the traversal -# was stopped, false. Otherwise undefined. -traverse = (node, callback) -> - type = node[0] - if typeof type == 'string' - result = callback node, type - if result? then return result - - for subnode, index in node - if typeof subnode is 'object' and subnode?.length - # NOTE: For-in nodes have unspecified var mutations. Skip them. - if type == 'for-in' and subnode?[0] == 'var' then continue - subresult = traverse subnode, callback - if subresult is false - return false - else if subresult? - node[index] = subresult - return undefined - -# A class for eliminating redundant variables from JavaScript. Give it an AST -# function/defun node and call run() to apply the optimization (in-place). -class Eliminator - constructor: (func) -> - # The statements of the function to analyze. - @body = func[3] - - # Identifier stats. Each of these objects is indexed by the identifier name. - # Whether the identifier is a local variable. - @isLocal = {} - # Whether the identifier is never modified after initialization. - @isSingleDef = {} - # How many times the identifier is used. - @useCount = {} - # Whether the initial value of a single-def identifier uses only nodes - # evaluating which has no side effects. - @usesOnlySimpleNodes = {} - # Whether the identifier depends on any non-local name, perhaps indirectly. - @dependsOnAGlobal = {} - # Whether the dependencies of the single-def identifier may be mutated - # within its live range. - @depsMutatedInLiveRange = {} - # Maps a given single-def variable to the AST expression of its initial value. - @initialValue = {} - # Maps identifiers to single-def variables which reference it in their - # initial value, i.e., which other variables it affects. - @affects = {} - - # Runs the eliminator on a given function body updating the AST in-place. - # @returns: The number of variables eliminated, or undefined if skipped. - run: -> - @calculateBasicVarStats() - @analyzeInitialValues() - @calculateTransitiveDependencies() - @analyzeLiveRanges() - - toReplace = {} - eliminated = 0 - for varName of @isSingleDef - if @isEliminateable varName - toReplace[varName] = @initialValue[varName] - eliminated++ - - @removeDeclarations toReplace - @collapseValues toReplace - @updateUses toReplace - - return eliminated - - # Runs the basic variable scan pass. Fills the following member variables: - # isLocal - # isSingleDef - # useCount - # initialValue - calculateBasicVarStats: -> - traverse @body, (node, type) => - if type is 'var' - for [varName, varValue] in node[1] - @isLocal[varName] = true - if not varValue? then varValue = ['name', 'undefined'] - @isSingleDef[varName] = not @isSingleDef.hasOwnProperty varName - @initialValue[varName] = varValue - @useCount[varName] = 0 - else if type is 'name' - varName = node[1] - if @useCount.hasOwnProperty varName then @useCount[varName]++ - else @isSingleDef[varName] = false - else if type in ['assign'] - varName = node[2][1] - if @isSingleDef[varName] then @isSingleDef[varName] = false - return undefined - return undefined - - # Analyzes the initial values of single-def variables. Requires basic variable - # stats to have been calculated. Fills the following member variables: - # affects - # dependsOnAGlobal - # usesOnlySimpleNodes - analyzeInitialValues: -> - for varName of @isSingleDef - if not @isSingleDef[varName] then continue - @usesOnlySimpleNodes[varName] = true - traverse @initialValue[varName], (node, type) => - if type not of NODES_WITHOUT_SIDE_EFFECTS - @usesOnlySimpleNodes[varName] = false - else if type is 'name' - reference = node[1] - if reference != 'undefined' - if not @affects[reference]? then @affects[reference] = {} - if not @isLocal[reference] then @dependsOnAGlobal[varName] = true - @affects[reference][varName] = true - return undefined - return undefined - - # Updates the dependency graph (@affects) to its transitive closure and - # synchronizes @dependsOnAGlobal to the new dependencies. - calculateTransitiveDependencies: -> - incomplete = true - todo = {} - for element of @affects - todo[element] = 1 - - #process.stdout.write 'pre ' + JSON.stringify(@affects, null, ' ') + '\n' - - while incomplete - incomplete = false - nextTodo = {} - for source, targets of @affects - for target of targets - if todo[target] - for target2 of @affects[target] - if not targets[target2] - if not @isLocal[source] then @dependsOnAGlobal[target2] = true - targets[target2] = true - nextTodo[source] = 1 - incomplete = true - todo = nextTodo - - #process.stdout.write 'post ' + JSON.stringify(@affects, null, ' ') + '\n' - - return undefined - - # Analyzes the live ranges of single-def variables. Requires dependencies to - # have been calculated. Fills the following member variables: - # depsMutatedInLiveRange - analyzeLiveRanges: -> - isLive = {} - - # Checks if a given node may mutate any of the currently live variables. - checkForMutations = (node, type) => - usedInThisStatement = {} - if type in ['assign', 'call'] - traverse node.slice(2, 4), (node, type) => - if type is 'name' then usedInThisStatement[node[1]] = true - return undefined - - if type in ['assign', 'unary-prefix', 'unary-postfix'] - if type is 'assign' or node[1] in ['--', '++'] - reference = node[2] - while reference[0] != 'name' - reference = reference[1] - reference = reference[1] - if @affects[reference]? - for varName of @affects[reference] - if isLive[varName] - isLive[varName] = false - - if type of CONTROL_FLOW_NODES - for varName of isLive - if @dependsOnAGlobal[varName] or not usedInThisStatement[varName] - isLive[varName] = false - else if type is 'assign' - for varName of isLive - if @dependsOnAGlobal[varName] and not usedInThisStatement[varName] - isLive[varName] = false - else if type is 'name' - reference = node[1] - if @isSingleDef[reference] - if not isLive[reference] - @depsMutatedInLiveRange[reference] = true - return undefined - - # Analyzes a block and all its children for variable ranges. Makes sure to - # account for the worst case of possible mutations. - analyzeBlock = (node, type) => - if type in ['switch', 'if', 'try', 'do', 'while', 'for', 'for-in'] - traverseChild = (child) -> - if typeof child == 'object' and child?.length - savedLive = {} - for name of isLive then savedLive[name] = true - traverse child, analyzeBlock - for name of isLive - if not isLive[name] then savedLive[name] = false - isLive = savedLive - if type is 'switch' - traverseChild node[1] - for child in node[2] - traverseChild child - else if type in ['if', 'try'] - for child in node - traverseChild child - else - # Don't put anything from outside into the body of a loop. - isLive = {} - for child in node then traverseChild child - # Don't keep anything alive through a loop - isLive = {} - return node - else if type is 'var' - for [varName, varValue] in node[1] - if varValue? then traverse varValue, checkForMutations - # Mark the variable as live - if @isSingleDef[varName] - isLive[varName] = true - # Mark variables that depend on it as no longer live - if @affects[varName]? - for varNameDep of @affects[varName] - if isLive[varNameDep] - isLive[varNameDep] = false - return node - else - checkForMutations node, type - return undefined - - traverse @body, analyzeBlock - - return undefined - - # Determines whether a given variable can be safely eliminated. Requires all - # analysis passes to have been run. - isEliminateable: (varName) -> - if @isSingleDef[varName] and @usesOnlySimpleNodes[varName] - if @useCount[varName] == 0 - return true - else if @useCount[varName] <= MAX_USES - return not @depsMutatedInLiveRange[varName] - return false - - # Removes all var declarations for the specified variables. - # @arg toRemove: An object whose keys are the variable names to remove. - removeDeclarations: (toRemove) -> - traverse @body, (node, type) -> - if type is 'var' - intactVars = (i for i in node[1] when not toRemove.hasOwnProperty i[0]) - if intactVars.length - node[1] = intactVars - return node - else - return ['toplevel', []] - return undefined - return undefined - - # Updates all the values for the given variables to eliminate reference to any - # of the other variables in the group. - # @arg values: A map from variable names to their values as AST expressions. - collapseValues: (values) -> - incomplete = true - while incomplete - incomplete = false - for varName, varValue of values - result = traverse varValue, (node, type) -> - if type == 'name' and values.hasOwnProperty(node[1]) and node[1] != varName - incomplete = true - return values[node[1]] - return undefined - if result? then values[varName] = result - return undefined - - # Replaces all uses of the specified variables with their respective - # expressions. - # @arg replacements: A map from variable names to AST expressions. - updateUses: (replacements) -> - traverse @body, (node, type) -> - if type is 'name' and replacements.hasOwnProperty node[1] - return replacements[node[1]] - return undefined - return undefined - - -# A class for optimizing expressions. We know that it is legitimate to collapse -# 5+7 in the generated code, as it will always be numerical, for example. -class ExpressionOptimizer - constructor: (node) -> - @node = node - - run: -> - traverse @node, (node, type) -> - if type is 'binary' and node[1] == '+' - names = [] - num = 0 - has_num = false - fail = false - traverse node, (subNode, subType) -> - if subType is 'binary' - if subNode[1] isnt '+' - fail = true - return false - return undefined - else if subType is 'name' - names.push subNode[1] - return undefined - else if subType is 'num' - num += subNode[1] - has_num = true - return undefined - else - fail = true - return false - if not fail and has_num - ret = ['num', num] - for name in names - ret = ['binary', '+', ['name', name], ret] - return ret - return undefined - - return undefined - -# The main entry point. Reads JavaScript from stdin, runs the eliminator on each -# function, then writes the optimized result to stdout. -main = -> - # Get the parse tree. - #process.stderr.write(JSON.stringify(process.argv[2]) + '\n') - src = fs.readFileSync(process.argv[2]).toString() - - throw 'Cannot identify generated functions' if GENERATED_FUNCTIONS_MARKER in src - generatedFunctionsLine = src.split('\n').filter (line) -> - return line.indexOf(GENERATED_FUNCTIONS_MARKER) == 0 - generatedFunctions = eval(generatedFunctionsLine[0].replace(GENERATED_FUNCTIONS_MARKER, '')) - - ast = uglify.parser.parse src - - ##process.stderr.write(JSON.stringify(ast) + '\n') - - # Run on all functions. - traverse ast, (node, type) -> - if type in ['defun', 'function'] and isGenerated node[1] - - # Run the eliminator - #process.stderr.write (node[1] || '(anonymous)') + '\n' - eliminated = new Eliminator(node).run() - if eliminated? - #process.stderr.write " Eliminated #{eliminated} vars.\n" - - # Run the expression optimizer - new ExpressionOptimizer(node[3]).run() - else - #process.stderr.write ' Skipped.\n' - - return undefined - - # Write out the optimized code. - # NOTE: For large file, can't generate code for the whole file in a single - # call due to the v8 memory limit. Writing out root children instead. - for node in ast[1] - # Parse and recompile again, to remove unneeded lines - src2 = uglify.uglify.gen_code node, GEN_OPTIONS - node2 = uglify.parser.parse src2 - - process.stdout.write uglify.uglify.gen_code node2, GEN_OPTIONS - process.stdout.write '\n' - process.stdout.write generatedFunctionsLine + '\n' - - return undefined - -main() diff --git a/tools/shared.py b/tools/shared.py index 84ce6c2e..6150060b 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -190,7 +190,6 @@ LLVM_NM=os.path.expanduser(build_llvm_tool_path('llvm-nm')) LLVM_INTERPRETER=os.path.expanduser(build_llvm_tool_path('lli')) LLVM_COMPILER=os.path.expanduser(build_llvm_tool_path('llc')) LLVM_EXTRACT=os.path.expanduser(build_llvm_tool_path('llvm-extract')) -COFFEESCRIPT = path_from_root('tools', 'eliminator', 'node_modules', 'coffee-script', 'bin', 'coffee') EMSCRIPTEN = path_from_root('emscripten.py') DEMANGLER = path_from_root('third_party', 'demangler.py') @@ -205,7 +204,6 @@ EMMAKEN = path_from_root('tools', 'emmaken.py') AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py') EXEC_LLVM = path_from_root('tools', 'exec_llvm.py') -VARIABLE_ELIMINATOR = path_from_root('tools', 'eliminator.js') JS_OPTIMIZER = path_from_root('tools', 'js-optimizer.js') FILE_PACKAGER = path_from_root('tools', 'file_packager.py') @@ -988,21 +986,6 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e return filename @staticmethod - def eliminator(filename, maybe_big=True): - if maybe_big: - ret = Building.splitter(filename, addendum='.el.js', func=Building.eliminator) - if ret: return ret - - input = open(filename, 'r').read() - output = Popen([NODE_JS, VARIABLE_ELIMINATOR, filename], stdout=PIPE).communicate()[0] - assert len(output) > 0, 'Error in eliminator: ' + output - filename += '.el.js' - f = open(filename, 'w') - f.write(output) - f.close() - return filename - - @staticmethod def closure_compiler(filename): if not os.path.exists(CLOSURE_COMPILER): raise Exception('Closure compiler appears to be missing, looked at: ' + str(CLOSURE_COMPILER)) |