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.
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
//==============================================================================
12
// *** Environment setup code ***
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;
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
24
process['stdout'].write(x + '\n');
26
printErr = function(x) {
27
process['stderr'].write(x + '\n');
30
var nodeFS = require('fs');
31
var nodePath = require('path');
33
if (!nodeFS.existsSync) {
34
nodeFS.existsSync = function(path) {
36
return !!nodeFS.readFileSync(path);
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)) {
54
read = function(filename) {
55
var absolute = find(filename);
56
return nodeFS['readFileSync'](absolute).toString();
63
arguments_ = process['argv'].slice(2);
65
} else if (ENVIRONMENT_IS_SHELL) {
66
// Polyfill over SpiderMonkey/V8 differences
68
this['read'] = function(f) { snarf(f) };
71
if (typeof scriptArgs != 'undefined') {
72
arguments_ = scriptArgs;
73
} else if (typeof arguments != 'undefined') {
74
arguments_ = arguments;
77
} else if (ENVIRONMENT_IS_WEB) {
78
this['print'] = printErr = function(x) {
82
this['read'] = function(url) {
83
var xhr = new XMLHttpRequest();
84
xhr.open('GET', url, false);
86
return xhr.responseText;
89
if (this['arguments']) {
90
arguments_ = arguments;
92
} else if (ENVIRONMENT_IS_WORKER) {
93
// We can do very little here...
95
this['load'] = importScripts;
98
throw 'Unknown runtime environment. Where are we?';
101
function globalEval(x) {
105
if (typeof load == 'undefined' && typeof read != 'undefined') {
106
this['load'] = function(f) {
111
if (typeof printErr === 'undefined') {
112
this['printErr'] = function(){};
115
if (typeof print === 'undefined') {
116
this['print'] = printErr;
118
// *** Environment setup code ***
120
var uglify = require('../tools/eliminator/node_modules/uglify-js');
121
var fs = require('fs');
122
var path = require('path');
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('+', '*', '|', '&', '^');
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]];
143
var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS';
144
var generatedFunctions = false; // whether we have received only generated functions
146
var minifierInfo = null;
148
function srcToAst(src) {
149
return uglify.parser.parse(src);
152
function astToSrc(ast, compress) {
153
return uglify.uglify.gen_code(ast, {
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;
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';
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);
197
if (result !== null) {
198
if (traverseChildren(node, traverse, pre, post, stack) == true) return true;
202
var postResult = post(node, type, stack);
203
result = result || postResult;
205
if (stack) stack.pop();
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);
221
function traverseGeneratedFunctions(ast, callback) {
222
assert(generatedFunctions);
223
if (ast[0] == 'toplevel') {
225
for (var i = 0; i < stats.length; i++) {
227
if (curr[0] == 'defun') callback(curr);
229
} else if (ast[0] == 'defun') {
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];
243
func.vars = func.vars.concat(node[1].map(function(varItem) { return varItem[0] }));
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);
260
function emptyNode() { // XXX do we need to create new nodes here? can't we reuse?
261
return ['toplevel', []]
264
function isEmptyNode(node) {
265
return node.length == 2 && node[0] == 'toplevel' && node[1].length == 0;
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, ' '));
276
function dumpSrc(ast) {
277
printErr(astToSrc(ast));
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) {
285
throw 'this is deprecated!'; // and does not work with parallel compilation
287
assert(ast[0] == 'toplevel');
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)) {
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;
310
ast[1][i][1][j] = [ident, value];
312
values[ident] = value;
318
if (node[1].length == 0) {
319
ast[1][i] = emptyNode();
322
traverseWithVariables(ast, function(node, type, allVars) {
323
if (type == 'name') {
325
if (ident in values && allVars.indexOf(ident) < 0) {
326
return copy(values[ident]);
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.
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).
342
// This pass assumes that unGlobalize has been run, so undefined
344
function removeAssignsToUndefined(ast) {
345
traverse(ast, function(node, type) {
346
if (type == 'assign' && jsonCompare(node[3], ['unary-prefix', 'void', ['num', 0]])) {
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];
357
// cleanup (|x = y = void 0| leaves |x = ;| right now)
361
traverse(ast, function(node, type) {
362
if (type == 'assign' && jsonCompare(node[3], 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];
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
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;
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();
403
// Various expression simplifications. Pre run before closure (where we still have metadata), Post run after.
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.
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];
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--) {
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++) {
435
return process(result, result[0], stack);
436
} else if (stack[i] == -1) {
437
break; // Too bad, we can't
439
break; // we must keep a coercion right on top of a heap access in asm mode
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) {
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
450
stack.push(-1); // This node is dangerous! Give up if you see this before you see '1'
455
// & and heap-related optimizations
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));
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]];
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];
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) {
480
input[1][1] = 'HEAPU' + heapBits; // make unsigned
483
// we cannot return HEAPU8 without a coercion, but at least we do HEAP8 & 255 => HEAPU8 | 0
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;
503
node[2] = node[2][2];
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') {
516
node[1] = node[2][1] >> node[3][1];
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) {
529
traverseGenerated(ast, function(node, type) {
530
if (type == 'binary' && node[1] == '+') {
531
if (node[2][0] == 'num' && node[3][0] == 'num') {
533
return ['num', node[2][1] + node[3][1]];
535
for (var i = 2; i <= 3; 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') {
540
node[ii][j][1] += node[i][1];
550
// if (x == 0) can be if (!x), etc.
551
function simplifyZeroComp(ast) {
552
traverseGenerated(ast, function(node, type) {
554
if (type == 'if' && (binary = node[1])[0] == 'binary') {
555
if ((binary[1] == '!=' || binary[1] == '!==') && binary[3][0] == 'num' && binary[3][1] == 0) {
558
} else if ((binary[1] == '==' || binary[1] == '===') && binary[3][0] == 'num' && binary[3][1] == 0) {
559
node[1] = ['unary-prefix', '!', binary[2]];
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') {
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')) {
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]);
597
// simplifyZeroComp(ast); TODO: investigate performance
598
if (asm) asmOpts(ast);
601
// In typed arrays mode 2, we can have
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) {
608
traverseGeneratedFunctions(ast, function(fun) {
610
var funFinished = {};
613
// Recognize variables and parameters
615
function newVar(name, param, addUse) {
619
defs: addUse ? 1 : 0,
621
timesShifted: [0, 0, 0, 0], // zero shifts of size 0, 1, 2, 3
629
fun[2].forEach(function(arg) {
630
newVar(arg, true, true);
634
// XXX if var has >>=, ignore it here? That means a previous pass already optimized it
635
var hasSwitch = traverse(fun, function(node, type) {
637
node[1].forEach(function(arg) {
638
newVar(arg[0], false, arg[1]);
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.
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) {
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++;
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]);
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];
674
vars[name].timesShifted[shifts]++;
678
return ['binary', '>>', subNode, ['num', shifts]];
680
return addShift(node[2]);
684
traverse(fun, function(node, type) {
685
if (node[0] == 'name' && node[2]) {
686
return node.slice(0, 2); // clean up our notes
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) {
697
if (totalTimesShifted != Math.max.apply(null, data.timesShifted)) {
698
// TODO: Handle multiple different shifts
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));
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;
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);
727
function needsShift(name) {
728
return vars[name] && vars[name].primaryShift >= 0;
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)) {
734
fun[3].unshift(['var', [[name + '$s' + data.primaryShift, ['binary', '>>', ['name', name], ['num', data.primaryShift]]]]]);
736
fun[3].unshift(['var', [[name + '$s' + data.primaryShift]]]);
740
traverse(fun, function(node, type, stack) { // add shift to assignments
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') {
752
for (var i = 0; i < args.length; i++) {
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]]]);
764
traverse(fun, function(node, type, stack) { // replace shifted name with new variable
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];
779
var SIMPLE_SHIFTS = set('<<', '>>');
781
while (more) { // combine shifts in the same direction as an optimization
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
787
return ['binary', node[1], node[2][2], ['num', node[3][1] + node[2][3][1]]];
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) {
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];
812
if (mulNode[3][0] == 'num') {
813
if (node[1] == '<<') {
814
mulNode[3][1] *= Math.pow(2, node[3][1]);
819
if (mulNode[3][1] % Math.pow(2, node[3][1]) == 0) {
820
mulNode[3][1] /= Math.pow(2, node[3][1]);
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]];
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) {
844
if (type == 'binary' && node[1] == '+' && (stack[stack.length-2][0] != 'binary' || stack[stack.length-2][1] != '+')) {
845
// 'Flatten' added items
847
function flatten(node) {
848
if (node[0] == 'binary' && node[1] == '+') {
852
addedItems.push(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);
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);
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
868
for (var i = 0; i < addedItems.length; i++) {
869
if (addedItems[i][0] == 'string') return; // this node is not relevant for us
871
addedItems.sort(function(node1, node2) {
872
return key(node1) - key(node2);
874
// Regenerate items, now sorted
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);
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);
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).
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]];
905
addedItems.unshift(['num', num]);
908
var ret = addedItems.pop();
909
while (addedItems.length > 0) { // re-create AST from addedItems
910
ret = ['binary', '+', ret, addedItems.pop()];
915
// Note finished variables
916
for (var name in vars) {
917
funFinished[name] = true;
923
function optimizeShiftsConservative(ast) {
924
optimizeShiftsInternal(ast, true);
927
function optimizeShiftsAggressive(ast) {
928
optimizeShiftsInternal(ast, false);
931
// We often have branchings that are simplified so one end vanishes, and
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]];
955
function simplifyExpressionsPost(ast) {
956
simplifyNotComps(ast);
959
var NO_SIDE_EFFECTS = set('num', 'name');
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]);
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;
977
function simplifyList(node, si) {
979
// Merge block items into this list, thus removing unneeded |{ .. }|'s
980
var statements = node[si];
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] || []));
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;
999
function vacuumInternal(node) {
1000
traverseChildren(node, vacuumInternal);
1004
if (node[1] && node[1].length == 1 && node[1][0][0] == 'block') {
1006
} else if (typeof node[1] == 'object') {
1007
ret = simplifyList(node, 1);
1008
if (ret) return ret;
1012
if (node[1][0] == 'block') {
1017
if (node[3].length == 1 && node[3][0][0] == 'block') {
1018
node[3] = node[3][0][1];
1021
ret = simplifyList(node, 3);
1022
if (ret) return ret;
1026
if (node[1][0] == 'num' && node[2][0] == 'toplevel' && (!node[2][1] || node[2][1].length == 0)) {
1028
} else if (isEmpty(node[2]) && !hasSideEffects(node[1])) {
1033
if (node[2][0] == 'toplevel' && (!node[2][1] || node[2][1].length == 0)) {
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]];
1053
traverseGeneratedFunctions(ast, function(node) {
1054
vacuumInternal(node);
1055
simplifyNotComps(node);
1059
function getStatements(node) {
1060
if (node[0] == 'defun') {
1062
} else if (node[0] == 'block') {
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;
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];
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');
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
1117
postInner[2] = ['block', []];
1123
postInner = postInner[3]; // Proceed to look in the else clause
1127
statements[i] = ['do', shellDo, ['block', [statements[i]]]];
1130
statements[i] = ['label', shellLabel, statements[i]];
1134
if (modified) return node;
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') {
1145
if (replaced = tryEliminate(node[2])) node[2] = replaced;
1146
if (node[3] && (replaced = tryEliminate(node[3]))) node[3] = replaced;
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) {
1155
node[1].splice(node[1].length-1, 1);
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];
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') {
1185
// Afterpass: Reduce
1186
// if (..) { .. break|continue } else { .. }
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)) {
1201
node[1] = ['unary-prefix', '!', node[1]];
1202
simplifyNotComps(node[1]); // bake the ! into the expression
1206
if (stat1[stat1.length-1][0] in LOOP_FLOW) {
1207
statements.splice.apply(statements, [i+1, 0].concat(stat2));
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) {
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.
1226
node[1] = '+' + node[1];
1227
} else if (type in LOOP) {
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];
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];
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
1251
// No label on the break/continue, so keep the last loop alive (no need for its label though)
1252
neededDos.push(lastLoop);
1254
// Find the label node that needs to stay alive
1255
stack.forEach(function(label) {
1257
if (label[1] == plus) label[1] = label[1].substr(1); // Remove '+', marking it as needed
1263
// We return whether another pass is necessary
1265
// Remove unneeded labels
1266
traverseGenerated(ast, function(node, type) {
1267
if (type == 'label' && node[1][0] == '+') {
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) {
1276
return node[2]; // Remove the label itself on the loop
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]);
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.;');
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.
1302
// Multiple pass two runs may be needed, as we remove one-time loops and so forth
1304
var more = passTwo(ast);
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
1314
if (vars.length == 1) {
1315
ret[1] = ['assign', true, ['name', vars[0][0]], vars[0][1]];
1319
for (var i = 0; i < vars.length-1; i++) {
1321
curr[1] = ['assign', true, ['name', vars[i][0]], vars[i][1]];
1322
if (i != vars.length-2) curr = curr[2] = [];
1324
curr[2] = ['assign', true, ['name', vars[vars.length-1][0]], vars[vars.length-1][1]];
1329
// asm.js support code - normalize (convert asm.js code to 'normal' JS, without
1330
// annotations, plus explicit metadata) and denormalize (vice versa)
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;
1340
function makeAsmParamCoercion(param, type) {
1341
return type == ASM_INT ? ['binary', '|', ['name', param], ['num', 0]] : ['unary-prefix', '+', ['name', param]];
1344
function makeAsmVarDef(v, type) {
1345
return [v, type == ASM_INT ? ['num', 0] : ['unary-prefix', '+', ['num', 0]]];
1348
function normalizeAsm(func) {
1349
//printErr('pre-normalize \n\n' + astToSrc(func) + '\n\n');
1351
params: {}, // ident => ASM_* type
1352
vars: {}, // ident => ASM_* type
1354
// process initial params
1355
var stats = func[3];
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;
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();
1367
// process initial variable definitions
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++) {
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
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++) {
1394
if (!(name in data.vars)) {
1395
data.vars[name] = detectAsmCoercion(value);
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
1403
node[1] = 'Math_' + node[2];
1409
//printErr('normalized \n\n' + astToSrc(func) + '\n\nwith: ' + JSON.stringify(data));
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();
1421
if (!isEmptyNode(stats[i])) break;
1424
// each param needs a line; reuse emptyNodes as much as we can
1426
for (var i in data.params) numParams++;
1428
while (emptyNodes < stats.length) {
1429
if (!isEmptyNode(stats[emptyNodes])) break;
1432
var neededEmptyNodes = numParams + 1; // params plus one big var
1433
if (neededEmptyNodes > emptyNodes) {
1435
for (var i = 0; i < neededEmptyNodes - emptyNodes; i++) args[i+2] = 0;
1436
stats.splice.apply(stats, args);
1438
// add param coercions
1440
func[2].forEach(function(param) {
1441
stats[next++] = ['stat', ['assign', true, ['name', param], makeAsmParamCoercion(param, data.params[param])]];
1443
// add variable definitions
1445
for (var v in data.vars) {
1446
varDefs.push(makeAsmVarDef(v, data.vars[v]));
1448
if (varDefs.length) {
1449
stats[next] = ['var', varDefs];
1451
stats[next] = emptyNode();
1453
//printErr('denormalized \n\n' + astToSrc(func) + '\n\n');
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.)
1460
// We do not optimize when there are switches, so this pass only makes sense with
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
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) {
1474
return [param, assign];
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');
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
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);
1495
} else if (type == 'switch') {
1502
var usedGlobals = {};
1504
// Minify globals using the mapping we were given
1505
traverse(fun, function(node, type) {
1506
if (type == 'name') {
1508
var minified = minifierInfo.globals[name];
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];
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) {
1531
for (var i = 0; i < fun[2].length; i++) {
1532
if (fun[2][i] == minified) fun[2][i] = newName;
1537
usedGlobals[minified] = 1;
1541
assert(fun[1] in minifierInfo.globals, fun[1]);
1542
fun[1] = minifierInfo.globals[fun[1]];
1544
var nextRegName = 0;
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;
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;
1563
assert('ran out of names');
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"
1572
var levelDominations = {};
1575
var unoptimizables = {};
1576
traverse(fun, function(node, type) {
1577
if (type == 'name') {
1579
if (localVars[name]) {
1580
if (!varUses[name]) varUses[name] = 0;
1582
if (possibles[name] && !varLevels[name]) unoptimizables[name] = 1; // used outside of simple domination
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
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;
1596
} else if (type in CONTROL_FLOW) {
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;
1605
levelDominations[level] = null;
1609
var optimizables = {};
1611
for (var possible in possibles) {
1612
if (!unoptimizables[possible]) optimizables[possible] = 1;
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
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
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;
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();
1649
fullNames[reg] = getNewRegName(reg, name);
1650
if (params[name]) paramRegs[reg] = 1;
1652
varRegs[name] = reg;
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])) {
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);
1672
traverse(fun, function(node, type) { // XXX we rely on traversal order being the same as execution order here
1673
if (type == 'name') {
1676
node[1] = fullNames[varRegs[name]];
1678
} else if (type in LOOP) {
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;
1687
}, function(node, type) {
1689
// Free registers that were locked to this loop
1690
if (loopRegs[loops]) {
1692
loopRegs[loops].forEach(function(loopReg) {
1693
freeRegsClasses[regTypes[fullNames[loopReg]]].push(loopReg);
1696
freeRegsClasses = freeRegsClasses.concat(loopRegs[loops]);
1698
loopRegs[loops].length = 0;
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
1707
//printErr('var regs: ' + JSON.stringify(varRegs) + '\n\nparam regs: ' + JSON.stringify(paramRegs));
1711
for (var i = 1; i < nextReg; i++) {
1712
var reg = fullNames[i];
1713
if (!paramRegs[i]) {
1719
if (vars.length > 0) getStatements(fun).unshift(['var', vars]);
1722
//printErr('unfake params: \n\n' + astToSrc(fun) + '\n\n');
1723
var finalAsmData = {
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;
1733
finalAsmData.params[reg] = type;
1737
denormalizeAsm(fun, finalAsmData);
1742
// Eliminator aka Expressionizer
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
1748
// var y = HEAP[20];
1751
// can be transformed into
1753
// print(a(10)+HEAP[20]);
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.
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
1767
// FUNCTION_TABLE[x]();
1769
// to be optimized (f could replace FUNCTION_TABLE, so in general JS eliminating x is not valid).
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
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)
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]);
1785
// First, find the potentially eliminatable functions: that have one definition and one use
1786
var definitions = {};
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
1795
for (var i = 0; i < func[2].length; i++) {
1796
locals[func[2][i]] = true;
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];
1808
if (!(name in definitions)) definitions[name] = 0;
1809
definitions[name]++;
1810
if (!values[name]) values[name] = value;
1812
if (!uses[name]) uses[name] = 0;
1813
locals[name] = true;
1815
} else if (type === 'name') {
1817
if (!uses[name]) uses[name] = 0;
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
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;
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
1848
if (!hasSideEffects) {
1849
varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally
1850
sideEffectFree[name] = true;
1852
varsToTryToRemove[name] = 1; // try to remove it later during scanning
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
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') {
1876
if (!(name in locals)) {
1879
if (!(name in potentials)) { // deps do not matter for potentials - they are defined once, so no complexity
1885
} else if (type == 'sub') {
1888
} else if (type == 'call') {
1898
usesGlobals: usesGlobals,
1899
usesMemory: usesMemory,
1904
globalsInvalidated = false;
1905
memoryInvalidated = false;
1906
callsInvalidated = false;
1907
//printErr('track ' + [name, JSON.stringify(tracked[name])]);
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');
1914
for (var name in tracked) {
1915
var info = tracked[name];
1916
if (info.usesGlobals) {
1920
for (var i = 0; i < temp.length; i++) {
1921
delete tracked[temp[i]];
1924
function invalidateMemory() {
1925
//printErr('invalidate memory');
1927
for (var name in tracked) {
1928
var info = tracked[name];
1929
if (info.usesMemory) {
1933
for (var i = 0; i < temp.length; i++) {
1934
delete tracked[temp[i]];
1937
function invalidateByDep(dep) {
1938
//printErr('invalidate by dep ' + dep);
1940
for (var name in tracked) {
1941
var info = tracked[name];
1942
if (info.deps[dep]) {
1946
for (var i = 0; i < temp.length; i++) {
1947
delete tracked[temp[i]];
1950
function invalidateCalls() {
1951
//printErr('invalidate calls');
1953
for (var name in tracked) {
1954
var info = tracked[name];
1955
if (info.doesCall) {
1959
for (var i = 0; i < temp.length; i++) {
1960
delete tracked[temp[i]];
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));
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) {
1973
//nesting++; // printErr-related
1974
//printErr(spaces(2*(nesting+1)) + 'trav: ' + JSON.stringify(node).substr(0, 50) + ' : ' + keys(tracked) + ' : ' + [allowTracking, ignoreSub, ignoreName]);
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
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;
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);
2001
// replace it in-place
2002
node.length = value.length;
2003
for (var i = 0; i < value.length; i++) {
2006
varsToRemove[name] = 2;
2009
if (allowTracking) track(name, node[3], node);
2011
} else if (target[0] == 'sub') {
2012
if (!memoryInvalidated) {
2014
memoryInvalidated = true;
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) {
2024
callsInvalidated = true;
2027
} else if (type == 'var') {
2029
for (var i = 0; i < vars.length; i++) {
2030
var name = vars[i][0];
2031
var value = vars[i][1];
2033
traverseInOrder(value);
2034
if (name in potentials && allowTracking) {
2035
track(name, value, node);
2037
invalidateByDep(name);
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++) {
2046
varsToRemove[name] = 2;
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
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
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
2069
if (name in tracked) {
2070
doEliminate(name, node);
2071
} else if (!(name in locals) && !callsInvalidated) {
2073
callsInvalidated = true;
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);
2082
for (var i = 0; i < args.length; i++) {
2083
traverseInOrder(args[i]);
2085
// these two invalidations will also invalidate calls
2086
if (!globalsInvalidated) {
2087
invalidateGlobals();
2088
globalsInvalidated = true;
2090
if (!memoryInvalidated) {
2092
memoryInvalidated = true;
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!
2099
callsInvalidated = true;
2101
allowTracking = false;
2102
traverseInOrder(node[2]); // 2 and 3 could be 'parallel', really..
2103
if (node[3]) traverseInOrder(node[3]);
2104
allowTracking = true;
2109
} else if (type == 'block') {
2110
var stats = node[1];
2112
for (var i = 0; i < stats.length; i++) {
2113
traverseInOrder(stats[i]);
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]);
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]);
2137
if (!(type in ABORTING_ELIMINATOR_SCAN_NODES)) {
2138
printErr('unfamiliar eliminator scan node: ' + JSON.stringify(node));
2143
//nesting--; // printErr-related
2145
traverseInOrder(node);
2147
function doEliminate(name, node) {
2148
//printErr('elim!!!!! ' + name);
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) {
2164
// wipe out the assign
2165
defNode[0] = 'toplevel';
2169
// replace this node in-place
2171
for (var i = 0; i < value.length; i++) {
2175
// empty it out in-place
2177
node[0] = 'toplevel';
2181
traverse(func, function(block) {
2182
var stats = getStatements(block);
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));
2190
if (type == 'stat') {
2194
// Check for things that affect elimination
2195
if (type in ELIMINATION_SAFE_NODES) {
2198
tracked = {}; // not a var or assign, break all potential elimination so far
2201
//printErr('delete StatBlock');
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';
2218
for (var v in varsToRemove) {
2219
if (varsToRemove[v] == 2) delete asmData.vars[v];
2221
denormalizeAsm(func, asmData);
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) {
2230
this.run = function() {
2231
traverse(this.node, function(node, type) {
2232
if (type === 'binary' && node[1] == '+') {
2235
var has_num = false;
2237
traverse(node, function(subNode, subType) {
2238
if (subType === 'binary') {
2239
if (subNode[1] != '+') {
2243
} else if (subType === 'name') {
2244
names.push(subNode[1]);
2246
} else if (subType === 'num') {
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];
2266
new ExpressionOptimizer(ast).run();
2269
function eliminateMemSafe(ast) {
2270
eliminate(ast, true);
2273
function minifyGlobals(ast) {
2276
var first = true; // do not minify initial 'var asm ='
2278
traverse(ast, function(node, type) {
2279
if (type == 'var') {
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++];
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++];
2298
// apply minification
2299
traverse(ast, function(node, type) {
2300
if (type == 'name') {
2302
if (name in minified) {
2303
node[1] = minified[name];
2307
suffix = '// MINIFY_INFO:' + JSON.stringify(minified);
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]]];
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';
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);
2335
var compress = false, printMetadata = true, asm = false, last = false;
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 }
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));
2371
arguments_.slice(1).forEach(function(arg) {
2377
var js = astToSrc(ast, compress), old;
2379
js = fixDotZero(js);
2382
// remove unneeded newlines+spaces, and print
2385
js = js.replace(/\n *\n/g, '\n');
2386
} while (js != old);