aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2011-11-19 21:38:22 -0800
committerAlon Zakai <alonzakai@gmail.com>2011-11-19 21:38:22 -0800
commit9a004a8fa1b29ea7cbf3b27e3007021e57a083bc (patch)
treed6d6b2e084b64ba2ff6fdee3d70920c5658028bb /tools
parent2f082bce5f10f698aa07f7c14de6b3da7f265bf1 (diff)
initial work on JS optimizer
Diffstat (limited to 'tools')
-rw-r--r--tools/js-optimizer.js152
-rw-r--r--tools/shared.py1
-rw-r--r--tools/test-js-optimizer-output.js33
-rw-r--r--tools/test-js-optimizer.js34
4 files changed, 220 insertions, 0 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
new file mode 100644
index 00000000..f8e65f55
--- /dev/null
+++ b/tools/js-optimizer.js
@@ -0,0 +1,152 @@
+//==============================================================================
+// Optimizer tool. This is meant to be run after the emscripten compiler has
+// finished generating code. These optimizations are done on the generated
+// code to further improve it. Some of the modifications also work in
+// conjunction with closure compiler.
+//==============================================================================
+
+var uglify = require('../tools/eliminator/node_modules/uglify-js');
+var fs = require('fs');
+
+// Make node environment compatible with JS shells
+
+function print(text) {
+ process.stdout.write(text + '\n');
+}
+function read(filename) {
+ if (filename[0] != '/') filename = __dirname.split('/').slice(0, -1).join('/') + '/src/' + filename;
+ return fs.readFileSync(filename).toString();
+}
+
+// Load some modules
+
+eval(read('utility.js'));
+
+// Utilities
+
+var FUNCTION = set('defun', 'function');
+
+// 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 pre: The pre to call for each node. This will be called with
+// the node as the first argument and its type as the second. If true is
+// returned, the traversal is stopped. If an object is returned,
+// it replaces the passed node in the tree.
+// @arg post: A callback to call after traversing all children.
+// @arg stack: If true, a stack will be implemented: If pre does not push on
+// the stack, we push a null. We pop when we leave the node. The
+// stack is passed as a third parameter to the callbacks.
+// @returns: If the root node was replaced, the new root node. If the traversal
+// was stopped, true. Otherwise undefined.
+function traverse(node, pre, post, stack) {
+ var type = node[0], result, len;
+ var relevant = typeof type == 'string';
+ if (relevant) {
+ if (stack) len = stack.length;
+ var result = pre(node, type, stack);
+ if (result == true) return true;
+ if (stack && len == stack.length) stack.push(null);
+ }
+ for (var i = 0; i < node.length; i++) {
+ var subnode = node[i];
+ if (typeof subnode == 'object' && subnode && subnode.length) {
+ var subresult = traverse(subnode, pre, post, stack);
+ if (subresult == true) return true;
+ if (typeof subresult == 'object') node[i] = subresult;
+ }
+ }
+ if (relevant) {
+ if (post) {
+ var postResult = post(node, type, stack);
+ result = result || postResult;
+ }
+ if (stack) stack.pop();
+ }
+ return result;
+}
+
+function makeEmptyNode() {
+ return ['toplevel', []]
+}
+
+// Passes
+
+// Undos closure's creation of global variables with values true, false,
+// undefined, null. These cut down on size, but do not affect gzip size
+// and make JS engine's lives slightly harder (?)
+function unGlobalize(ast) {
+ assert(ast[0] == 'toplevel');
+ var values = {};
+ // Find global renamings of the relevant values
+ ast[1].forEach(function(node, i) {
+ if (node[0] != 'var') return;
+ node[1] = node[1].filter(function(varItem) {
+ var key = varItem[0];
+ var value = varItem[1];
+ if (!value) return true;
+ if (value[0] == 'name' && value[1] == 'null') {
+ values[key] = ['name','null'];
+ return false;
+ } else if (value[0] == 'unary-prefix' && value[1] == 'void' && value[2] && value[2].length == 2 && value[2][0] == 'num' && value[2][1] == 0){
+ values[key] = ['unary-prefix','void',['num',0]];
+ return false;
+ } else if (value[0] == 'unary-prefix' && value[1] == '!' && value[2] && value[2].length == 2 && value[2][0] == 'num' && value[2][1] == 0){
+ values[key] = ['unary-prefix','!',['num',0]];
+ return false;
+ } else if (value[0] == 'unary-prefix' && value[1] == '!' && value[2] && value[2].length == 2 && value[2][0] == 'num' && value[2][1] == 1){
+ values[key] = ['unary-prefix','!',['num',1]];
+ return false;
+ }
+ return true;
+ });
+ if (node[1].length == 0) {
+ ast[1][i] = makeEmptyNode();
+ }
+ });
+ // Walk the ast, with an understanding of JS variable scope (i.e., function-level scoping)
+ traverse(ast, function(node, type, stack) {
+ if (type in FUNCTION) {
+ stack.push({ type: 'function', vars: node[2] });
+ } else if (type == 'var') {
+ // Find our function, add our vars
+ var func = stack[stack.length-1];
+ if (func) {
+ func.vars = func.vars.concat(node[1].map(function(varItem) { return varItem[0] }));
+ }
+ }
+ }, function(node, type, stack) {
+ if (type in FUNCTION) {
+ // We know all of the variables that are seen here, proceed to do relevant replacements
+ var allVars = stack.map(function(item) { return item ? item.vars : [] }).reduce(concatenator, []); // FIXME dictionary for speed?
+ traverse(node, function(node, type, stack) {
+ // Be careful not to look into our inner functions. They have already been processed.
+ if (type == 'name' && sum(stack) == 1) {
+ var ident = node[1];
+ if (ident in values && allVars.indexOf(ident) < 0) {
+ return copy(values[ident]);
+ }
+ } else if (type in FUNCTION) {
+ stack.push(1);
+ } else {
+ stack.push(0);
+ }
+ }, null, []);
+ }
+ }, []);
+}
+
+// Main
+
+var src = fs.readFileSync('/dev/stdin').toString();
+
+var ast = uglify.parser.parse(src);
+
+unGlobalize(ast);
+
+print(uglify.uglify.gen_code(ast, {
+ ascii_only: true,
+ beautify: true,
+ indent_level: 2
+}));
+
diff --git a/tools/shared.py b/tools/shared.py
index 065c71b8..0a4219d8 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -32,6 +32,7 @@ DFE = path_from_root('tools', 'dead_function_eliminator.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', 'eliminator.coffee')
+JS_OPTIMIZER = path_from_root('tools', 'js-optimizer.js')
# Additional compiler options
diff --git a/tools/test-js-optimizer-output.js b/tools/test-js-optimizer-output.js
new file mode 100644
index 00000000..c50adde2
--- /dev/null
+++ b/tools/test-js-optimizer-output.js
@@ -0,0 +1,33 @@
+var c;
+var b = 5;
+
+function abc() {
+ var cheez = void 0;
+ var fleefl;
+ cheez = 10;
+ fleefl = void 0;
+ var waka = void 0, flake = void 0, marfoosh = void 0;
+ var waka2 = 5, flake2 = void 0, marfoosh2 = void 0;
+ var waka3 = void 0, flake3 = 5, marfoosh3 = void 0;
+ var waka4 = void 0, flake4 = void 0, marfoosh4 = 5;
+ var test1 = !0, test2 = !1, test3 = null, test4 = z, test5 = b, test6 = void 0;
+}
+function xyz() {
+ var x = 52;
+ var cheez = x;
+ var t = 22;
+ function inner1(c) {
+ var i = x, j = null, k = 5, l = t, m = s2;
+ var s2 = 8;
+ }
+ var inner2 = (function(d) {
+ var i = x, j = null, k = 5, l = t, m = s2;
+ var s2 = 8;
+ });
+}
+function xyz2(x) {
+ var cheez = x;
+}
+zzz = (function(nada) {
+ var cheez = void 0;
+});
diff --git a/tools/test-js-optimizer.js b/tools/test-js-optimizer.js
new file mode 100644
index 00000000..14306740
--- /dev/null
+++ b/tools/test-js-optimizer.js
@@ -0,0 +1,34 @@
+var c;
+var b = 5, x = void 0, y = null, t = !0;
+var s = !1, s2 = !1;
+function abc() {
+ var cheez = x;
+ var fleefl;
+ cheez = 10;
+ fleefl = x;
+ var waka = x, flake = x, marfoosh = x;
+ var waka2 = 5, flake2 = x, marfoosh2 = x;
+ var waka3 = x, flake3 = 5, marfoosh3 = x;
+ var waka4 = x, flake4 = x, marfoosh4 = 5;
+ var test1 = t, test2 = s, test3 = y, test4 = z, test5 = b, test6 = x;
+}
+function xyz() {
+ var x = 52;
+ var cheez = x;
+ var t = 22;
+ function inner1(c) {
+ var i = x, j = y, k = 5, l = t, m = s2;
+ var s2 = 8;
+ }
+ var inner2 = function(d) {
+ var i = x, j = y, k = 5, l = t, m = s2;
+ var s2 = 8;
+ };
+}
+function xyz2(x) {
+ var cheez = x;
+}
+zzz = function(nada) {
+ var cheez = x;
+};
+