~ubuntu-branches/ubuntu/vivid/emscripten/vivid

« back to all changes in this revision

Viewing changes to tools/js-optimizer.js

  • Committer: Package Import Robot
  • Author(s): Sylvestre Ledru
  • Date: 2013-05-02 13:11:51 UTC
  • Revision ID: package-import@ubuntu.com-20130502131151-q8dvteqr1ef2x7xz
Tags: upstream-1.4.1~20130504~adb56cb
ImportĀ upstreamĀ versionĀ 1.4.1~20130504~adb56cb

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//==============================================================================
 
2
// Optimizer tool. This is meant to be run after the emscripten compiler has
 
3
// finished generating code. These optimizations are done on the generated
 
4
// code to further improve it. Some of the modifications also work in
 
5
// conjunction with closure compiler.
 
6
//
 
7
// TODO: Optimize traverse to modify a node we want to replace, in-place,
 
8
//       instead of returning it to the previous call frame where we check?
 
9
// TODO: Share EMPTY_NODE instead of emptyNode that constructs?
 
10
//==============================================================================
 
11
 
 
12
// *** Environment setup code ***
 
13
var arguments_ = [];
 
14
 
 
15
var ENVIRONMENT_IS_NODE = typeof process === 'object';
 
16
var ENVIRONMENT_IS_WEB = typeof window === 'object';
 
17
var ENVIRONMENT_IS_WORKER = typeof importScripts === 'function';
 
18
var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
 
19
 
 
20
if (ENVIRONMENT_IS_NODE) {
 
21
  // Expose functionality in the same simple way that the shells work
 
22
  // Note that we pollute the global namespace here, otherwise we break in node
 
23
  print = function(x) {
 
24
    process['stdout'].write(x + '\n');
 
25
  };
 
26
  printErr = function(x) {
 
27
    process['stderr'].write(x + '\n');
 
28
  };
 
29
 
 
30
  var nodeFS = require('fs');
 
31
  var nodePath = require('path');
 
32
 
 
33
  if (!nodeFS.existsSync) {
 
34
    nodeFS.existsSync = function(path) {
 
35
      try {
 
36
        return !!nodeFS.readFileSync(path);
 
37
      } catch(e) {
 
38
        return false;
 
39
      }
 
40
    }
 
41
  }
 
42
 
 
43
  function find(filename) {
 
44
    var prefixes = [nodePath.join(__dirname, '..', 'src'), process.cwd()];
 
45
    for (var i = 0; i < prefixes.length; ++i) {
 
46
      var combined = nodePath.join(prefixes[i], filename);
 
47
      if (nodeFS.existsSync(combined)) {
 
48
        return combined;
 
49
      }
 
50
    }
 
51
    return filename;
 
52
  }
 
53
 
 
54
  read = function(filename) {
 
55
    var absolute = find(filename);
 
56
    return nodeFS['readFileSync'](absolute).toString();
 
57
  };
 
58
 
 
59
  load = function(f) {
 
60
    globalEval(read(f));
 
61
  };
 
62
 
 
63
  arguments_ = process['argv'].slice(2);
 
64
 
 
65
} else if (ENVIRONMENT_IS_SHELL) {
 
66
  // Polyfill over SpiderMonkey/V8 differences
 
67
  if (!this['read']) {
 
68
    this['read'] = function(f) { snarf(f) };
 
69
  }
 
70
 
 
71
  if (typeof scriptArgs != 'undefined') {
 
72
    arguments_ = scriptArgs;
 
73
  } else if (typeof arguments != 'undefined') {
 
74
    arguments_ = arguments;
 
75
  }
 
76
 
 
77
} else if (ENVIRONMENT_IS_WEB) {
 
78
  this['print'] = printErr = function(x) {
 
79
    console.log(x);
 
80
  };
 
81
 
 
82
  this['read'] = function(url) {
 
83
    var xhr = new XMLHttpRequest();
 
84
    xhr.open('GET', url, false);
 
85
    xhr.send(null);
 
86
    return xhr.responseText;
 
87
  };
 
88
 
 
89
  if (this['arguments']) {
 
90
    arguments_ = arguments;
 
91
  }
 
92
} else if (ENVIRONMENT_IS_WORKER) {
 
93
  // We can do very little here...
 
94
 
 
95
  this['load'] = importScripts;
 
96
 
 
97
} else {
 
98
  throw 'Unknown runtime environment. Where are we?';
 
99
}
 
100
 
 
101
function globalEval(x) {
 
102
  eval.call(null, x);
 
103
}
 
104
 
 
105
if (typeof load == 'undefined' && typeof read != 'undefined') {
 
106
  this['load'] = function(f) {
 
107
    globalEval(read(f));
 
108
  };
 
109
}
 
110
 
 
111
if (typeof printErr === 'undefined') {
 
112
  this['printErr'] = function(){};
 
113
}
 
114
 
 
115
if (typeof print === 'undefined') {
 
116
  this['print'] = printErr;
 
117
}
 
118
// *** Environment setup code ***
 
119
 
 
120
var uglify = require('../tools/eliminator/node_modules/uglify-js');
 
121
var fs = require('fs');
 
122
var path = require('path');
 
123
 
 
124
// Load some modules
 
125
 
 
126
load('utility.js');
 
127
 
 
128
// Utilities
 
129
 
 
130
var FUNCTION = set('defun', 'function');
 
131
var LOOP = set('do', 'while', 'for');
 
132
var LOOP_FLOW = set('break', 'continue');
 
133
var ASSIGN_OR_ALTER = set('assign', 'unary-postfix', 'unary-prefix');
 
134
var CONTROL_FLOW = set('do', 'while', 'for', 'if', 'switch');
 
135
var NAME_OR_NUM = set('name', 'num');
 
136
var ASSOCIATIVE_BINARIES = set('+', '*', '|', '&', '^');
 
137
 
 
138
var NULL_NODE = ['name', 'null'];
 
139
var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]];
 
140
var TRUE_NODE = ['unary-prefix', '!', ['num', 0]];
 
141
var FALSE_NODE = ['unary-prefix', '!', ['num', 1]];
 
142
 
 
143
var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS';
 
144
var generatedFunctions = false; // whether we have received only generated functions
 
145
 
 
146
var minifierInfo = null;
 
147
 
 
148
function srcToAst(src) {
 
149
  return uglify.parser.parse(src);
 
150
}
 
151
 
 
152
function astToSrc(ast, compress) {
 
153
    return uglify.uglify.gen_code(ast, {
 
154
    ascii_only: true,
 
155
    beautify: !compress,
 
156
    indent_level: 2
 
157
  });
 
158
}
 
159
 
 
160
// Traverses the children of a node. If the traverse function returns an object,
 
161
// replaces the child. If it returns true, stop the traversal and return true.
 
162
function traverseChildren(node, traverse, pre, post, stack) {
 
163
  for (var i = 0; i < node.length; i++) {
 
164
    var subnode = node[i];
 
165
    if (typeof subnode == 'object' && subnode && subnode.length) {
 
166
      var subresult = traverse(subnode, pre, post, stack);
 
167
      if (subresult == true) return true;
 
168
      if (subresult !== null && typeof subresult == 'object') node[i] = subresult;
 
169
    }
 
170
  }
 
171
}
 
172
 
 
173
// Traverses a JavaScript syntax tree rooted at the given node calling the given
 
174
// callback for each node.
 
175
//   @arg node: The root of the AST.
 
176
//   @arg pre: The pre to call for each node. This will be called with
 
177
//     the node as the first argument and its type as the second. If true is
 
178
//     returned, the traversal is stopped. If an object is returned,
 
179
//     it replaces the passed node in the tree. If null is returned, we stop
 
180
//     traversing the subelements (but continue otherwise).
 
181
//   @arg post: A callback to call after traversing all children.
 
182
//   @arg stack: If true, a stack will be implemented: If pre does not push on
 
183
//               the stack, we push a 0. We pop when we leave the node. The
 
184
//               stack is passed as a third parameter to the callbacks.
 
185
//   @returns: If the root node was replaced, the new root node. If the traversal
 
186
//     was stopped, true. Otherwise undefined.
 
187
function traverse(node, pre, post, stack) {
 
188
  var type = node[0], result, len;
 
189
  var relevant = typeof type == 'string';
 
190
  if (relevant) {
 
191
    if (stack) len = stack.length;
 
192
    var result = pre(node, type, stack);
 
193
    if (result == true) return true;
 
194
    if (result !== null && typeof result == 'object') node = result; // Continue processing on this node
 
195
    if (stack && len == stack.length) stack.push(0);
 
196
  }
 
197
  if (result !== null) {
 
198
    if (traverseChildren(node, traverse, pre, post, stack) == true) return true;
 
199
  }
 
200
  if (relevant) {
 
201
    if (post) {
 
202
      var postResult = post(node, type, stack);
 
203
      result = result || postResult;
 
204
    }
 
205
    if (stack) stack.pop();
 
206
  }
 
207
  return result;
 
208
}
 
209
 
 
210
// Only walk through the generated functions
 
211
function traverseGenerated(ast, pre, post, stack) {
 
212
  assert(generatedFunctions);
 
213
  traverse(ast, function(node) {
 
214
    if (node[0] == 'defun') {
 
215
      traverse(node, pre, post, stack);
 
216
      return null;
 
217
    }
 
218
  });
 
219
}
 
220
 
 
221
function traverseGeneratedFunctions(ast, callback) {
 
222
  assert(generatedFunctions);
 
223
  if (ast[0] == 'toplevel') {
 
224
    var stats = ast[1];
 
225
    for (var i = 0; i < stats.length; i++) {
 
226
      var curr = stats[i];
 
227
      if (curr[0] == 'defun') callback(curr);
 
228
    }
 
229
  } else if (ast[0] == 'defun') {
 
230
    callback(ast);
 
231
  }
 
232
}
 
233
 
 
234
// Walk the ast in a simple way, with an understanding of which JS variables are defined)
 
235
function traverseWithVariables(ast, callback) {
 
236
  traverse(ast, function(node, type, stack) {
 
237
    if (type in FUNCTION) {
 
238
      stack.push({ type: 'function', vars: node[2] });
 
239
    } else if (type == 'var') {
 
240
      // Find our function, add our vars
 
241
      var func = stack[stack.length-1];
 
242
      if (func) {
 
243
        func.vars = func.vars.concat(node[1].map(function(varItem) { return varItem[0] }));
 
244
      }
 
245
    }
 
246
  }, function(node, type, stack) {
 
247
    if (type == 'toplevel' || type in FUNCTION) {
 
248
      // We know all of the variables that are seen here, proceed to do relevant replacements
 
249
      var allVars = stack.map(function(item) { return item ? item.vars : [] }).reduce(concatenator, []); // FIXME dictionary for speed?
 
250
      traverse(node, function(node2, type2, stack2) {
 
251
        // Be careful not to look into our inner functions. They have already been processed.
 
252
        if (sum(stack2) > 1 || (type == 'toplevel' && sum(stack2) == 1)) return;
 
253
        if (type2 in FUNCTION) stack2.push(1);
 
254
        return callback(node2, type2, allVars);
 
255
      }, null, []);
 
256
    }
 
257
  }, []);
 
258
}
 
259
 
 
260
function emptyNode() { // XXX do we need to create new nodes here? can't we reuse?
 
261
  return ['toplevel', []]
 
262
}
 
263
 
 
264
function isEmptyNode(node) {
 
265
  return node.length == 2 && node[0] == 'toplevel' && node[1].length == 0;
 
266
}
 
267
 
 
268
// Passes
 
269
 
 
270
// Dump the AST. Useful for debugging. For example,
 
271
//  node tools/js-optimizer.js ABSOLUTE_PATH_TO_FILE dumpAst
 
272
function dumpAst(ast) {
 
273
  printErr(JSON.stringify(ast, null, '  '));
 
274
}
 
275
 
 
276
function dumpSrc(ast) {
 
277
  printErr(astToSrc(ast));
 
278
}
 
279
 
 
280
// Undos closure's creation of global variables with values true, false,
 
281
// undefined, null. These cut down on size, but do not affect gzip size
 
282
// and make JS engine's lives slightly harder (?)
 
283
function unGlobalize(ast) {
 
284
 
 
285
  throw 'this is deprecated!'; // and does not work with parallel compilation
 
286
 
 
287
  assert(ast[0] == 'toplevel');
 
288
  var values = {};
 
289
  // Find global renamings of the relevant values
 
290
  ast[1].forEach(function(node, i) {
 
291
    if (node[0] != 'var') return;
 
292
    node[1] = node[1].filter(function(varItem, j) {
 
293
      var ident = varItem[0];
 
294
      var value = varItem[1];
 
295
      if (!value) return true;
 
296
      var possible = false;
 
297
      if (jsonCompare(value, NULL_NODE) ||
 
298
          jsonCompare(value, UNDEFINED_NODE) ||
 
299
          jsonCompare(value, TRUE_NODE) ||
 
300
          jsonCompare(value, FALSE_NODE)) {
 
301
        possible = true;
 
302
      }
 
303
      if (!possible) return true;
 
304
      // Make sure there are no assignments to this variable. (This isn't fast, we traverse many times..)
 
305
      ast[1][i][1][j] = emptyNode();
 
306
      var assigned = false;
 
307
      traverseWithVariables(ast, function(node, type, allVars) {
 
308
        if (type == 'assign' && node[2][0] == 'name' && node[2][1] == ident) assigned = true;
 
309
      });
 
310
      ast[1][i][1][j] = [ident, value];
 
311
      if (!assigned) {
 
312
        values[ident] = value;
 
313
        return false;
 
314
      }
 
315
      return true;
 
316
    });
 
317
 
 
318
    if (node[1].length == 0) {
 
319
      ast[1][i] = emptyNode();
 
320
    }
 
321
  });
 
322
  traverseWithVariables(ast, function(node, type, allVars) {
 
323
    if (type == 'name') {
 
324
      var ident = node[1];
 
325
      if (ident in values && allVars.indexOf(ident) < 0) {
 
326
        return copy(values[ident]);
 
327
      }
 
328
    }
 
329
  });
 
330
}
 
331
 
 
332
// Closure compiler, when inlining, will insert assignments to
 
333
// undefined for the shared variables. However, in compiled code
 
334
// - and in library/shell code too! - we should never rely on
 
335
// undefined being assigned. So we can simply remove those assignments.
 
336
//
 
337
// Note: An inlined function that kept a large value referenced, may
 
338
//       keep that references when inlined, if we remove the setting to
 
339
//       undefined. This is not dangerous in compiled code, but might be
 
340
//       in supporting code (for example, holding on to the HEAP when copying).
 
341
//
 
342
// This pass assumes that unGlobalize has been run, so undefined
 
343
// is now explicit.
 
344
function removeAssignsToUndefined(ast) {
 
345
  traverse(ast, function(node, type) {
 
346
    if (type == 'assign' && jsonCompare(node[3], ['unary-prefix', 'void', ['num', 0]])) {
 
347
      return emptyNode();
 
348
    } else if (type == 'var') {
 
349
      node[1] = node[1].map(function(varItem, j) {
 
350
        var ident = varItem[0];
 
351
        var value = varItem[1];
 
352
        if (jsonCompare(value, UNDEFINED_NODE)) return [ident];
 
353
        return [ident, value];
 
354
      });
 
355
    }
 
356
  });
 
357
  // cleanup (|x = y = void 0| leaves |x = ;| right now)
 
358
  var modified = true;
 
359
  while (modified) {
 
360
    modified = false;
 
361
    traverse(ast, function(node, type) {
 
362
      if (type == 'assign' && jsonCompare(node[3], emptyNode())) {
 
363
        modified = true;
 
364
        return emptyNode();
 
365
      } else if (type == 'var') {
 
366
        node[1] = node[1].map(function(varItem, j) {
 
367
          var ident = varItem[0];
 
368
          var value = varItem[1];
 
369
          if (value && jsonCompare(value, emptyNode())) return [ident];
 
370
          return [ident, value];
 
371
        });
 
372
      }
 
373
    });
 
374
  }
 
375
}
 
376
 
 
377
// XXX This is an invalid optimization
 
378
// We sometimes leave some settings to label that are not needed, if later in
 
379
// the relooper we realize that we have a single entry, so no checks on label
 
380
// are actually necessary. It's easy to clean those up now.
 
381
function removeUnneededLabelSettings(ast) {
 
382
  traverse(ast, function(node, type) {
 
383
    if (type == 'defun') { // all of our compiled code is in defun nodes
 
384
      // Find all checks
 
385
      var checked = {};
 
386
      traverse(node, function(node, type) {
 
387
        if (type == 'binary' && node[1] == '==' && node[2][0] == 'name' && node[2][1] == 'label') {
 
388
          assert(node[3][0] == 'num');
 
389
          checked[node[3][1]] = 1;
 
390
        }
 
391
      });
 
392
      // Remove unneeded sets
 
393
      traverse(node, function(node, type) {
 
394
        if (type == 'assign' && node[2][0] == 'name' && node[2][1] == 'label') {
 
395
          assert(node[3][0] == 'num');
 
396
          if (!(node[3][1] in checked)) return emptyNode();
 
397
        }
 
398
      });
 
399
    }
 
400
  });
 
401
}
 
402
 
 
403
// Various expression simplifications. Pre run before closure (where we still have metadata), Post run after.
 
404
 
 
405
function simplifyExpressionsPre(ast) {
 
406
  // When there is a bunch of math like (((8+5)|0)+12)|0, only the external |0 is needed, one correction is enough.
 
407
  // At each node, ((X|0)+Y)|0 can be transformed into (X+Y): The inner corrections are not needed
 
408
  // TODO: Is the same is true for 0xff, 0xffff?
 
409
  // Likewise, if we have |0 inside a block that will be >>'d, then the |0 is unnecessary because some
 
410
  // 'useful' mathops already |0 anyhow.
 
411
 
 
412
  function simplifyBitops(ast) {
 
413
    var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^');
 
414
    var SAFE_BINARY_OPS = set('+', '-', '*'); // division is unsafe as it creates non-ints in JS; mod is unsafe as signs matter so we can't remove |0's
 
415
    var ZERO = ['num', 0];
 
416
    var rerun = true;
 
417
    while (rerun) {
 
418
      rerun = false;
 
419
      traverseGenerated(ast, function process(node, type, stack) {
 
420
        if (type == 'binary' && node[1] == '|') {
 
421
          if (node[2][0] == 'num' && node[3][0] == 'num') {
 
422
            return ['num', node[2][1] | node[3][1]];
 
423
          } else if (jsonCompare(node[2], ZERO) || jsonCompare(node[3], ZERO)) {
 
424
            // We might be able to remove this correction
 
425
            for (var i = stack.length-1; i >= 0; i--) {
 
426
              if (stack[i] == 1) {
 
427
                // we will replace ourselves with the non-zero side. Recursively process that node.
 
428
                var result = jsonCompare(node[2], ZERO) ? node[3] : node[2], other;
 
429
                // replace node in-place
 
430
                node.length = result.length;
 
431
                for (var j = 0; j < result.length; j++) {
 
432
                  node[j] = result[j];
 
433
                }
 
434
                rerun = true;
 
435
                return process(result, result[0], stack);
 
436
              } else if (stack[i] == -1) {
 
437
                break; // Too bad, we can't
 
438
              } else if (asm) {
 
439
                break; // we must keep a coercion right on top of a heap access in asm mode
 
440
              }
 
441
            }
 
442
          }
 
443
          stack.push(1); // From here on up, no need for this kind of correction, it's done at the top
 
444
                         // (Add this at the end, so it is only added if we did not remove it)
 
445
        } else if (type == 'binary' && node[1] in USEFUL_BINARY_OPS) {
 
446
          stack.push(1);
 
447
        } else if ((type == 'binary' && node[1] in SAFE_BINARY_OPS) || type == 'num' || type == 'name') {
 
448
          stack.push(0); // This node is safe in that it does not interfere with this optimization
 
449
        } else {
 
450
          stack.push(-1); // This node is dangerous! Give up if you see this before you see '1'
 
451
        }
 
452
      }, null, []);
 
453
    }
 
454
 
 
455
    // & and heap-related optimizations
 
456
 
 
457
    var heapBits, heapUnsigned;
 
458
    function parseHeap(name) {
 
459
      if (name.substr(0, 4) != 'HEAP') return false;
 
460
      heapUnsigned = name[4] == 'U';
 
461
      heapBits = parseInt(name.substr(heapUnsigned ? 5 : 4));
 
462
      return true;
 
463
    }
 
464
 
 
465
    traverseGenerated(ast, function(node, type) {
 
466
      if (type == 'binary' && node[1] == '&' && node[3][0] == 'num') {
 
467
        if (node[2][0] == 'num') return ['num', node[2][1] & node[3][1]];
 
468
        var input = node[2];
 
469
        var amount = node[3][1];
 
470
        if (input[0] == 'binary' && input[1] == '&' && input[3][0] == 'num') {
 
471
          // Collapse X & 255 & 1
 
472
          node[3][1] = amount & input[3][1];
 
473
          node[2] = input[2];
 
474
        } else if (input[0] == 'sub' && input[1][0] == 'name') {
 
475
          // HEAP8[..] & 255 => HEAPU8[..]
 
476
          var name = input[1][1];
 
477
          if (parseHeap(name)) {
 
478
            if (amount == Math.pow(2, heapBits)-1) {
 
479
              if (!heapUnsigned) {
 
480
                input[1][1] = 'HEAPU' + heapBits; // make unsigned
 
481
              }
 
482
              if (asm) {
 
483
                // we cannot return HEAPU8 without a coercion, but at least we do HEAP8 & 255 => HEAPU8 | 0
 
484
                node[1] = '|';
 
485
                node[3][1] = 0;
 
486
                return node;
 
487
              }
 
488
              return input;
 
489
            }
 
490
          }
 
491
        }
 
492
      } else if (type       == 'binary' && node[1]    == '>>' && node[3][0]    == 'num' &&
 
493
                 node[2][0] == 'binary' && node[2][1] == '<<' && node[2][3][0] == 'num' &&
 
494
                 node[2][2][0] == 'sub' && node[2][2][1][0] == 'name') {
 
495
        // collapse HEAPU?8[..] << 24 >> 24 etc. into HEAP8[..] | 0
 
496
        // TODO: run this before | 0 | 0 removal, because we generate | 0
 
497
        var amount = node[3][1];
 
498
        var name = node[2][2][1][1];
 
499
        if (amount == node[2][3][1] && parseHeap(name)) {
 
500
          if (heapBits == 32 - amount) {
 
501
            node[2][2][1][1] = 'HEAP' + heapBits;
 
502
            node[1] = '|';
 
503
            node[2] = node[2][2];
 
504
            node[3][1] = 0;
 
505
            return node;
 
506
          }
 
507
        }
 
508
      }
 
509
    });
 
510
 
 
511
    if (asm) {
 
512
      // optimize num >> num, in asm we need this here since we do not run optimizeShifts
 
513
      traverseGenerated(ast, function(node, type) {
 
514
        if (type == 'binary' && node[1] == '>>' && node[2][0] == 'num' && node[3][0] == 'num') {
 
515
          node[0] = 'num';
 
516
          node[1] = node[2][1] >> node[3][1];
 
517
          node.length = 2;
 
518
        }
 
519
      });
 
520
    }
 
521
  }
 
522
 
 
523
  // The most common mathop is addition, e.g. in getelementptr done repeatedly. We can join all of those,
 
524
  // by doing (num+num) ==> newnum, and (name+num)+num = name+newnum
 
525
  function joinAdditions(ast) {
 
526
    var rerun = true;
 
527
    while (rerun) {
 
528
      rerun = false;
 
529
      traverseGenerated(ast, function(node, type) {
 
530
        if (type == 'binary' && node[1] == '+') {
 
531
          if (node[2][0] == 'num' && node[3][0] == 'num') {
 
532
            rerun = true;
 
533
            return ['num', node[2][1] + node[3][1]];
 
534
          }
 
535
          for (var i = 2; i <= 3; i++) {
 
536
            var ii = 5-i;
 
537
            for (var j = 2; j <= 3; j++) {
 
538
              if (node[i][0] == 'num' && node[ii][0] == 'binary' && node[ii][1] == '+' && node[ii][j][0] == 'num') {
 
539
                rerun = true;
 
540
                node[ii][j][1] += node[i][1];
 
541
                return node[ii];
 
542
              }
 
543
            }
 
544
          }
 
545
        }
 
546
      });
 
547
    }
 
548
  }
 
549
 
 
550
  // if (x == 0) can be if (!x), etc.
 
551
  function simplifyZeroComp(ast) {
 
552
    traverseGenerated(ast, function(node, type) {
 
553
      var binary;
 
554
      if (type == 'if' && (binary = node[1])[0] == 'binary') {
 
555
        if ((binary[1] == '!=' || binary[1] == '!==') && binary[3][0] == 'num' && binary[3][1] == 0) {
 
556
          node[1] = binary[2];
 
557
          return node;
 
558
        } else if ((binary[1] == '==' || binary[1] == '===') && binary[3][0] == 'num' && binary[3][1] == 0) {
 
559
          node[1] = ['unary-prefix', '!', binary[2]];
 
560
          return node;
 
561
        }
 
562
      }
 
563
    });
 
564
  }
 
565
 
 
566
  function asmOpts(ast) {
 
567
    // 1. Add final returns when necessary
 
568
    // 2. Remove unneeded coercions on function calls that have no targets (eliminator removed it)
 
569
    traverseGeneratedFunctions(ast, function(fun) {
 
570
      var returnType = null;
 
571
      traverse(fun, function(node, type) {
 
572
        if (type == 'return' && node[1]) {
 
573
          returnType = detectAsmCoercion(node[1]);
 
574
        } else if (type == 'stat') {
 
575
          var inner = node[1];
 
576
          if ((inner[0] == 'binary' && inner[1] in ASSOCIATIVE_BINARIES && inner[2][0] == 'call' && inner[3][0] == 'num') ||
 
577
              (inner[0] == 'unary-prefix' && inner[1] == '+' && inner[2][0] == 'call')) {
 
578
            node[1] = inner[2];
 
579
          }
 
580
        }
 
581
      });
 
582
      // Add a final return if one is missing.
 
583
      if (returnType !== null) {
 
584
        var stats = getStatements(fun);
 
585
        var last = stats[stats.length-1];
 
586
        if (last[0] != 'return') {
 
587
          var returnValue = ['num', 0];
 
588
          if (returnType == ASM_DOUBLE) returnValue = ['unary-prefix', '+', returnValue];
 
589
          stats.push(['return', returnValue]);
 
590
        }
 
591
      }
 
592
    });
 
593
  }
 
594
 
 
595
  simplifyBitops(ast);
 
596
  joinAdditions(ast);
 
597
  // simplifyZeroComp(ast); TODO: investigate performance
 
598
  if (asm) asmOpts(ast);
 
599
}
 
600
 
 
601
// In typed arrays mode 2, we can have
 
602
//  HEAP[x >> 2]
 
603
// very often. We can in some cases do the shift on the variable itself when it is set,
 
604
// to greatly reduce the number of shift operations.
 
605
// TODO: when shifting a variable, if there are other uses, keep an unshifted version too, to prevent slowdowns?
 
606
function optimizeShiftsInternal(ast, conservative) {
 
607
  var MAX_SHIFTS = 3;
 
608
  traverseGeneratedFunctions(ast, function(fun) {
 
609
    var funMore = true;
 
610
    var funFinished = {};
 
611
    while (funMore) {
 
612
      funMore = false;
 
613
      // Recognize variables and parameters
 
614
      var vars = {};
 
615
      function newVar(name, param, addUse) {
 
616
        if (!vars[name]) {
 
617
          vars[name] = {
 
618
            param: param,
 
619
            defs: addUse ? 1 : 0,
 
620
            uses: 0,
 
621
            timesShifted: [0, 0, 0, 0], // zero shifts of size 0, 1, 2, 3
 
622
            benefit: 0,
 
623
            primaryShift: -1
 
624
          };
 
625
        }
 
626
      }
 
627
      // params
 
628
      if (fun[2]) {
 
629
        fun[2].forEach(function(arg) {
 
630
          newVar(arg, true, true);
 
631
        });
 
632
      }
 
633
      // vars
 
634
      // XXX if var has >>=, ignore it here? That means a previous pass already optimized it
 
635
      var hasSwitch = traverse(fun, function(node, type) {
 
636
        if (type == 'var') {
 
637
          node[1].forEach(function(arg) {
 
638
            newVar(arg[0], false, arg[1]);
 
639
          });
 
640
        } else if (type == 'switch') {
 
641
          // The relooper can't always optimize functions, and we currently don't work with
 
642
          // switch statements when optimizing shifts. Bail.
 
643
          return true;
 
644
        }
 
645
      });
 
646
      if (hasSwitch) {
 
647
        break;
 
648
      }
 
649
      // uses and defs TODO: weight uses by being inside a loop (powers). without that, we
 
650
      // optimize for code size, not speed.
 
651
      traverse(fun, function(node, type, stack) {
 
652
        stack.push(node);
 
653
        if (type == 'name' && vars[node[1]] && stack[stack.length-2][0] != 'assign') {
 
654
          vars[node[1]].uses++;
 
655
        } else if (type == 'assign' && node[2][0] == 'name' && vars[node[2][1]]) {
 
656
          vars[node[2][1]].defs++;
 
657
        }
 
658
      }, null, []);
 
659
      // First, break up elements inside a shift. This lets us see clearly what to do next.
 
660
      traverse(fun, function(node, type) {
 
661
        if (type == 'binary' && node[1] == '>>' && node[3][0] == 'num') {
 
662
          var shifts = node[3][1];
 
663
          if (shifts <= MAX_SHIFTS) {
 
664
            // Push the >> inside the value elements
 
665
            function addShift(subNode) {
 
666
              if (subNode[0] == 'binary' && subNode[1] == '+') {
 
667
                subNode[2] = addShift(subNode[2]);
 
668
                subNode[3] = addShift(subNode[3]);
 
669
                return subNode;
 
670
              }
 
671
              if (subNode[0] == 'name' && !subNode[2]) { // names are returned with a shift, but we also note their being shifted
 
672
                var name = subNode[1];
 
673
                if (vars[name]) {
 
674
                  vars[name].timesShifted[shifts]++;
 
675
                  subNode[2] = true;
 
676
                }
 
677
              }
 
678
              return ['binary', '>>', subNode, ['num', shifts]];
 
679
            }
 
680
            return addShift(node[2]);
 
681
          }
 
682
        }
 
683
      });
 
684
      traverse(fun, function(node, type) {
 
685
        if (node[0] == 'name' && node[2]) {
 
686
          return node.slice(0, 2); // clean up our notes
 
687
        }
 
688
      });
 
689
      // At this point, shifted expressions are split up, and we know who the vars are and their info, so we can decide
 
690
      // TODO: vars that depend on other vars
 
691
      for (var name in vars) {
 
692
        var data = vars[name];
 
693
        var totalTimesShifted = sum(data.timesShifted);
 
694
        if (totalTimesShifted == 0) {
 
695
          continue;
 
696
        }
 
697
        if (totalTimesShifted != Math.max.apply(null, data.timesShifted)) {
 
698
          // TODO: Handle multiple different shifts
 
699
          continue;
 
700
        }
 
701
        if (funFinished[name]) continue;
 
702
        // We have one shift size (and possible unshifted uses). Consider replacing this variable with a shifted clone. If
 
703
        // the estimated benefit is >0, we will do it
 
704
        if (data.defs == 1) {
 
705
          data.benefit = totalTimesShifted - 2*(data.defs + (data.param ? 1 : 0));
 
706
        }
 
707
        if (conservative) data.benefit = 0;
 
708
        if (data.benefit > 0) {
 
709
          funMore = true; // We will reprocess this function
 
710
          for (var i = 0; i < 4; i++) {
 
711
            if (data.timesShifted[i]) {
 
712
              data.primaryShift = i;
 
713
            }
 
714
          }
 
715
        }
 
716
      }
 
717
      //printErr(JSON.stringify(vars));
 
718
      function cleanNotes() { // We need to mark 'name' nodes as 'processed' in some passes here; this cleans the notes up
 
719
        traverse(fun, function(node, type) {
 
720
          if (node[0] == 'name' && node[2]) {
 
721
            return node.slice(0, 2);
 
722
          }
 
723
        });
 
724
      }
 
725
      cleanNotes();
 
726
      // Apply changes
 
727
      function needsShift(name) {
 
728
        return vars[name] && vars[name].primaryShift >= 0;
 
729
      }
 
730
      for (var name in vars) { // add shifts for params and var's for all new variables
 
731
        var data = vars[name];
 
732
        if (needsShift(name)) {
 
733
          if (data.param) {
 
734
            fun[3].unshift(['var', [[name + '$s' + data.primaryShift, ['binary', '>>', ['name', name], ['num', data.primaryShift]]]]]);
 
735
          } else {
 
736
            fun[3].unshift(['var', [[name + '$s' + data.primaryShift]]]);
 
737
          }
 
738
        }
 
739
      }
 
740
      traverse(fun, function(node, type, stack) { // add shift to assignments
 
741
        stack.push(node);
 
742
        if (node[0] == 'assign' && node[1] === true && node[2][0] == 'name' && needsShift(node[2][1]) && !node[2][2]) {
 
743
          var name = node[2][1];
 
744
          var data = vars[name];
 
745
          var parent = stack[stack.length-3];
 
746
          var statements = getStatements(parent);
 
747
          assert(statements, 'Invalid parent for assign-shift: ' + dump(parent));
 
748
          var i = statements.indexOf(stack[stack.length-2]);
 
749
          statements.splice(i+1, 0, ['stat', ['assign', true, ['name', name + '$s' + data.primaryShift], ['binary', '>>', ['name', name, true], ['num', data.primaryShift]]]]);
 
750
        } else if (node[0] == 'var') {
 
751
          var args = node[1];
 
752
          for (var i = 0; i < args.length; i++) {
 
753
            var arg = args[i];
 
754
            var name = arg[0];
 
755
            var data = vars[name];
 
756
            if (arg[1] && needsShift(name)) {
 
757
              args.splice(i+1, 0, [name + '$s' + data.primaryShift, ['binary', '>>', ['name', name, true], ['num', data.primaryShift]]]);
 
758
            }
 
759
          }
 
760
          return node;
 
761
        }
 
762
      }, null, []);
 
763
      cleanNotes();
 
764
      traverse(fun, function(node, type, stack) { // replace shifted name with new variable
 
765
        stack.push(node);
 
766
        if (node[0] == 'binary' && node[1] == '>>' && node[2][0] == 'name' && needsShift(node[2][1]) && node[3][0] == 'num') {
 
767
          var name = node[2][1];
 
768
          var data = vars[name];
 
769
          var parent = stack[stack.length-2];
 
770
          // Don't modify in |x$sN = x >> 2|, in normal assigns and in var assigns
 
771
          if (parent[0] == 'assign' && parent[2][0] == 'name' && parent[2][1] == name + '$s' + data.primaryShift) return;
 
772
          if (parent[0] == name + '$s' + data.primaryShift) return;
 
773
          if (node[3][1] == data.primaryShift) {
 
774
            return ['name', name + '$s' + data.primaryShift];
 
775
          }
 
776
        }
 
777
      }, null, []);
 
778
      cleanNotes();
 
779
      var SIMPLE_SHIFTS = set('<<', '>>');
 
780
      var more = true;
 
781
      while (more) { // combine shifts in the same direction as an optimization
 
782
        more = false;
 
783
        traverse(fun, function(node, type) {
 
784
          if (node[0] == 'binary' && node[1] in SIMPLE_SHIFTS && node[2][0] == 'binary' && node[2][1] == node[1] &&
 
785
              node[3][0] == 'num' && node[2][3][0] == 'num') { // do not turn a << b << c into a << b + c; while logically identical, it is slower
 
786
            more = true;
 
787
            return ['binary', node[1], node[2][2], ['num', node[3][1] + node[2][3][1]]];
 
788
          }
 
789
        });
 
790
      }
 
791
      // Before recombining, do some additional optimizations
 
792
      traverse(fun, function(node, type) {
 
793
        // Apply constant shifts onto constants
 
794
        if (type == 'binary' && node[1] == '>>' && node[2][0] == 'num' && node[3][0] == 'num' && node[3][1] <= MAX_SHIFTS) {
 
795
          var subNode = node[2];
 
796
          var shifts = node[3][1];
 
797
          var result = subNode[1] / Math.pow(2, shifts);
 
798
          if (result % 1 == 0) {
 
799
            subNode[1] = result;
 
800
            return subNode;
 
801
          }
 
802
        }
 
803
        // Optimize the case of ($a*80)>>2 into ($a*20)|0
 
804
        if (type == 'binary' && node[1] in SIMPLE_SHIFTS &&
 
805
            node[2][0] == 'binary' && node[2][1] == '*') {
 
806
          var mulNode = node[2];
 
807
          if (mulNode[2][0] == 'num') {
 
808
            var temp = mulNode[2];
 
809
            mulNode[2] = mulNode[3];
 
810
            mulNode[3] = temp;
 
811
          }
 
812
          if (mulNode[3][0] == 'num') {
 
813
            if (node[1] == '<<') {
 
814
              mulNode[3][1] *= Math.pow(2, node[3][1]);
 
815
              node[1] = '|';
 
816
              node[3][1] = 0;
 
817
              return node;
 
818
            } else {
 
819
              if (mulNode[3][1] % Math.pow(2, node[3][1]) == 0) {
 
820
                mulNode[3][1] /= Math.pow(2, node[3][1]);
 
821
                node[1] = '|';
 
822
                node[3][1] = 0;
 
823
                return node;
 
824
              }
 
825
            }
 
826
          }
 
827
        }
 
828
        /* XXX - theoretically useful optimization(s), but commented out as not helpful in practice
 
829
        // Transform (x << 2) >> 2 into x & mask or something even simpler
 
830
        if (type == 'binary'       && node[1]    == '>>' && node[3][0] == 'num' &&
 
831
            node[2][0] == 'binary' && node[2][1] == '<<' && node[2][3][0] == 'num' && node[3][1] == node[2][3][1]) {
 
832
          var subNode = node[2];
 
833
          var shifts = node[3][1];
 
834
          var mask = ((0xffffffff << shifts) >>> shifts) | 0;
 
835
          return ['binary', '&', subNode[2], ['num', mask]];
 
836
          //return ['binary', '|', subNode[2], ['num', 0]];
 
837
          //return subNode[2];
 
838
        }
 
839
        */
 
840
      });
 
841
      // Re-combine remaining shifts, to undo the breaking up we did before. may require reordering inside +'s
 
842
      traverse(fun, function(node, type, stack) {
 
843
        stack.push(node);
 
844
        if (type == 'binary' && node[1] == '+' && (stack[stack.length-2][0] != 'binary' || stack[stack.length-2][1] != '+')) {
 
845
          // 'Flatten' added items
 
846
          var addedItems = [];
 
847
          function flatten(node) {
 
848
            if (node[0] == 'binary' && node[1] == '+') {
 
849
              flatten(node[2]);
 
850
              flatten(node[3]);
 
851
            } else {
 
852
              addedItems.push(node);
 
853
            }
 
854
          }
 
855
          flatten(node);
 
856
          var originalOrder = addedItems.slice();
 
857
          function key(node) { // a unique value for all relevant shifts for recombining, non-unique for stuff we don't need to bother with
 
858
            function originalOrderKey(item) {
 
859
              return -originalOrder.indexOf(item);
 
860
            }
 
861
            if (node[0] == 'binary' && node[1] in SIMPLE_SHIFTS) {
 
862
              if (node[3][0] == 'num' && node[3][1] <= MAX_SHIFTS) return 2*node[3][1] + (node[1] == '>>' ? 100 : 0); // 0-106
 
863
              return (node[1] == '>>' ? 20000 : 10000) + originalOrderKey(node);
 
864
            }
 
865
            if (node[0] == 'num') return -20000 + node[1];
 
866
            return -10000 + originalOrderKey(node); // Don't modify the original order if we don't modify anything
 
867
          }
 
868
          for (var i = 0; i < addedItems.length; i++) {
 
869
            if (addedItems[i][0] == 'string') return; // this node is not relevant for us
 
870
          }
 
871
          addedItems.sort(function(node1, node2) {
 
872
            return key(node1) - key(node2);
 
873
          });
 
874
          // Regenerate items, now sorted
 
875
          var i = 0;
 
876
          while (i < addedItems.length-1) { // re-combine inside addedItems
 
877
            var k = key(addedItems[i]), k1 = key(addedItems[i+1]);
 
878
            if (k == k1 && k >= 0 && k1 <= 106) {
 
879
              addedItems[i] = ['binary', addedItems[i][1], ['binary', '+', addedItems[i][2], addedItems[i+1][2]], addedItems[i][3]];
 
880
              addedItems.splice(i+1, 1);
 
881
            } else {
 
882
              i++;
 
883
            }
 
884
          }
 
885
          var num = 0;
 
886
          for (i = 0; i < addedItems.length; i++) { // combine all numbers into one
 
887
            if (addedItems[i][0] == 'num') {
 
888
              num += addedItems[i][1];
 
889
              addedItems.splice(i, 1);
 
890
              i--;
 
891
            }
 
892
          }
 
893
          if (num != 0) { // add the numbers into an existing shift, we 
 
894
                          // prefer (x+5)>>7 over (x>>7)+5 , since >>'s result is known to be 32-bit and is more easily optimized.
 
895
                          // Also, in the former we can avoid the parentheses, which saves a little space (the number will be bigger,
 
896
                          // so it might take more space, but normally at most one more digit).
 
897
            var added = false;
 
898
            for (i = 0; i < addedItems.length; i++) {
 
899
              if (addedItems[i][0] == 'binary' && addedItems[i][1] == '>>' && addedItems[i][3][0] == 'num' && addedItems[i][3][1] <= MAX_SHIFTS) {
 
900
                addedItems[i] = ['binary', '>>', ['binary', '+', addedItems[i][2], ['num', num << addedItems[i][3][1]]], addedItems[i][3]];
 
901
                added = true;
 
902
              }
 
903
            }
 
904
            if (!added) {
 
905
              addedItems.unshift(['num', num]);
 
906
            }
 
907
          }
 
908
          var ret = addedItems.pop();
 
909
          while (addedItems.length > 0) { // re-create AST from addedItems
 
910
            ret = ['binary', '+', ret, addedItems.pop()];
 
911
          }
 
912
          return ret;
 
913
        }
 
914
      }, null, []);
 
915
      // Note finished variables
 
916
      for (var name in vars) {
 
917
        funFinished[name] = true;
 
918
      }
 
919
    }
 
920
  });
 
921
}
 
922
 
 
923
function optimizeShiftsConservative(ast) {
 
924
  optimizeShiftsInternal(ast, true);
 
925
}
 
926
 
 
927
function optimizeShiftsAggressive(ast) {
 
928
  optimizeShiftsInternal(ast, false);
 
929
}
 
930
 
 
931
// We often have branchings that are simplified so one end vanishes, and
 
932
// we then get
 
933
//   if (!(x < 5))
 
934
// or such. Simplifying these saves space and time.
 
935
function simplifyNotComps(ast) {
 
936
  traverse(ast, function(node, type) {
 
937
    if (type == 'unary-prefix' && node[1] == '!' && node[2][0] == 'binary') {
 
938
      if (node[2][1] == '<') {
 
939
        return ['binary', '>=', node[2][2], node[2][3]];
 
940
      } else if (node[2][1] == '>') {
 
941
        return ['binary', '<=', node[2][2], node[2][3]];
 
942
      } else if (node[2][1] == '==') {
 
943
        return ['binary', '!=', node[2][2], node[2][3]];
 
944
      } else if (node[2][1] == '!=') {
 
945
        return ['binary', '==', node[2][2], node[2][3]];
 
946
      } else if (node[2][1] == '===') {
 
947
        return ['binary', '!==', node[2][2], node[2][3]];
 
948
      } else if (node[2][1] == '!==') {
 
949
        return ['binary', '===', node[2][2], node[2][3]];
 
950
      }
 
951
    }
 
952
  });
 
953
}
 
954
 
 
955
function simplifyExpressionsPost(ast) {
 
956
  simplifyNotComps(ast);
 
957
}
 
958
 
 
959
var NO_SIDE_EFFECTS = set('num', 'name');
 
960
 
 
961
function hasSideEffects(node) { // this is 99% incomplete!
 
962
  if (node[0] in NO_SIDE_EFFECTS) return false;
 
963
  if (node[0] == 'unary-prefix') return hasSideEffects(node[2]);
 
964
  if (node[0] == 'binary') return hasSideEffects(node[2]) || hasSideEffects(node[3]);
 
965
  return true;
 
966
}
 
967
 
 
968
// Clear out empty ifs and blocks, and redundant blocks/stats and so forth
 
969
// Operates on generated functions only
 
970
function vacuum(ast) {
 
971
  function isEmpty(node) {
 
972
    if (!node) return true;
 
973
    if (node[0] == 'toplevel' && (!node[1] || node[1].length == 0)) return true;
 
974
    if (node[0] == 'block' && (!node[1] || (typeof node[1] != 'object') || node[1].length == 0 || (node[1].length == 1 && isEmpty(node[1])))) return true;
 
975
    return false;
 
976
  }
 
977
  function simplifyList(node, si) {
 
978
    var changed = false;
 
979
    // Merge block items into this list, thus removing unneeded |{ .. }|'s
 
980
    var statements = node[si];
 
981
    var i = 0;
 
982
    while (i < statements.length) {
 
983
      var subNode = statements[i];
 
984
      if (subNode[0] == 'block') {
 
985
        statements.splice.apply(statements, [i, 1].concat(subNode[1] || []));
 
986
        changed = true;
 
987
      } else {
 
988
        i++;
 
989
      }
 
990
    }
 
991
    // Remove empty items
 
992
    var pre = node[si].length;
 
993
    node[si] = node[si].filter(function(node) { return !isEmpty(node) });
 
994
    if (node[si].length < pre) changed = true;
 
995
    if (changed) {
 
996
      return node;
 
997
    }
 
998
  }
 
999
  function vacuumInternal(node) {
 
1000
    traverseChildren(node, vacuumInternal);
 
1001
    var ret;
 
1002
    switch(node[0]) {
 
1003
      case 'block': {
 
1004
        if (node[1] && node[1].length == 1 && node[1][0][0] == 'block') {
 
1005
          return node[1][0];
 
1006
        } else if (typeof node[1] == 'object') {
 
1007
          ret = simplifyList(node, 1);
 
1008
          if (ret) return ret;
 
1009
        }
 
1010
      } break;
 
1011
      case 'stat': {
 
1012
        if (node[1][0] == 'block') {
 
1013
          return node[1];
 
1014
        }
 
1015
      } break;
 
1016
      case 'defun': {
 
1017
        if (node[3].length == 1 && node[3][0][0] == 'block') {
 
1018
          node[3] = node[3][0][1];
 
1019
          return node;
 
1020
        } else {
 
1021
          ret = simplifyList(node, 3);
 
1022
          if (ret) return ret;
 
1023
        }
 
1024
      } break;
 
1025
      case 'do': {
 
1026
        if (node[1][0] == 'num' && node[2][0] == 'toplevel' && (!node[2][1] || node[2][1].length == 0)) {
 
1027
          return emptyNode();
 
1028
        } else if (isEmpty(node[2]) && !hasSideEffects(node[1])) {
 
1029
          return emptyNode();
 
1030
        }
 
1031
      } break;
 
1032
      case 'label': {
 
1033
        if (node[2][0] == 'toplevel' && (!node[2][1] || node[2][1].length == 0)) {
 
1034
          return emptyNode();
 
1035
        }
 
1036
      } break;
 
1037
      case 'if': {
 
1038
        var empty2 = isEmpty(node[2]), empty3 = isEmpty(node[3]), has3 = node.length == 4;
 
1039
        if (!empty2 && empty3 && has3) { // empty else clauses
 
1040
          return node.slice(0, 3);
 
1041
        } else if (empty2 && !empty3) { // empty if blocks
 
1042
          return ['if', ['unary-prefix', '!', node[1]], node[3]];
 
1043
        } else if (empty2 && empty3) {
 
1044
          if (hasSideEffects(node[1])) {
 
1045
            return ['stat', node[1]];
 
1046
          } else {
 
1047
            return emptyNode();
 
1048
          }
 
1049
        }
 
1050
      } break;
 
1051
    }
 
1052
  }
 
1053
  traverseGeneratedFunctions(ast, function(node) {
 
1054
    vacuumInternal(node);
 
1055
    simplifyNotComps(node);
 
1056
  });
 
1057
}
 
1058
 
 
1059
function getStatements(node) {
 
1060
  if (node[0] == 'defun') {
 
1061
    return node[3];
 
1062
  } else if (node[0] == 'block') {
 
1063
    return node[1];
 
1064
  } else {
 
1065
    return null;
 
1066
  }
 
1067
}
 
1068
 
 
1069
// Multiple blocks from the relooper are, in general, implemented by
 
1070
//   if (label == x) { } else if ..
 
1071
// and branching into them by
 
1072
//   if (condition) { label == x } else ..
 
1073
// We can hoist the multiple block into the condition, thus removing code and one 'if' check
 
1074
function hoistMultiples(ast) {
 
1075
  traverseGeneratedFunctions(ast, function(node) {
 
1076
    traverse(node, function(node, type) {
 
1077
      var statements = getStatements(node);
 
1078
      if (!statements) return;
 
1079
      var modified = false;
 
1080
      for (var i = 0; i < statements.length-1; i++) {
 
1081
        var modifiedI = false;
 
1082
        var pre = statements[i];
 
1083
        if (pre[0] != 'if') continue;
 
1084
        var post = statements[i+1];
 
1085
        // Look into some block types. shell() will then recreate the shell that we looked into
 
1086
        var postInner = post;
 
1087
        var shellLabel = false, shellDo = false;
 
1088
        while (true) {
 
1089
          if (postInner[0] == 'label') {
 
1090
            shellLabel = postInner[1];
 
1091
            postInner = postInner[2];
 
1092
          } else if (postInner[0] == 'do') {
 
1093
            shellDo = postInner[1];
 
1094
            postInner = postInner[2][1][0];
 
1095
          } else {
 
1096
            break; // give up
 
1097
          }
 
1098
        }
 
1099
        if (postInner[0] != 'if') continue;
 
1100
        // Look into this if, and its elseifs
 
1101
        while (postInner && postInner[0] == 'if') {
 
1102
          var cond = postInner[1];
 
1103
          if (cond[0] == 'binary' && cond[1] == '==' && cond[2][0] == 'name' && cond[2][1] == 'label') {
 
1104
            assert(cond[3][0] == 'num');
 
1105
            // We have a valid Multiple check here. Try to hoist it, look for the source in |pre| and its else's
 
1106
            var labelNum = cond[3][1];
 
1107
            var labelBlock = postInner[2];
 
1108
            assert(labelBlock[0] == 'block');
 
1109
            var found = false;
 
1110
            traverse(pre, function(preNode, preType) {
 
1111
              if (!found && preType == 'assign' && preNode[2][0] == 'name' && preNode[2][1] == 'label') {
 
1112
                assert(preNode[3][0] == 'num');
 
1113
                if (preNode[3][1] == labelNum) {
 
1114
                  // That's it! Hoist away. We can also throw away the label setting as its goal has already been achieved
 
1115
                  found = true;
 
1116
                  modifiedI = true;
 
1117
                  postInner[2] = ['block', []];
 
1118
                  return labelBlock;
 
1119
                }
 
1120
              }
 
1121
            });
 
1122
          }
 
1123
          postInner = postInner[3]; // Proceed to look in the else clause
 
1124
        }
 
1125
        if (modifiedI) {
 
1126
          if (shellDo) {
 
1127
            statements[i] = ['do', shellDo, ['block', [statements[i]]]];
 
1128
          }
 
1129
          if (shellLabel) {
 
1130
            statements[i] = ['label', shellLabel, statements[i]];
 
1131
          }
 
1132
        }
 
1133
      }
 
1134
      if (modified) return node;
 
1135
    });
 
1136
 
 
1137
    // After hoisting in this function, it is safe to remove { label = x; } blocks, because
 
1138
    // if they were leading to the next code right after them, they would be hoisted, and if they
 
1139
    // are going to some other place entirely, they would break or continue. The only risky
 
1140
    // situation is if the code after us is a multiple, in which case we might be checking for
 
1141
    // this label inside it (or in a later multiple, even)
 
1142
    function tryEliminate(node) {
 
1143
      if (node[0] == 'if') {
 
1144
        var replaced;
 
1145
        if (replaced = tryEliminate(node[2])) node[2] = replaced;
 
1146
        if (node[3] && (replaced = tryEliminate(node[3]))) node[3] = replaced;
 
1147
      } else {
 
1148
        if (node[0] == 'block' && node[1] && node[1].length > 0) {
 
1149
          var subNode = node[1][node[1].length-1];
 
1150
          if (subNode[0] == 'stat' && subNode[1][0] == 'assign' && subNode[1][2][0] == 'name' &&
 
1151
              subNode[1][2][1] == 'label' && subNode[1][3][0] == 'num') {
 
1152
            if (node[1].length == 1) {
 
1153
              return emptyNode();
 
1154
            } else {
 
1155
              node[1].splice(node[1].length-1, 1);
 
1156
              return node;
 
1157
            }
 
1158
          }
 
1159
        }
 
1160
      }
 
1161
      return false;
 
1162
    }
 
1163
    function getActualStatement(node) { // find the actual active statement, ignoring a label and one-time do loop
 
1164
      if (node[0] == 'label') node = node[2];
 
1165
      if (node[0] == 'do') node = node[2];
 
1166
      if (node[0] == 'block' && node[1].length == 1) node = node[1][0];
 
1167
      return node;
 
1168
    }
 
1169
    vacuum(node);
 
1170
    traverse(node, function(node, type) {
 
1171
      var statements = getStatements(node);
 
1172
      if (!statements) return;
 
1173
      for (var i = 0; i < statements.length-1; i++) {
 
1174
        var curr = getActualStatement(statements[i]);
 
1175
        var next = statements[i+1];
 
1176
        if (curr[0] == 'if' && next[0] != 'if' && next[0] != 'label' && next[0] != 'do' && next[0] != 'while') {
 
1177
          tryEliminate(curr);
 
1178
        }
 
1179
      }
 
1180
    });
 
1181
  });
 
1182
 
 
1183
  vacuum(ast);
 
1184
 
 
1185
  // Afterpass: Reduce
 
1186
  //    if (..) { .. break|continue } else { .. }
 
1187
  // to
 
1188
  //    if (..) { .. break|continue } ..
 
1189
  traverseGenerated(ast, function(container, type) {
 
1190
    var statements = getStatements(container);
 
1191
    if (!statements) return;
 
1192
    for (var i = 0; i < statements.length; i++) {
 
1193
      var node = statements[i];
 
1194
      if (node[0] == 'if' && node[2][0] == 'block' && node[3] && node[3][0] == 'block') {
 
1195
        var stat1 = node[2][1], stat2 = node[3][1];
 
1196
        // If break|continue in the latter and not the former, reverse them
 
1197
        if (!(stat1[stat1.length-1][0] in LOOP_FLOW) && (stat2[stat2.length-1][0] in LOOP_FLOW)) {
 
1198
          var temp = node[3];
 
1199
          node[3] = node[2];
 
1200
          node[2] = temp;
 
1201
          node[1] = ['unary-prefix', '!', node[1]];
 
1202
          simplifyNotComps(node[1]); // bake the ! into the expression
 
1203
          stat1 = node[2][1];
 
1204
          stat2 = node[3][1];
 
1205
        }
 
1206
        if (stat1[stat1.length-1][0] in LOOP_FLOW) {
 
1207
          statements.splice.apply(statements, [i+1, 0].concat(stat2));
 
1208
          node[3] = null;
 
1209
        }
 
1210
      }
 
1211
    }
 
1212
  });
 
1213
}
 
1214
 
 
1215
// Simplifies loops
 
1216
// WARNING: This assumes all loops and breaks/continues are labelled
 
1217
function loopOptimizer(ast) {
 
1218
  // Remove unneeded labels and one-time (do while(0)) loops. It is convenient to do these both at once.
 
1219
  function passTwo(ast) {
 
1220
    var neededDos = [];
 
1221
    // Find unneeded labels
 
1222
    traverseGenerated(ast, function(node, type, stack) {
 
1223
      if (type == 'label' && node[2][0] in LOOP) {
 
1224
        // this is a labelled loop. we don't know if it's needed yet. Mark its label for removal for now now.
 
1225
        stack.push(node);
 
1226
        node[1] = '+' + node[1];
 
1227
      } else if (type in LOOP) {
 
1228
        stack.push(node);
 
1229
      } else if (type in LOOP_FLOW) {
 
1230
        // Find topmost loop, and its label if there is one
 
1231
        var lastLabel = null, lastLoop = null, i = stack.length-1;
 
1232
        while (i >= 0 && !lastLoop) {
 
1233
          if (stack[i][0] in LOOP) lastLoop = stack[i];
 
1234
          i--;
 
1235
        }
 
1236
        assert(lastLoop, 'Cannot break/continue without a Label');
 
1237
        while (i >= 0 && !lastLabel) {
 
1238
          if (stack[i][0] in LOOP) break; // another loop in the middle - no label for lastLoop
 
1239
          if (stack[i][0] == 'label') lastLabel = stack[i];
 
1240
          i--;
 
1241
        }
 
1242
        var ident = node[1]; // there may not be a label ident if this is a simple break; or continue;
 
1243
        var plus = '+' + ident;
 
1244
        if (lastLabel && ident && (ident == lastLabel[1] || plus == lastLabel[1])) {
 
1245
          // If this is a 'do' loop, this break means we actually need it.
 
1246
          neededDos.push(lastLoop);
 
1247
          // We don't need the control flow command to have a label - it's referring to the current loop
 
1248
          return [node[0]];
 
1249
        } else {
 
1250
          if (!ident) {
 
1251
            // No label on the break/continue, so keep the last loop alive (no need for its label though)
 
1252
            neededDos.push(lastLoop);
 
1253
          } else {
 
1254
            // Find the label node that needs to stay alive
 
1255
            stack.forEach(function(label) {
 
1256
              if (!label) return;
 
1257
              if (label[1] == plus) label[1] = label[1].substr(1); // Remove '+', marking it as needed
 
1258
            });
 
1259
          }
 
1260
        }
 
1261
      }
 
1262
    }, null, []);
 
1263
    // We return whether another pass is necessary
 
1264
    var more = false;
 
1265
    // Remove unneeded labels
 
1266
    traverseGenerated(ast, function(node, type) {
 
1267
      if (type == 'label' && node[1][0] == '+') {
 
1268
        more = true;
 
1269
        var ident = node[1].substr(1);
 
1270
        // Remove label from loop flow commands
 
1271
        traverse(node[2], function(node2, type) {
 
1272
          if (type in LOOP_FLOW && node2[1] == ident) {
 
1273
            return [node2[0]];
 
1274
          }
 
1275
        });
 
1276
        return node[2]; // Remove the label itself on the loop
 
1277
      }
 
1278
    });
 
1279
    // Remove unneeded one-time loops. We need such loops if (1) they have a label, or (2) they have a direct break so they are in neededDos.
 
1280
    // First, add all labeled loops of this nature to neededDos
 
1281
    traverseGenerated(ast, function(node, type) {
 
1282
      if (type == 'label' && node[2][0] == 'do') {
 
1283
        neededDos.push(node[2]);
 
1284
      }
 
1285
    });
 
1286
    // Remove unneeded dos, we know who they are now
 
1287
    traverseGenerated(ast, function(node, type) {
 
1288
      if (type == 'do' && neededDos.indexOf(node) < 0) {
 
1289
        assert(jsonCompare(node[1], ['num', 0]), 'Trying to remove a one-time do loop that is not one of our generated ones.;');
 
1290
        more = true;
 
1291
        return node[2];
 
1292
      }
 
1293
    });
 
1294
    return more;
 
1295
  }
 
1296
 
 
1297
  // Go
 
1298
 
 
1299
  // TODO: pass 1: Removal of unneeded continues, breaks if they get us to where we are already going. That will
 
1300
  //               help the next pass.
 
1301
 
 
1302
  // Multiple pass two runs may be needed, as we remove one-time loops and so forth
 
1303
  do {
 
1304
    var more = passTwo(ast);
 
1305
    vacuum(ast);
 
1306
  } while (more);
 
1307
 
 
1308
  vacuum(ast);
 
1309
}
 
1310
 
 
1311
function unVarify(vars, ret) { // transform var x=1, y=2 etc. into (x=1, y=2), i.e., the same assigns, but without a var definition
 
1312
  ret = ret || [];
 
1313
  ret[0] = 'stat';
 
1314
  if (vars.length == 1) {
 
1315
    ret[1] = ['assign', true, ['name', vars[0][0]], vars[0][1]];
 
1316
  } else {
 
1317
    ret[1] = [];
 
1318
    var curr = ret[1];
 
1319
    for (var i = 0; i < vars.length-1; i++) {
 
1320
      curr[0] = 'seq';
 
1321
      curr[1] = ['assign', true, ['name', vars[i][0]], vars[i][1]];
 
1322
      if (i != vars.length-2) curr = curr[2] = [];
 
1323
    }
 
1324
    curr[2] = ['assign', true, ['name', vars[vars.length-1][0]], vars[vars.length-1][1]];
 
1325
  }
 
1326
  return ret;
 
1327
}
 
1328
 
 
1329
// asm.js support code - normalize (convert asm.js code to 'normal' JS, without
 
1330
// annotations, plus explicit metadata) and denormalize (vice versa)
 
1331
var ASM_INT = 0;
 
1332
var ASM_DOUBLE = 1;
 
1333
 
 
1334
function detectAsmCoercion(node) {
 
1335
  // for params, +x vs x|0, for vars, 0.0 vs 0
 
1336
  if (node[0] == 'num' && node[1].toString().indexOf('.') >= 0) return ASM_DOUBLE;
 
1337
  return node[0] == 'unary-prefix' ? ASM_DOUBLE : ASM_INT;
 
1338
}
 
1339
 
 
1340
function makeAsmParamCoercion(param, type) {
 
1341
  return type == ASM_INT ? ['binary', '|', ['name', param], ['num', 0]] : ['unary-prefix', '+', ['name', param]];
 
1342
}
 
1343
 
 
1344
function makeAsmVarDef(v, type) {
 
1345
  return [v, type == ASM_INT ? ['num', 0] : ['unary-prefix', '+', ['num', 0]]];
 
1346
}
 
1347
 
 
1348
function normalizeAsm(func) {
 
1349
  //printErr('pre-normalize \n\n' + astToSrc(func) + '\n\n');
 
1350
  var data = {
 
1351
    params: {}, // ident => ASM_* type
 
1352
    vars: {}, // ident => ASM_* type
 
1353
  };
 
1354
  // process initial params
 
1355
  var stats = func[3];
 
1356
  var i = 0;
 
1357
  while (i < stats.length) {
 
1358
    var node = stats[i];
 
1359
    if (node[0] != 'stat' || node[1][0] != 'assign' || node[1][2][0] != 'name') break;
 
1360
    node = node[1];
 
1361
    var name = node[2][1];
 
1362
    if (func[2] && func[2].indexOf(name) < 0) break; // not an assign into a parameter, but a global
 
1363
    data.params[name] = detectAsmCoercion(node[3]);
 
1364
    stats[i] = emptyNode();
 
1365
    i++;
 
1366
  }
 
1367
  // process initial variable definitions
 
1368
  outer:
 
1369
  while (i < stats.length) {
 
1370
    var node = stats[i];
 
1371
    if (node[0] != 'var') break;
 
1372
    for (var j = 0; j < node[1].length; j++) {
 
1373
      var v = node[1][j];
 
1374
      var name = v[0];
 
1375
      var value = v[1];
 
1376
      if (!(name in data.vars)) {
 
1377
        assert(value[0] == 'num' || (value[0] == 'unary-prefix' && value[2][0] == 'num')); // must be valid coercion no-op
 
1378
        data.vars[name] = detectAsmCoercion(value);
 
1379
        v.length = 1; // make an un-assigning var
 
1380
      } else {
 
1381
        break outer;
 
1382
      }
 
1383
    }
 
1384
    i++;
 
1385
  }
 
1386
  // finally, look for other var definitions and collect them
 
1387
  while (i < stats.length) {
 
1388
    traverse(stats[i], function(node, type) {
 
1389
      if (type == 'var') {
 
1390
        for (var j = 0; j < node[1].length; j++) {
 
1391
          var v = node[1][j];
 
1392
          var name = v[0];
 
1393
          var value = v[1];
 
1394
          if (!(name in data.vars)) {
 
1395
            data.vars[name] = detectAsmCoercion(value);
 
1396
          }
 
1397
        }
 
1398
        unVarify(node[1], node);
 
1399
      } else if (type == 'dot') {
 
1400
        if (node[1][0] == 'name' && node[1][1] == 'Math') {
 
1401
          // transform Math.max to Math_max; we forward in the latter version
 
1402
          node[0] = 'name';
 
1403
          node[1] = 'Math_' + node[2];
 
1404
        }
 
1405
      }
 
1406
    });
 
1407
    i++;
 
1408
  }
 
1409
  //printErr('normalized \n\n' + astToSrc(func) + '\n\nwith: ' + JSON.stringify(data));
 
1410
  return data;
 
1411
}
 
1412
 
 
1413
function denormalizeAsm(func, data) {
 
1414
  //printErr('pre-denormalize \n\n' + astToSrc(func) + '\n\nwith: ' + JSON.stringify(data));
 
1415
  var stats = func[3];
 
1416
  // Remove var definitions, if any
 
1417
  for (var i = 0; i < stats.length; i++) {
 
1418
    if (stats[i][0] == 'var') {
 
1419
      stats[i] = emptyNode();
 
1420
    } else {
 
1421
      if (!isEmptyNode(stats[i])) break;
 
1422
    }
 
1423
  }
 
1424
  // each param needs a line; reuse emptyNodes as much as we can
 
1425
  var numParams = 0;
 
1426
  for (var i in data.params) numParams++;
 
1427
  var emptyNodes = 0;
 
1428
  while (emptyNodes < stats.length) {
 
1429
    if (!isEmptyNode(stats[emptyNodes])) break;
 
1430
    emptyNodes++;
 
1431
  }
 
1432
  var neededEmptyNodes = numParams + 1; // params plus one big var
 
1433
  if (neededEmptyNodes > emptyNodes) {
 
1434
    var args = [0, 0];
 
1435
    for (var i = 0; i < neededEmptyNodes - emptyNodes; i++) args[i+2] = 0;
 
1436
    stats.splice.apply(stats, args);
 
1437
  }
 
1438
  // add param coercions
 
1439
  var next = 0;
 
1440
  func[2].forEach(function(param) {
 
1441
    stats[next++] = ['stat', ['assign', true, ['name', param], makeAsmParamCoercion(param, data.params[param])]];
 
1442
  });
 
1443
  // add variable definitions
 
1444
  var varDefs = [];
 
1445
  for (var v in data.vars) {
 
1446
    varDefs.push(makeAsmVarDef(v, data.vars[v]));
 
1447
  }
 
1448
  if (varDefs.length) {
 
1449
    stats[next] = ['var', varDefs];
 
1450
  } else {
 
1451
    stats[next] = emptyNode();
 
1452
  }
 
1453
  //printErr('denormalized \n\n' + astToSrc(func) + '\n\n');
 
1454
}
 
1455
 
 
1456
// Very simple 'registerization', coalescing of variables into a smaller number,
 
1457
// as part of minification. Globals-level minification began in a previous pass,
 
1458
// we receive minifierInfo which tells us how to rename globals. (Only in asm.js.)
 
1459
//
 
1460
// We do not optimize when there are switches, so this pass only makes sense with
 
1461
// relooping.
 
1462
// TODO: Consider how this fits in with the rest of the optimization toolchain. Do
 
1463
//       we still need the eliminator? Closure? And in what order? Perhaps just
 
1464
//       closure simple?
 
1465
function registerize(ast) {
 
1466
  traverseGeneratedFunctions(ast, function(fun) {
 
1467
    if (asm) var asmData = normalizeAsm(fun);
 
1468
    // Add parameters as a first (fake) var (with assignment), so they get taken into consideration
 
1469
    var params = {}; // note: params are special, they can never share a register between them (see later)
 
1470
    if (fun[2] && fun[2].length) {
 
1471
      var assign = ['num', 0];
 
1472
      fun[3].unshift(['var', fun[2].map(function(param) {
 
1473
        params[param] = 1;
 
1474
        return [param, assign];
 
1475
      })]);
 
1476
    }
 
1477
    if (asm) {
 
1478
      // copy params into vars
 
1479
      for (var p in asmData.params) asmData.vars[p] = asmData.params[p];
 
1480
      //printErr('fake params: \n\n' + astToSrc(fun) + '\n\n');
 
1481
    }
 
1482
    // Replace all var definitions with assignments; we will add var definitions at the top after we registerize
 
1483
    // We also mark local variables - i.e., having a var definition
 
1484
    var localVars = {};
 
1485
    var hasSwitch = false; // we cannot optimize variables if there is a switch
 
1486
    traverse(fun, function(node, type) {
 
1487
      if (type == 'var') {
 
1488
        node[1].forEach(function(defined) { localVars[defined[0]] = 1 });
 
1489
        var vars = node[1].filter(function(varr) { return varr[1] });
 
1490
        if (vars.length >= 1) {
 
1491
          return unVarify(vars);
 
1492
        } else {
 
1493
          return emptyNode();
 
1494
        }
 
1495
      } else if (type == 'switch') {
 
1496
        hasSwitch = true;
 
1497
      }
 
1498
    });
 
1499
    vacuum(fun);
 
1500
    if (minifierInfo) {
 
1501
      assert(asm);
 
1502
      var usedGlobals = {};
 
1503
      var nextLocal = 0;
 
1504
      // Minify globals using the mapping we were given
 
1505
      traverse(fun, function(node, type) {
 
1506
        if (type == 'name') {
 
1507
          var name = node[1];
 
1508
          var minified = minifierInfo.globals[name];
 
1509
          if (minified) {
 
1510
            assert(!localVars[name], name); // locals must not shadow globals, or else we don't know which is which
 
1511
            if (localVars[minified]) {
 
1512
              // trying to minify a global into a name used locally. rename all the locals
 
1513
              var newName = '$_newLocal_' + (nextLocal++);
 
1514
              assert(!localVars[newName]);
 
1515
              if (params[minified]) {
 
1516
                params[newName] = 1;
 
1517
                delete params[minified];
 
1518
              }
 
1519
              localVars[newName] = 1;
 
1520
              delete localVars[minified];
 
1521
              asmData.vars[newName] = asmData.vars[minified];
 
1522
              delete asmData.vars[minified];
 
1523
              asmData.params[newName] = asmData.params[minified];
 
1524
              delete asmData.params[minified];
 
1525
              traverse(fun, function(node, type) {
 
1526
                if (type == 'name' && node[1] == minified) {
 
1527
                  node[1] = newName;
 
1528
                }
 
1529
              });
 
1530
              if (fun[2]) {
 
1531
                for (var i = 0; i < fun[2].length; i++) {
 
1532
                  if (fun[2][i] == minified) fun[2][i] = newName;
 
1533
                }
 
1534
              }
 
1535
            }
 
1536
            node[1] = minified;
 
1537
            usedGlobals[minified] = 1;
 
1538
          }
 
1539
        }
 
1540
      });
 
1541
      assert(fun[1] in minifierInfo.globals, fun[1]);
 
1542
      fun[1] = minifierInfo.globals[fun[1]];
 
1543
      assert(fun[1]);
 
1544
      var nextRegName = 0;
 
1545
    }
 
1546
    var regTypes = {};
 
1547
    function getNewRegName(num, name) {
 
1548
      if (!asm) return 'r' + num;
 
1549
      var type = asmData.vars[name];
 
1550
      if (!minifierInfo) {
 
1551
        var ret = (type ? 'd' : 'i') + num;
 
1552
        regTypes[ret] = type;
 
1553
        return ret;
 
1554
      }
 
1555
      // find the next free minified name that is not used by a global that shows up in this function
 
1556
      while (nextRegName < minifierInfo.names.length) {
 
1557
        var ret = minifierInfo.names[nextRegName++];
 
1558
        if (!usedGlobals[ret]) {
 
1559
          regTypes[ret] = type;
 
1560
          return ret;
 
1561
        }
 
1562
      }
 
1563
      assert('ran out of names');
 
1564
    }
 
1565
    // Find the # of uses of each variable.
 
1566
    // While doing so, check if all a variable's uses are dominated in a simple
 
1567
    // way by a simple assign, if so, then we can assign its register to it
 
1568
    // just for its definition to its last use, and not to the entire toplevel loop,
 
1569
    // we call such variables "optimizable"
 
1570
    var varUses = {};
 
1571
    var level = 1;
 
1572
    var levelDominations = {};
 
1573
    var varLevels = {};
 
1574
    var possibles = {};
 
1575
    var unoptimizables = {};
 
1576
    traverse(fun, function(node, type) {
 
1577
      if (type == 'name') {
 
1578
        var name = node[1];
 
1579
        if (localVars[name]) {
 
1580
          if (!varUses[name]) varUses[name] = 0;
 
1581
          varUses[name]++;
 
1582
          if (possibles[name] && !varLevels[name]) unoptimizables[name] = 1; // used outside of simple domination
 
1583
        }
 
1584
      } else if (type == 'assign' && typeof node[1] != 'string') {
 
1585
        if (node[2] && node[2][0] == 'name') {
 
1586
          var name = node[2][1];
 
1587
          // if local and not yet used, this might be optimizable if we dominate
 
1588
          // all other uses
 
1589
          if (localVars[name] && !varUses[name] && !varLevels[name]) {
 
1590
            possibles[name] = 1;
 
1591
            varLevels[name] = level;
 
1592
            if (!levelDominations[level]) levelDominations[level] = {};
 
1593
            levelDominations[level][name] = 1;
 
1594
          }
 
1595
        }
 
1596
      } else if (type in CONTROL_FLOW) {
 
1597
        level++;
 
1598
      }
 
1599
    }, function(node, type) {
 
1600
      if (type in CONTROL_FLOW) {
 
1601
        // Invalidate all dominating on this level, further users make it unoptimizable
 
1602
        for (var name in levelDominations[level]) {
 
1603
          varLevels[name] = 0;
 
1604
        }
 
1605
        levelDominations[level] = null;
 
1606
        level--;
 
1607
      }
 
1608
    });
 
1609
    var optimizables = {};
 
1610
    if (!hasSwitch) {
 
1611
      for (var possible in possibles) {
 
1612
        if (!unoptimizables[possible]) optimizables[possible] = 1;
 
1613
      }
 
1614
    }
 
1615
    // Go through the function's code, assigning 'registers'.
 
1616
    // The only tricky bit is to keep variables locked on a register through loops,
 
1617
    // since they can potentially be returned to. Optimizable variables lock onto
 
1618
    // loops that they enter, unoptimizable variables lock in a conservative way
 
1619
    // into the topmost loop.
 
1620
    // Note that we cannot lock onto a variable in a loop if it was used and free'd
 
1621
    // before! (then they could overwrite us in the early part of the loop). For now
 
1622
    // we just use a fresh register to make sure we avoid this, but it could be
 
1623
    // optimized to check for safe registers (free, and not used in this loop level).
 
1624
    var varRegs = {}; // maps variables to the register they will use all their life
 
1625
    var freeRegsClasses = asm ? [[], []] : []; // two classes for asm, one otherwise
 
1626
    var nextReg = 1;
 
1627
    var fullNames = {};
 
1628
    var loopRegs = {}; // for each loop nesting level, the list of bound variables
 
1629
    var loops = 0; // 0 is toplevel, 1 is first loop, etc
 
1630
    var saved = 0;
 
1631
    var activeOptimizables = {};
 
1632
    var optimizableLoops = {};
 
1633
    var paramRegs = {}; // true if the register is used by a parameter (and so needs no def at start of function; also cannot
 
1634
                        // be shared with another param, each needs its own)
 
1635
    function decUse(name) {
 
1636
      if (!varUses[name]) return false; // no uses left, or not a relevant variable
 
1637
      if (optimizables[name]) activeOptimizables[name] = 1;
 
1638
      var reg = varRegs[name];
 
1639
      if (asm) assert(name in asmData.vars, name);
 
1640
      var freeRegs = asm ? freeRegsClasses[asmData.vars[name]] : freeRegsClasses;
 
1641
      if (!reg) {
 
1642
        // acquire register
 
1643
        if (optimizables[name] && freeRegs.length > 0 &&
 
1644
            !(params[name] && paramRegs[freeRegs[freeRegs.length-1]])) { // do not share registers between parameters
 
1645
          reg = freeRegs.pop();
 
1646
          saved++;
 
1647
        } else {
 
1648
          reg = nextReg++;
 
1649
          fullNames[reg] = getNewRegName(reg, name);
 
1650
          if (params[name]) paramRegs[reg] = 1;
 
1651
        }
 
1652
        varRegs[name] = reg;
 
1653
      }
 
1654
      varUses[name]--;
 
1655
      assert(varUses[name] >= 0);
 
1656
      if (varUses[name] == 0) {
 
1657
        if (optimizables[name]) delete activeOptimizables[name];
 
1658
        // If we are not in a loop, or we are optimizable and not bound to a loop
 
1659
        // (we might have been in one but left it), we can free the register now.
 
1660
        if (loops == 0 || (optimizables[name] && !optimizableLoops[name])) {
 
1661
          // free register
 
1662
          freeRegs.push(reg);
 
1663
        } else {
 
1664
          // when the relevant loop is exited, we will free the register
 
1665
          var releventLoop = optimizables[name] ? (optimizableLoops[name] || 1) : 1;
 
1666
          if (!loopRegs[releventLoop]) loopRegs[releventLoop] = [];
 
1667
          loopRegs[releventLoop].push(reg);
 
1668
        }
 
1669
      }
 
1670
      return true;
 
1671
    }
 
1672
    traverse(fun, function(node, type) { // XXX we rely on traversal order being the same as execution order here
 
1673
      if (type == 'name') {
 
1674
        var name = node[1];
 
1675
        if (decUse(name)) {
 
1676
          node[1] = fullNames[varRegs[name]];
 
1677
        }
 
1678
      } else if (type in LOOP) {
 
1679
        loops++;
 
1680
        // Active optimizables lock onto this loop, if not locked onto one that encloses this one
 
1681
        for (var name in activeOptimizables) {
 
1682
          if (!optimizableLoops[name]) {
 
1683
            optimizableLoops[name] = loops;
 
1684
          }
 
1685
        }
 
1686
      }
 
1687
    }, function(node, type) {
 
1688
      if (type in LOOP) {
 
1689
        // Free registers that were locked to this loop
 
1690
        if (loopRegs[loops]) {
 
1691
          if (asm) {
 
1692
            loopRegs[loops].forEach(function(loopReg) {
 
1693
              freeRegsClasses[regTypes[fullNames[loopReg]]].push(loopReg);
 
1694
            });
 
1695
          } else {
 
1696
            freeRegsClasses = freeRegsClasses.concat(loopRegs[loops]);
 
1697
          }
 
1698
          loopRegs[loops].length = 0;
 
1699
        }
 
1700
        loops--;
 
1701
      }
 
1702
    });
 
1703
    if (fun[2] && fun[2].length) {
 
1704
      fun[2].length = 0; // clear params, we will fill with registers
 
1705
      fun[3].shift(); // remove fake initial var
 
1706
    }
 
1707
    //printErr('var regs: ' + JSON.stringify(varRegs) + '\n\nparam regs: ' + JSON.stringify(paramRegs));
 
1708
    if (!asm) {
 
1709
      if (nextReg > 1) {
 
1710
        var vars = [];
 
1711
        for (var i = 1; i < nextReg; i++) {
 
1712
          var reg = fullNames[i];
 
1713
          if (!paramRegs[i]) {
 
1714
            vars.push([reg]);
 
1715
          } else {
 
1716
            fun[2].push(reg);
 
1717
          }
 
1718
        }
 
1719
        if (vars.length > 0) getStatements(fun).unshift(['var', vars]);
 
1720
      }
 
1721
    } else {
 
1722
      //printErr('unfake params: \n\n' + astToSrc(fun) + '\n\n');
 
1723
      var finalAsmData = {
 
1724
        params: {},
 
1725
        vars: {},
 
1726
      };
 
1727
      for (var i = 1; i < nextReg; i++) {
 
1728
        var reg = fullNames[i];
 
1729
        var type = regTypes[reg];
 
1730
        if (!paramRegs[i]) {
 
1731
          finalAsmData.vars[reg] = type;
 
1732
        } else {
 
1733
          finalAsmData.params[reg] = type;
 
1734
          fun[2].push(reg);
 
1735
        }
 
1736
      }
 
1737
      denormalizeAsm(fun, finalAsmData);
 
1738
    }
 
1739
  });
 
1740
}
 
1741
 
 
1742
// Eliminator aka Expressionizer
 
1743
//
 
1744
// The goal of this pass is to eliminate unneeded variables (which represent one of the infinite registers in the LLVM
 
1745
// model) and thus to generate complex expressions where possible, for example
 
1746
//
 
1747
//  var x = a(10);
 
1748
//  var y = HEAP[20];
 
1749
//  print(x+y);
 
1750
//
 
1751
// can be transformed into
 
1752
//
 
1753
//  print(a(10)+HEAP[20]);
 
1754
//
 
1755
// The basic principle is to scan along the code in the order of parsing/execution, and keep a list of tracked
 
1756
// variables that are current contenders for elimination. We must untrack when we see something that we cannot
 
1757
// cross, for example, a write to memory means we must invalidate variables that depend on reading from
 
1758
// memory, since if we change the order then we do not preserve the computation.
 
1759
//
 
1760
// We rely on some assumptions about emscripten-generated code here, which means we can do a lot more than
 
1761
// a general JS optimization can. For example, we assume that 'sub' nodes (indexing like HEAP[..]) are
 
1762
// memory accesses or FUNCTION_TABLE accesses, and in both cases that the symbol cannot be replaced although
 
1763
// the contents can. So we assume FUNCTION_TABLE might have its contents changed but not be pointed to
 
1764
// a different object, which allows
 
1765
//
 
1766
//  var x = f();
 
1767
//  FUNCTION_TABLE[x]();
 
1768
//
 
1769
// to be optimized (f could replace FUNCTION_TABLE, so in general JS eliminating x is not valid).
 
1770
//
 
1771
// In memSafe mode, we are more careful and assume functions can replace HEAP and FUNCTION_TABLE, which
 
1772
// can happen in ALLOW_MEMORY_GROWTH mode
 
1773
 
 
1774
var ELIMINATION_SAFE_NODES = set('var', 'assign', 'call', 'if', 'toplevel', 'do', 'return', 'label'); // do is checked carefully, however
 
1775
var NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS = set('name', 'num', 'string', 'binary', 'sub', 'unary-prefix');
 
1776
var IGNORABLE_ELIMINATOR_SCAN_NODES = set('num', 'toplevel', 'string', 'break', 'continue', 'dot'); // dot can only be STRING_TABLE.*
 
1777
var ABORTING_ELIMINATOR_SCAN_NODES = set('new', 'object', 'function', 'defun', 'switch', 'for', 'while', 'array', 'throw'); // we could handle some of these, TODO, but nontrivial (e.g. for while, the condition is hit multiple times after the body)
 
1778
 
 
1779
function eliminate(ast, memSafe) {
 
1780
  // Find variables that have a single use, and if they can be eliminated, do so
 
1781
  traverseGeneratedFunctions(ast, function(func, type) {
 
1782
    if (asm) var asmData = normalizeAsm(func);
 
1783
    //printErr('eliminate in ' + func[1]);
 
1784
 
 
1785
    // First, find the potentially eliminatable functions: that have one definition and one use
 
1786
    var definitions = {};
 
1787
    var uses = {};
 
1788
    var values = {};
 
1789
    var locals = {};
 
1790
    var varsToRemove = {}; // variables being removed, that we can eliminate all 'var x;' of (this refers to 'var' nodes we should remove)
 
1791
                           // 1 means we should remove it, 2 means we successfully removed it
 
1792
    var varsToTryToRemove = {}; // variables that have 0 uses, but have side effects - when we scan we can try to remove them
 
1793
    // add arguments as locals
 
1794
    if (func[2]) {
 
1795
      for (var i = 0; i < func[2].length; i++) {
 
1796
        locals[func[2][i]] = true;
 
1797
      }
 
1798
    }
 
1799
    // examine body and note locals
 
1800
    traverse(func, function(node, type) {
 
1801
      if (type === 'var') {
 
1802
        var node1 = node[1];
 
1803
        for (var i = 0; i < node1.length; i++) {
 
1804
          var node1i = node1[i];
 
1805
          var name = node1i[0];
 
1806
          var value = node1i[1];
 
1807
          if (value) {
 
1808
            if (!(name in definitions)) definitions[name] = 0;
 
1809
            definitions[name]++;
 
1810
            if (!values[name]) values[name] = value;
 
1811
          }
 
1812
          if (!uses[name]) uses[name] = 0;
 
1813
          locals[name] = true;
 
1814
        }
 
1815
      } else if (type === 'name') {
 
1816
        var name = node[1];
 
1817
        if (!uses[name]) uses[name] = 0;
 
1818
        uses[name]++;
 
1819
      } else if (type == 'assign') {
 
1820
        var target = node[2];
 
1821
        if (target[0] == 'name') {
 
1822
          var name = target[1];
 
1823
          if (!(name in definitions)) definitions[name] = 0;
 
1824
          definitions[name]++;
 
1825
          if (!uses[name]) uses[name] = 0;
 
1826
          if (!values[name]) values[name] = node[3];
 
1827
          if (node[1] === true) { // not +=, -= etc., just =
 
1828
            uses[name]--; // because the name node will show up by itself in the previous case
 
1829
          }
 
1830
        }
 
1831
      }
 
1832
    });
 
1833
    var potentials = {}; // local variables with 1 definition and 1 use
 
1834
    var sideEffectFree = {}; // whether a local variable has no side effects in its definition
 
1835
    for (var name in locals) {
 
1836
      if (definitions[name] == 1 && uses[name] == 1) {
 
1837
        potentials[name] = 1;
 
1838
      } else if (uses[name] == 0 && (!definitions[name] || definitions[name] <= 1)) { // no uses, no def or 1 def (cannot operate on phis, and the llvm optimizer will remove unneeded phis anyhow)
 
1839
        var hasSideEffects = false;
 
1840
        if (values[name]) {
 
1841
          traverse(values[name], function(node, type) {
 
1842
            if (!(type in NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS)) {
 
1843
              hasSideEffects = true; // cannot remove this unused variable, constructing it has side effects
 
1844
              return true;
 
1845
            }
 
1846
          });
 
1847
        }
 
1848
        if (!hasSideEffects) {
 
1849
          varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally
 
1850
          sideEffectFree[name] = true;
 
1851
        } else {
 
1852
          varsToTryToRemove[name] = 1; // try to remove it later during scanning
 
1853
        }
 
1854
      }
 
1855
    }
 
1856
    //printErr('defs: ' + JSON.stringify(definitions));
 
1857
    //printErr('uses: ' + JSON.stringify(uses));
 
1858
    //printErr('values: ' + JSON.stringify(values));
 
1859
    //printErr('locals: ' + JSON.stringify(locals));
 
1860
    //printErr('varsToRemove: ' + JSON.stringify(varsToRemove));
 
1861
    //printErr('varsToTryToRemove: ' + JSON.stringify(varsToTryToRemove));
 
1862
    definitions = values = null;
 
1863
    //printErr('potentials: ' + JSON.stringify(potentials));
 
1864
    // We can now proceed through the function. In each list of statements, we try to eliminate
 
1865
    var tracked = {};
 
1866
    var globalsInvalidated = false; // do not repeat invalidations, until we track something new
 
1867
    var memoryInvalidated = false;
 
1868
    var callsInvalidated = false;
 
1869
    function track(name, value, defNode) { // add a potential that has just been defined to the tracked list, we hope to eliminate it
 
1870
      var usesGlobals = false, usesMemory = false, deps = {}, doesCall = false;
 
1871
      var ignoreName = false; // one-time ignorings of names, as first op in sub and call
 
1872
      traverse(value, function(node, type) {
 
1873
        if (type == 'name') {
 
1874
          if (!ignoreName) {
 
1875
            var name = node[1];
 
1876
            if (!(name in locals)) {
 
1877
              usesGlobals = true;
 
1878
            }
 
1879
            if (!(name in potentials)) { // deps do not matter for potentials - they are defined once, so no complexity
 
1880
              deps[name] = 1;
 
1881
            }
 
1882
          } else {
 
1883
            ignoreName = false;
 
1884
          }
 
1885
        } else if (type == 'sub') {
 
1886
          usesMemory = true;
 
1887
          ignoreName = true;
 
1888
        } else if (type == 'call') {
 
1889
          usesGlobals = true;
 
1890
          usesMemory = true;
 
1891
          doesCall = true;        
 
1892
          ignoreName = true;
 
1893
        } else {
 
1894
          ignoreName = false;
 
1895
        }
 
1896
      });
 
1897
      tracked[name] = {
 
1898
        usesGlobals: usesGlobals,
 
1899
        usesMemory: usesMemory,
 
1900
        defNode: defNode,
 
1901
        deps: deps,
 
1902
        doesCall: doesCall
 
1903
      };
 
1904
      globalsInvalidated = false;
 
1905
      memoryInvalidated = false;
 
1906
      callsInvalidated = false;
 
1907
      //printErr('track ' + [name, JSON.stringify(tracked[name])]);
 
1908
    }
 
1909
    var temp = [];
 
1910
    // TODO: invalidate using a sequence number for each type (if you were tracked before the last invalidation, you are cancelled). remove for.in loops
 
1911
    function invalidateGlobals() {
 
1912
      //printErr('invalidate globals');
 
1913
      temp.length = 0;
 
1914
      for (var name in tracked) {
 
1915
        var info = tracked[name];
 
1916
        if (info.usesGlobals) {
 
1917
          temp.push(name);
 
1918
        }
 
1919
      }
 
1920
      for (var i = 0; i < temp.length; i++) {
 
1921
        delete tracked[temp[i]];
 
1922
      }
 
1923
    }
 
1924
    function invalidateMemory() {
 
1925
      //printErr('invalidate memory');
 
1926
      temp.length = 0;
 
1927
      for (var name in tracked) {
 
1928
        var info = tracked[name];
 
1929
        if (info.usesMemory) {
 
1930
          temp.push(name);
 
1931
        }
 
1932
      }
 
1933
      for (var i = 0; i < temp.length; i++) {
 
1934
        delete tracked[temp[i]];
 
1935
      }
 
1936
    }
 
1937
    function invalidateByDep(dep) {
 
1938
      //printErr('invalidate by dep ' + dep);
 
1939
      temp.length = 0;
 
1940
      for (var name in tracked) {
 
1941
        var info = tracked[name];
 
1942
        if (info.deps[dep]) {
 
1943
          temp.push(name);
 
1944
        }
 
1945
      }
 
1946
      for (var i = 0; i < temp.length; i++) {
 
1947
        delete tracked[temp[i]];
 
1948
      }
 
1949
    }
 
1950
    function invalidateCalls() {
 
1951
      //printErr('invalidate calls');
 
1952
      temp.length = 0;
 
1953
      for (var name in tracked) {
 
1954
        var info = tracked[name];
 
1955
        if (info.doesCall) {
 
1956
          temp.push(name);
 
1957
        }
 
1958
      }
 
1959
      for (var i = 0; i < temp.length; i++) {
 
1960
        delete tracked[temp[i]];
 
1961
      }
 
1962
    }
 
1963
 
 
1964
    // Generate the sequence of execution. This determines what is executed before what, so we know what can be reordered. Using
 
1965
    // that, performs invalidations and eliminations
 
1966
    function scan(node) {
 
1967
      //printErr('scan: ' + JSON.stringify(node).substr(0, 50) + ' : ' + keys(tracked));
 
1968
      var abort = false;
 
1969
      var allowTracking = true; // false inside an if; also prevents recursing in an if
 
1970
      //var nesting = 1; // printErr-related
 
1971
      function traverseInOrder(node, ignoreSub, ignoreName) {
 
1972
        if (abort) return;
 
1973
        //nesting++; // printErr-related
 
1974
        //printErr(spaces(2*(nesting+1)) + 'trav: ' + JSON.stringify(node).substr(0, 50) + ' : ' + keys(tracked) + ' : ' + [allowTracking, ignoreSub, ignoreName]);
 
1975
        var type = node[0];
 
1976
        if (type == 'assign') {
 
1977
          var target = node[2];
 
1978
          var value = node[3];
 
1979
          var nameTarget = target[0] == 'name';
 
1980
          traverseInOrder(target, true, nameTarget); // evaluate left
 
1981
          traverseInOrder(value); // evaluate right
 
1982
          // do the actual assignment
 
1983
          if (nameTarget) {
 
1984
            var name = target[1];
 
1985
            if (!(name in potentials)) {
 
1986
              if (!(name in varsToTryToRemove)) {
 
1987
                // expensive check for invalidating specific tracked vars. This list is generally quite short though, because of
 
1988
                // how we just eliminate in short spans and abort when control flow happens TODO: history numbers instead
 
1989
                invalidateByDep(name); // can happen more than once per dep..
 
1990
                if (!(name in locals) && !globalsInvalidated) {
 
1991
                  invalidateGlobals();
 
1992
                  globalsInvalidated = true;
 
1993
                }
 
1994
                // if we can track this name (that we assign into), and it has 0 uses and we want to remove its 'var'
 
1995
                // definition - then remove it right now, there is no later chance
 
1996
                if (allowTracking && (name in varsToRemove) && uses[name] == 0) {
 
1997
                  track(name, node[3], node);
 
1998
                  doEliminate(name, node);
 
1999
                }
 
2000
              } else {
 
2001
                // replace it in-place
 
2002
                node.length = value.length;
 
2003
                for (var i = 0; i < value.length; i++) {
 
2004
                  node[i] = value[i];
 
2005
                }
 
2006
                varsToRemove[name] = 2;
 
2007
              }
 
2008
            } else {
 
2009
              if (allowTracking) track(name, node[3], node);
 
2010
            }
 
2011
          } else if (target[0] == 'sub') {
 
2012
            if (!memoryInvalidated) {
 
2013
              invalidateMemory();
 
2014
              memoryInvalidated = true;
 
2015
            }
 
2016
          }
 
2017
        } else if (type == 'sub') {
 
2018
          traverseInOrder(node[1], false, !memSafe); // evaluate inner
 
2019
          traverseInOrder(node[2]); // evaluate outer
 
2020
          if (!ignoreSub) { // ignoreSub means we are a write (happening later), not a read
 
2021
            // do the memory access
 
2022
            if (!callsInvalidated) {
 
2023
              invalidateCalls();
 
2024
              callsInvalidated = true;
 
2025
            }
 
2026
          }
 
2027
        } else if (type == 'var') {
 
2028
          var vars = node[1];
 
2029
          for (var i = 0; i < vars.length; i++) {
 
2030
            var name = vars[i][0];
 
2031
            var value = vars[i][1];
 
2032
            if (value) {
 
2033
              traverseInOrder(value);
 
2034
              if (name in potentials && allowTracking) {
 
2035
                track(name, value, node);
 
2036
              } else {
 
2037
                invalidateByDep(name);
 
2038
              }
 
2039
              if (vars.length == 1 && name in varsToTryToRemove && value) {
 
2040
                // replace it in-place
 
2041
                value = ['stat', value];
 
2042
                node.length = value.length;
 
2043
                for (var i = 0; i < value.length; i++) {
 
2044
                  node[i] = value[i];
 
2045
                }
 
2046
                varsToRemove[name] = 2;
 
2047
              }
 
2048
            }
 
2049
          }
 
2050
        } else if (type == 'binary') {
 
2051
          var flipped = false;
 
2052
          if (node[1] in ASSOCIATIVE_BINARIES && !(node[2][0] in NAME_OR_NUM) && node[3][0] in NAME_OR_NUM) { // TODO recurse here?
 
2053
            // associatives like + and * can be reordered in the simple case of one of the sides being a name, since we assume they are all just numbers
 
2054
            var temp = node[2];
 
2055
            node[2] = node[3];
 
2056
            node[3] = temp;
 
2057
            flipped = true;
 
2058
          }
 
2059
          traverseInOrder(node[2]);
 
2060
          traverseInOrder(node[3]);
 
2061
          if (flipped && node[2][0] in NAME_OR_NUM) { // dunno if we optimized, but safe to flip back - and keeps the code closer to the original and more readable
 
2062
            var temp = node[2];
 
2063
            node[2] = node[3];
 
2064
            node[3] = temp;
 
2065
          }
 
2066
        } else if (type == 'name') {
 
2067
          if (!ignoreName) { // ignoreName means we are the name of something like a call or a sub - irrelevant for us
 
2068
            var name = node[1];
 
2069
            if (name in tracked) {
 
2070
              doEliminate(name, node);
 
2071
            } else if (!(name in locals) && !callsInvalidated) {
 
2072
              invalidateCalls();
 
2073
              callsInvalidated = true;
 
2074
            }
 
2075
          }
 
2076
        } else if (type == 'unary-prefix' || type == 'unary-postfix') {
 
2077
          traverseInOrder(node[2]);
 
2078
        } else if (type in IGNORABLE_ELIMINATOR_SCAN_NODES) {
 
2079
        } else if (type == 'call') {
 
2080
          traverseInOrder(node[1], false, true);
 
2081
          var args = node[2];
 
2082
          for (var i = 0; i < args.length; i++) {
 
2083
            traverseInOrder(args[i]);
 
2084
          }
 
2085
          // these two invalidations will also invalidate calls
 
2086
          if (!globalsInvalidated) {
 
2087
            invalidateGlobals();
 
2088
            globalsInvalidated = true;
 
2089
          }
 
2090
          if (!memoryInvalidated) {
 
2091
            invalidateMemory();
 
2092
            memoryInvalidated = true;
 
2093
          }
 
2094
        } else if (type == 'if') {
 
2095
          if (allowTracking) {
 
2096
            traverseInOrder(node[1]); // can eliminate into condition, but nowhere else
 
2097
            if (!callsInvalidated) { // invalidate calls, since we cannot eliminate them into an if that may not execute!
 
2098
              invalidateCalls();
 
2099
              callsInvalidated = true;
 
2100
            }
 
2101
            allowTracking = false;
 
2102
            traverseInOrder(node[2]); // 2 and 3 could be 'parallel', really..
 
2103
            if (node[3]) traverseInOrder(node[3]);
 
2104
            allowTracking = true;
 
2105
          } else {
 
2106
            tracked = {};
 
2107
            abort = true;
 
2108
          }
 
2109
        } else if (type == 'block') {
 
2110
          var stats = node[1];
 
2111
          if (stats) {
 
2112
            for (var i = 0; i < stats.length; i++) {
 
2113
              traverseInOrder(stats[i]);
 
2114
            }
 
2115
          }
 
2116
        } else if (type == 'stat') {
 
2117
          traverseInOrder(node[1]);
 
2118
        } else if (type == 'label') {
 
2119
          traverseInOrder(node[2]);
 
2120
        } else if (type == 'seq') {
 
2121
          traverseInOrder(node[1]);
 
2122
          traverseInOrder(node[2]);
 
2123
        } else if (type == 'do') {
 
2124
          if (node[1][0] == 'num' && node[1][1] == 0) { // one-time loop
 
2125
            traverseInOrder(node[2]);
 
2126
          } else {
 
2127
            tracked = {};
 
2128
            abort = true;
 
2129
          }
 
2130
        } else if (type == 'return') {
 
2131
          if (node[1]) traverseInOrder(node[1]);
 
2132
        } else if (type == 'conditional') {
 
2133
          traverseInOrder(node[1]);
 
2134
          traverseInOrder(node[2]);
 
2135
          traverseInOrder(node[3]);
 
2136
        } else {
 
2137
          if (!(type in ABORTING_ELIMINATOR_SCAN_NODES)) {
 
2138
            printErr('unfamiliar eliminator scan node: ' + JSON.stringify(node));
 
2139
          }
 
2140
          tracked = {};
 
2141
          abort = true;
 
2142
        }
 
2143
        //nesting--; // printErr-related
 
2144
      }
 
2145
      traverseInOrder(node);
 
2146
    }
 
2147
    function doEliminate(name, node) {
 
2148
      //printErr('elim!!!!! ' + name);
 
2149
      // yes, eliminate!
 
2150
      varsToRemove[name] = 2; // both assign and var definitions can have other vars we must clean up
 
2151
      var info = tracked[name];
 
2152
      delete tracked[name];
 
2153
      var defNode = info.defNode;
 
2154
      if (!sideEffectFree[name]) {
 
2155
        if (defNode[0] == 'var') {
 
2156
          defNode[1].forEach(function(pair) {
 
2157
            if (pair[0] == name) {
 
2158
              value = pair[1];
 
2159
            }
 
2160
          });
 
2161
          assert(value);
 
2162
        } else { // assign
 
2163
          value = defNode[3];
 
2164
          // wipe out the assign
 
2165
          defNode[0] = 'toplevel';
 
2166
          defNode[1] = [];
 
2167
          defNode.length = 2;
 
2168
        }
 
2169
        // replace this node in-place
 
2170
        node.length = 0;
 
2171
        for (var i = 0; i < value.length; i++) {
 
2172
          node[i] = value[i];
 
2173
        }
 
2174
      } else {
 
2175
        // empty it out in-place
 
2176
        node.length = 0;
 
2177
        node[0] = 'toplevel';
 
2178
        node[1] = [];
 
2179
      }
 
2180
    }
 
2181
    traverse(func, function(block) {
 
2182
      var stats = getStatements(block);
 
2183
      if (!stats) return;
 
2184
      tracked = {};
 
2185
      //printErr('new StatBlock');
 
2186
      for (var i = 0; i < stats.length; i++) {
 
2187
        var node = stats[i];
 
2188
        //printErr('StatBlock[' + i + '] => ' + JSON.stringify(node));
 
2189
        var type = node[0];
 
2190
        if (type == 'stat') {
 
2191
          node = node[1];
 
2192
          type = node[0];
 
2193
        }
 
2194
        // Check for things that affect elimination
 
2195
        if (type in ELIMINATION_SAFE_NODES) {
 
2196
          scan(node);
 
2197
        } else {
 
2198
          tracked = {}; // not a var or assign, break all potential elimination so far
 
2199
        }
 
2200
      }
 
2201
      //printErr('delete StatBlock');
 
2202
    });
 
2203
 
 
2204
    // clean up vars
 
2205
    //printErr('cleaning up ' + JSON.stringify(varsToRemove));
 
2206
    traverse(func, function(node, type) {
 
2207
      if (type === 'var') {
 
2208
        node[1] = node[1].filter(function(pair) { return !varsToRemove[pair[0]] });
 
2209
        if (node[1].length == 0) {
 
2210
          // wipe out an empty |var;|
 
2211
          node[0] = 'toplevel';
 
2212
          node[1] = [];
 
2213
        }
 
2214
      }
 
2215
    });
 
2216
 
 
2217
    if (asm) {
 
2218
      for (var v in varsToRemove) {
 
2219
        if (varsToRemove[v] == 2) delete asmData.vars[v];
 
2220
      }
 
2221
      denormalizeAsm(func, asmData);
 
2222
    }
 
2223
  });
 
2224
 
 
2225
  // A class for optimizing expressions. We know that it is legitimate to collapse
 
2226
  // 5+7 in the generated code, as it will always be numerical, for example. XXX do we need this? here?
 
2227
  function ExpressionOptimizer(node) {
 
2228
    this.node = node;
 
2229
 
 
2230
    this.run = function() {
 
2231
      traverse(this.node, function(node, type) {
 
2232
        if (type === 'binary' && node[1] == '+') {
 
2233
          var names = [];
 
2234
          var num = 0;
 
2235
          var has_num = false;
 
2236
          var fail = false;
 
2237
          traverse(node, function(subNode, subType) {
 
2238
            if (subType === 'binary') {
 
2239
              if (subNode[1] != '+') {
 
2240
                fail = true;
 
2241
                return false;
 
2242
              }
 
2243
            } else if (subType === 'name') {
 
2244
              names.push(subNode[1]);
 
2245
              return;
 
2246
            } else if (subType === 'num') {
 
2247
              num += subNode[1];
 
2248
              has_num = true;
 
2249
              return;
 
2250
            } else {
 
2251
              fail = true;
 
2252
              return false;
 
2253
            }
 
2254
          });
 
2255
          if (!fail && has_num) {
 
2256
            var ret = ['num', num];
 
2257
            for (var i = 0; i < names.length; i++) {
 
2258
              ret = ['binary', '+', ['name', names[i]], ret];
 
2259
            }
 
2260
            return ret;
 
2261
          }
 
2262
        }
 
2263
      });
 
2264
    };
 
2265
  }
 
2266
  new ExpressionOptimizer(ast).run();
 
2267
}
 
2268
 
 
2269
function eliminateMemSafe(ast) {
 
2270
  eliminate(ast, true);
 
2271
}
 
2272
 
 
2273
function minifyGlobals(ast) {
 
2274
  var minified = {};
 
2275
  var next = 0;
 
2276
  var first = true; // do not minify initial 'var asm ='
 
2277
  // find the globals
 
2278
  traverse(ast, function(node, type) {
 
2279
    if (type == 'var') {
 
2280
      if (first) {
 
2281
        first = false;
 
2282
        return;
 
2283
      }
 
2284
      var vars = node[1];
 
2285
      for (var i = 0; i < vars.length; i++) {
 
2286
        var name = vars[i][0];
 
2287
        assert(next < minifierInfo.names.length);
 
2288
        vars[i][0] = minified[name] = minifierInfo.names[next++];
 
2289
      }
 
2290
    }
 
2291
  });
 
2292
  // add all globals in function chunks, i.e. not here but passed to us
 
2293
  for (var i = 0; i < minifierInfo.globals.length; i++) {
 
2294
    name = minifierInfo.globals[i];
 
2295
    assert(next < minifierInfo.names.length);
 
2296
    minified[name] = minifierInfo.names[next++];
 
2297
  }
 
2298
  // apply minification
 
2299
  traverse(ast, function(node, type) {
 
2300
    if (type == 'name') {
 
2301
      var name = node[1];
 
2302
      if (name in minified) {
 
2303
        node[1] = minified[name];
 
2304
      }
 
2305
    }
 
2306
  });
 
2307
  suffix = '// MINIFY_INFO:' + JSON.stringify(minified);
 
2308
}
 
2309
 
 
2310
// Change +5 to DOT$ZERO(5). We then textually change 5 to 5.0 (uglify's ast cannot differentiate between 5 and 5.0 directly)
 
2311
function prepDotZero(ast) {
 
2312
  traverse(ast, function(node, type) {
 
2313
    if (type == 'unary-prefix' && node[1] == '+') {
 
2314
      if (node[2][0] == 'num' ||
 
2315
          (node[2][0] == 'unary-prefix' && node[2][1] == '-' && node[2][2][0] == 'num')) {
 
2316
        return ['call', ['name', 'DOT$ZERO'], [node[2]]];
 
2317
      }
 
2318
    }
 
2319
  });
 
2320
}
 
2321
function fixDotZero(js) {
 
2322
  return js.replace(/DOT\$ZERO\(([-+]?(0x)?[0-9a-f]*\.?[0-9]+([eE][-+]?[0-9]+)?)\)/g, function(m, num) {
 
2323
    if (num.substr(0, 2) == '0x' || num.substr(0, 3) == '-0x') {
 
2324
      return eval(num) + '.0';
 
2325
    }
 
2326
    if (num.indexOf('.') >= 0) return num;
 
2327
    var e = num.indexOf('e');
 
2328
    if (e < 0) return num + '.0';
 
2329
    return num.substr(0, e) + '.0' + num.substr(e);
 
2330
  });
 
2331
}
 
2332
 
 
2333
// Passes table
 
2334
 
 
2335
var compress = false, printMetadata = true, asm = false, last = false;
 
2336
 
 
2337
var passes = {
 
2338
  dumpAst: dumpAst,
 
2339
  dumpSrc: dumpSrc,
 
2340
  unGlobalize: unGlobalize,
 
2341
  removeAssignsToUndefined: removeAssignsToUndefined,
 
2342
  //removeUnneededLabelSettings: removeUnneededLabelSettings,
 
2343
  simplifyExpressionsPre: simplifyExpressionsPre,
 
2344
  optimizeShiftsConservative: optimizeShiftsConservative,
 
2345
  optimizeShiftsAggressive: optimizeShiftsAggressive,
 
2346
  simplifyExpressionsPost: simplifyExpressionsPost,
 
2347
  hoistMultiples: hoistMultiples,
 
2348
  loopOptimizer: loopOptimizer,
 
2349
  registerize: registerize,
 
2350
  eliminate: eliminate,
 
2351
  eliminateMemSafe: eliminateMemSafe,
 
2352
  minifyGlobals: minifyGlobals,
 
2353
  compress: function() { compress = true },
 
2354
  noPrintMetadata: function() { printMetadata = false },
 
2355
  asm: function() { asm = true },
 
2356
  last: function() { last = true }
 
2357
};
 
2358
 
 
2359
// Main
 
2360
 
 
2361
var suffix = '';
 
2362
 
 
2363
var src = read(arguments_[0]);
 
2364
var ast = srcToAst(src);
 
2365
//printErr(JSON.stringify(ast)); throw 1;
 
2366
generatedFunctions = src.indexOf(GENERATED_FUNCTIONS_MARKER) >= 0;
 
2367
var minifierInfoStart = src.indexOf('// MINIFY_INFO:')
 
2368
if (minifierInfoStart > 0) minifierInfo = JSON.parse(src.substr(minifierInfoStart + 15));
 
2369
//printErr(JSON.stringify(minifierInfo));
 
2370
 
 
2371
arguments_.slice(1).forEach(function(arg) {
 
2372
  passes[arg](ast);
 
2373
});
 
2374
if (asm && last) {
 
2375
  prepDotZero(ast);
 
2376
}
 
2377
var js = astToSrc(ast, compress), old;
 
2378
if (asm && last) {
 
2379
  js = fixDotZero(js);
 
2380
}
 
2381
 
 
2382
// remove unneeded newlines+spaces, and print
 
2383
do {
 
2384
  old = js;
 
2385
  js = js.replace(/\n *\n/g, '\n');
 
2386
} while (js != old);
 
2387
print(js);
 
2388
print('\n');
 
2389
print(suffix);
 
2390