aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/library.js138
-rw-r--r--src/preamble.js2
-rw-r--r--tests/parseInt/output.txt134
-rw-r--r--tests/parseInt/src.c64
-rw-r--r--tests/runner.py22
-rw-r--r--tools/eliminator/eliminator-test-output.js56
-rw-r--r--tools/eliminator/eliminator-test.js62
-rw-r--r--tools/eliminator/eliminator.coffee149
-rw-r--r--tools/eliminator/node_modules/uglify-js/lib/process.js4
9 files changed, 533 insertions, 98 deletions
diff --git a/src/library.js b/src/library.js
index 5e0a53a1..62e4de55 100644
--- a/src/library.js
+++ b/src/library.js
@@ -2146,6 +2146,7 @@ LibraryManager.library = {
(type === 's')) {
buffer.push(String.fromCharCode(next));
next = get();
+ curr++;
} else {
break;
}
@@ -3073,11 +3074,14 @@ LibraryManager.library = {
vsnprintf: 'snprintf',
vprintf: 'printf',
vsprintf: 'sprintf',
- // TODO: Implement v*scanf().
+ vscanf: 'scanf',
+ vfscanf: 'fscanf',
+ vsscanf: 'sscanf',
__01fopen64_: 'fopen',
__01fseeko64_: 'fseek',
__01ftello64_: 'ftell',
__01tmpfile64_: 'tmpfile',
+ __isoc99_fscanf: 'fscanf',
// TODO: Check if any other aliases are needed.
_IO_getc: 'getc',
_IO_putc: 'putc',
@@ -3099,6 +3103,7 @@ LibraryManager.library = {
return allocate(info.object.contents.slice(offset, offset+num),
'i8', ALLOC_NORMAL);
},
+ __01mmap64_: 'mmap',
munmap: function(start, num) {
// FIXME: Not really correct at all.
@@ -3168,7 +3173,16 @@ LibraryManager.library = {
strtod__deps: ['isspace', 'isdigit'],
strtod: function(str, endptr) {
// Skip space.
- while (_isspace(str)) str++;
+ while (_isspace({{{ makeGetValue('str', 0, 'i8') }}})) str++;
+
+ // Check for a plus/minus sign.
+ var multiplier = 1;
+ if ({{{ makeGetValue('str', 0, 'i8') }}} == '-'.charCodeAt(0)) {
+ multiplier = -1;
+ str++;
+ } else if ({{{ makeGetValue('str', 0, 'i8') }}} == '+'.charCodeAt(0)) {
+ str++;
+ }
var chr;
var ret = 0;
@@ -3223,8 +3237,94 @@ LibraryManager.library = {
{{{ makeSetValue('endptr', 0, 'str', '*') }}}
}
+ return ret * multiplier;
+ },
+
+ _parseInt__deps: ['isspace', '__setErrNo', '$ERRNO_CODES'],
+ _parseInt: function(str, endptr, base, min, max, unsignBits) {
+ // Skip space.
+ while (_isspace({{{ makeGetValue('str', 0, 'i8') }}})) str++;
+
+ // Check for a plus/minus sign.
+ var multiplier = 1;
+ if ({{{ makeGetValue('str', 0, 'i8') }}} == '-'.charCodeAt(0)) {
+ multiplier = -1;
+ str++;
+ } else if ({{{ makeGetValue('str', 0, 'i8') }}} == '+'.charCodeAt(0)) {
+ str++;
+ }
+
+ // Find base.
+ var finalBase = base;
+ if (!finalBase) {
+ if ({{{ makeGetValue('str', 0, 'i8') }}} == '0'.charCodeAt(0)) {
+ if ({{{ makeGetValue('str+1', 0, 'i8') }}} == 'x'.charCodeAt(0) ||
+ {{{ makeGetValue('str+1', 0, 'i8') }}} == 'X'.charCodeAt(0)) {
+ finalBase = 16;
+ str += 2;
+ } else {
+ finalBase = 8;
+ str++;
+ }
+ }
+ }
+ if (!finalBase) finalBase = 10;
+
+ // Get digits.
+ var chr;
+ var ret = 0;
+ while ((chr = {{{ makeGetValue('str', 0, 'i8') }}}) != 0) {
+ var digit = parseInt(String.fromCharCode(chr), finalBase);
+ if (isNaN(digit)) {
+ break;
+ } else {
+ ret = ret * finalBase + digit;
+ str++;
+ }
+ }
+
+ // Apply sign.
+ ret *= multiplier;
+
+ // Set end pointer.
+ if (endptr) {
+ {{{ makeSetValue('endptr', 0, 'str', '*') }}}
+ }
+
+ // Unsign if needed.
+ if (unsignBits) {
+ if (Math.abs(ret) > max) {
+ ret = max;
+ ___setErrNo(ERRNO_CODES.ERANGE);
+ } else {
+ ret = unSign(ret, unsignBits);
+ }
+ }
+
+ // Validate range.
+ if (ret > max || ret < min) {
+ ret = ret > max ? max : min;
+ ___setErrNo(ERRNO_CODES.ERANGE);
+ }
+
return ret;
},
+ strtoll__deps: ['_parseInt'],
+ strtoll: function(str, endptr, base) {
+ return __parseInt(str, endptr, base, -9223372036854775808, 9223372036854775807); // LLONG_MIN, LLONG_MAX; imprecise.
+ },
+ strtol__deps: ['_parseInt'],
+ strtol: function(str, endptr, base) {
+ return __parseInt(str, endptr, base, -2147483648, 2147483647); // LONG_MIN, LONG_MAX.
+ },
+ strtoul__deps: ['_parseInt'],
+ strtoul: function(str, endptr, base) {
+ return __parseInt(str, endptr, base, 0, 4294967295, 32); // ULONG_MAX.
+ },
+ strtoull__deps: ['_parseInt'],
+ strtoull: function(str, endptr, base) {
+ return __parseInt(str, endptr, base, 0, 18446744073709551615, 64); // ULONG_MAX; imprecise.
+ },
qsort__deps: ['memcpy'],
qsort: function(base, num, size, comparator) {
@@ -3258,6 +3358,13 @@ LibraryManager.library = {
var poolPtr;
var envPtr;
if (_environ === null) {
+ // Set default values. Use string keys for Closure Compiler compatibility.
+ ENV['USER'] = 'root';
+ ENV['PATH'] = '/';
+ ENV['PWD'] = '/';
+ ENV['HOME'] = '/';
+ ENV['LANG'] = 'en_US.UTF-8';
+ ENV['_'] = './this.program';
// Allocate memory.
poolPtr = allocate(TOTAL_ENV_SIZE, 'i8', ALLOC_STATIC);
envPtr = allocate(MAX_ENV_VALUES * {{{ QUANTUM_SIZE }}},
@@ -3300,14 +3407,7 @@ LibraryManager.library = {
},
$ENV__deps: ['__buildEnvironment'],
$ENV__postset: '___buildEnvironment(ENV);',
- $ENV: {
- 'USER': 'root',
- 'PATH': '/',
- 'PWD': '/',
- 'HOME': '/',
- 'LANG': 'en_US.UTF-8',
- '_': './this.program'
- },
+ $ENV: {},
getenv__deps: ['$ENV'],
getenv: function(name) {
// char *getenv(const char *name);
@@ -3546,16 +3646,6 @@ LibraryManager.library = {
return pdest;
},
- strtol: function(ptr, endptr, base) {
- assert(!endptr, "We don't support all strtol params yet");
- return parseInt(Pointer_stringify(ptr), base);
- },
- strtoul__deps: ['strtol'],
- strtoul: function(ptr, endptr, base) {
- var result = _strtol(ptr, endptr, base);
- return unSign(result, 32);
- },
-
strcmp__deps: ['strncmp'],
strcmp: function(px, py) {
return _strncmp(px, py, TOTAL_MEMORY);
@@ -4339,7 +4429,7 @@ LibraryManager.library = {
return 0;
} else {
if (DLFCN_DATA.error) _free(DLFCN_DATA.error);
- var msgArr = Module.intArrayFromString(DLFCN_DATA.errorMsg);
+ var msgArr = intArrayFromString(DLFCN_DATA.errorMsg);
DLFCN_DATA.error = allocate(msgArr, 'i8', ALLOC_NORMAL);
DLFCN_DATA.errorMsg = null;
return DLFCN_DATA.error;
@@ -4625,15 +4715,17 @@ LibraryManager.library = {
// setjmp.h
// ==========================================================================
- _setjmp: function(env) {
+ setjmp: function(env) {
// XXX print('WARNING: setjmp() not really implemented, will fail if longjmp() is actually called');
return 0;
},
+ _setjmp: 'setjmp',
- _longjmp: function(env, val) {
+ longjmp: function(env, val) {
// not really working...
assert(0);
},
+ _longjmp: 'longjmp',
// ==========================================================================
// signal.h
diff --git a/src/preamble.js b/src/preamble.js
index 54011558..8ab9bce8 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -562,7 +562,7 @@ Module['String_copy'] = String_copy;
// Tools
if (typeof print === 'undefined') {
- print = console.log; // we are on the web
+ this['print'] = console.log; // we are on the web
}
// This processes a JS string into a C-line array of numbers, 0-terminated.
diff --git a/tests/parseInt/output.txt b/tests/parseInt/output.txt
new file mode 100644
index 00000000..f487db74
--- /dev/null
+++ b/tests/parseInt/output.txt
@@ -0,0 +1,134 @@
+strtol("-9223372036854775809") = -2147483648
+ERR 34
+strtoll("-9223372036854775809") = -9223372036854776000
+ERR 34
+strtoul("-9223372036854775809") = 4294967295
+ERR 34
+strtoull("-9223372036854775809") = 9223372036854774000
+
+strtol("-9223372036854775808") = -2147483648
+ERR 34
+strtoll("-9223372036854775808") = -9223372036854776000
+ERR 34
+strtoul("-9223372036854775808") = 4294967295
+ERR 34
+strtoull("-9223372036854775808") = 9223372036854774000
+
+strtol("-9223372036854775807") = -2147483648
+ERR 34
+strtoll("-9223372036854775807") = -9223372036854776000
+ERR 34
+strtoul("-9223372036854775807") = 4294967295
+ERR 34
+strtoull("-9223372036854775807") = 9223372036854774000
+
+strtol("-2147483649") = -2147483648
+ERR 34
+strtoll("-2147483649") = -2147483649
+strtoul("-2147483649") = 2147483647
+strtoull("-2147483649") = 18446744071562068000
+
+strtol("-2147483648") = -2147483648
+strtoll("-2147483648") = -2147483648
+strtoul("-2147483648") = 2147483648
+strtoull("-2147483648") = 18446744071562068000
+
+strtol("-2147483647") = -2147483647
+strtoll("-2147483647") = -2147483647
+strtoul("-2147483647") = 2147483649
+strtoull("-2147483647") = 18446744071562068000
+
+strtol("-5") = -5
+strtoll("-5") = -5
+strtoul("-5") = 4294967291
+strtoull("-5") = 18446744073709552000
+
+strtol("-1") = -1
+strtoll("-1") = -1
+strtoul("-1") = 4294967295
+strtoull("-1") = 18446744073709552000
+
+strtol("0") = 0
+strtoll("0") = 0
+strtoul("0") = 0
+strtoull("0") = 0
+
+strtol("1") = 1
+strtoll("1") = 1
+strtoul("1") = 1
+strtoull("1") = 1
+
+strtol("5") = 5
+strtoll("5") = 5
+strtoul("5") = 5
+strtoull("5") = 5
+
+strtol("2147483646") = 2147483646
+strtoll("2147483646") = 2147483646
+strtoul("2147483646") = 2147483646
+strtoull("2147483646") = 2147483646
+
+strtol("2147483647") = 2147483647
+strtoll("2147483647") = 2147483647
+strtoul("2147483647") = 2147483647
+strtoull("2147483647") = 2147483647
+
+strtol("2147483648") = 2147483647
+ERR 34
+strtoll("2147483648") = 2147483648
+strtoul("2147483648") = 2147483648
+strtoull("2147483648") = 2147483648
+
+strtol("4294967294") = 2147483647
+ERR 34
+strtoll("4294967294") = 4294967294
+strtoul("4294967294") = 4294967294
+strtoull("4294967294") = 4294967294
+
+strtol("4294967295") = 2147483647
+ERR 34
+strtoll("4294967295") = 4294967295
+strtoul("4294967295") = 4294967295
+strtoull("4294967295") = 4294967295
+
+strtol("4294967296") = 2147483647
+ERR 34
+strtoll("4294967296") = 4294967296
+strtoul("4294967296") = 4294967295
+ERR 34
+strtoull("4294967296") = 4294967296
+
+strtol("18446744073709551614") = 2147483647
+ERR 34
+strtoll("18446744073709551614") = 9223372036854776000
+ERR 34
+strtoul("18446744073709551614") = 4294967295
+ERR 34
+strtoull("18446744073709551614") = 18446744073709552000
+
+strtol("18446744073709551615") = 2147483647
+ERR 34
+strtoll("18446744073709551615") = 9223372036854776000
+ERR 34
+strtoul("18446744073709551615") = 4294967295
+ERR 34
+strtoull("18446744073709551615") = 18446744073709552000
+
+strtol("18446744073709551616") = 2147483647
+ERR 34
+strtoll("18446744073709551616") = 9223372036854776000
+ERR 34
+strtoul("18446744073709551616") = 4294967295
+ERR 34
+strtoull("18446744073709551616") = 18446744073709552000
+
+strtol("0x12", 0, 0) = 18
+strtol("0x12", 0, 10) = 0
+strtol("012", 0, 0) = 10
+strtol("012", 0, 10) = 12
+strtol("0y12", 0, 0) = 0
+strtol("hello", 0, 30) = 14167554
+strtol("hello", 0, 10) = 0
+strtol("not-a-number") = 0
+strtol(" 0x12end") = 302
+endptr - str = 7
diff --git a/tests/parseInt/src.c b/tests/parseInt/src.c
new file mode 100644
index 00000000..dde15410
--- /dev/null
+++ b/tests/parseInt/src.c
@@ -0,0 +1,64 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+void check_error() {
+ if (errno) {
+ printf("ERR %d\n", errno);
+ errno = 0;
+ }
+}
+
+int main() {
+ char* test_values[] = {
+ "-9223372036854775809",
+ "-9223372036854775808",
+ "-9223372036854775807",
+ "-2147483649",
+ "-2147483648",
+ "-2147483647",
+ "-5",
+ "-1",
+ "0",
+ "1",
+ "5",
+ "2147483646",
+ "2147483647",
+ "2147483648",
+ "4294967294",
+ "4294967295",
+ "4294967296",
+ "18446744073709551614",
+ "18446744073709551615",
+ "18446744073709551616",
+ };
+
+ for (int i = 0; i < sizeof(test_values) / sizeof(test_values[0]); i++) {
+ printf("strtol(\"%s\") = %ld\n", test_values[i], strtol(test_values[i], 0, 0));
+ check_error();
+ printf("strtoll(\"%s\") = %lld\n", test_values[i], strtoll(test_values[i], 0, 0));
+ check_error();
+ printf("strtoul(\"%s\") = %lu\n", test_values[i], strtoul(test_values[i], 0, 0));
+ check_error();
+ printf("strtoull(\"%s\") = %llu\n", test_values[i], strtoull(test_values[i], 0, 0));
+ check_error();
+ printf("\n");
+ }
+
+ printf("strtol(\"0x12\", 0, 0) = %ld\n", strtol("0x12", 0, 0));
+ printf("strtol(\"0x12\", 0, 10) = %ld\n", strtol("0x12", 0, 10));
+ printf("strtol(\"012\", 0, 0) = %ld\n", strtol("012", 0, 0));
+ printf("strtol(\"012\", 0, 10) = %ld\n", strtol("012", 0, 10));
+ printf("strtol(\"0y12\", 0, 0) = %ld\n", strtol("0y12", 0, 0));
+ printf("strtol(\"hello\", 0, 30) = %ld\n", strtol("hello", 0, 30));
+ printf("strtol(\"hello\", 0, 10) = %ld\n", strtol("hello", 0, 10));
+ printf("strtol(\"not-a-number\") = %ld\n", strtol("not-a-number", 0, 0));
+
+ char str[] = " 0x12end";
+ char* endptr;
+ printf("strtol(\" 0x12end\") = %ld\n", strtol(str, &endptr, 0));
+ printf("endptr - str = %d\n", endptr - str);
+ check_error();
+
+ return 0;
+}
diff --git a/tests/runner.py b/tests/runner.py
index d452bb74..54898ac0 100644
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -667,8 +667,9 @@ if 'benchmark' not in str(sys.argv):
memmove(&x, &y, magic-7); // 0 should not crash us
int xx, yy, zz;
- int cc = sscanf("abc_10.b1_xyz_543", "abc_%d.%2x_xyz_%3d", &xx, &yy, &zz);
- printf("%d:%d,%d,%d\\n", cc, xx, yy, zz);
+ char s[32];
+ int cc = sscanf("abc_10.b1_xyz_543_defg", "abc_%d.%2x_xyz_%3d_%3s", &xx, &yy, &zz, s);
+ printf("%d:%d,%d,%d,%s\\n", cc, xx, yy, zz, s);
printf("%d\\n", argc);
puts(argv[1]);
@@ -687,7 +688,7 @@ if 'benchmark' not in str(sys.argv):
return 0;
}
'''
- self.do_test(src, '3:10,177,543\n4\nwowie\ntoo\n76\n5\n(null)\n/* a comment */\n// another\ntest\n', ['wowie', 'too', '74'])
+ self.do_test(src, '4:10,177,543,def\n4\nwowie\ntoo\n76\n5\n(null)\n/* a comment */\n// another\ntest\n', ['wowie', 'too', '74'])
def test_error(self):
src = r'''
@@ -2216,6 +2217,7 @@ if 'benchmark' not in str(sys.argv):
printf("%g\n", strtod("1.0", &endptr));
printf("%g\n", strtod("123", &endptr));
printf("%g\n", strtod("123.456", &endptr));
+ printf("%g\n", strtod("-123.456", &endptr));
printf("%g\n", strtod("1234567891234567890", &endptr));
printf("%g\n", strtod("1234567891234567890e+50", &endptr));
printf("%g\n", strtod("84e+220", &endptr));
@@ -2224,8 +2226,8 @@ if 'benchmark' not in str(sys.argv):
printf("%g\n", strtod("123e-250", &endptr));
printf("%g\n", strtod("123e-450", &endptr));
- char str[] = "12.34e56end";
- strtod(str, &endptr);
+ char str[] = " 12.34e56end";
+ printf("%g\n", strtod(str, &endptr));
printf("%d\n", endptr - str);
return 0;
}
@@ -2239,6 +2241,7 @@ if 'benchmark' not in str(sys.argv):
1
123
123.456
+ -123.456
1.23457e+18
1.23457e+68
8.4e+221
@@ -2246,10 +2249,17 @@ if 'benchmark' not in str(sys.argv):
1.23e-48
1.23e-248
0
- 8
+ 1.234e+57
+ 10
'''
self.do_test(src, re.sub(r'\n\s+', '\n', expected))
+ def test_parseInt(self):
+ if USE_TYPED_ARRAYS != 0: return self.skip() # Typed arrays truncate i64.
+ src = open(path_from_root('tests', 'parseInt', 'src.c'), 'r').read()
+ expected = open(path_from_root('tests', 'parseInt', 'output.txt'), 'r').read()
+ self.do_test(src, expected)
+
def test_printf(self):
if USE_TYPED_ARRAYS != 0: return self.skip() # Typed arrays truncate i64.
src = open(path_from_root('tests', 'printf', 'test.c'), 'r').read()
diff --git a/tools/eliminator/eliminator-test-output.js b/tools/eliminator/eliminator-test-output.js
index 4ff8a965..19faa80d 100644
--- a/tools/eliminator/eliminator-test-output.js
+++ b/tools/eliminator/eliminator-test-output.js
@@ -6,12 +6,11 @@ function f() {
HEAP[123] = (GLOB[1] + 1) / 2;
}
var g = function(a1, a2) {
- var __label__;
var a = 1;
var c = a * 2 - 1;
- a++;
+ a = c;
foo(c);
foo(2);
@@ -24,7 +23,7 @@ var g = function(a1, a2) {
quux(iterator);
}
var $0 = HEAP[5];
- HEAP[myglobal] = 123;
+ MAYBE_HEAP[myglobal] = 123;
if ($0 < 0) {
__label__ = 1;
@@ -41,3 +40,54 @@ var g = function(a1, a2) {
4: 5
};
};
+function h() {
+ var out;
+ bar(hello);
+ var hello = 5;
+ if (0) {
+ var sb1 = 21;
+ }
+ out = sb1;
+ if (0) {
+ var sb2 = 23;
+ } else {
+ out = sb2;
+ }
+ if (0) {
+ out = sb3;
+ } else {
+ var sb3 = 23;
+ }
+ for (var it = 0; it < 5; it++) {
+ x = y ? x + 1 : 7;
+ var x = -5;
+ }
+
+ if (1) {
+ otherGlob = glob;
+ breakMe();
+ }
+ var oneUse2 = glob2;
+ while (1) {
+ otherGlob2 = oneUse2;
+ breakMe();
+ }
+ return out;
+}
+function strtok_part(b, j, f) {
+ var a;
+ for (;;) {
+ h = a == 13 ? h : 0;
+ a = HEAP[d + h];
+ if (a == g != 0) break;
+ var h = h + 1;
+ if (a != 0) a = 13;
+ }
+}
+function py() {
+
+
+ var $7 = HEAP[HEAP[__PyThreadState_Current] + 12] + 1;
+ var $8 = HEAP[__PyThreadState_Current] + 12;
+ HEAP[$8] = $7;
+}
diff --git a/tools/eliminator/eliminator-test.js b/tools/eliminator/eliminator-test.js
index 68d9fdf5..8a364c0a 100644
--- a/tools/eliminator/eliminator-test.js
+++ b/tools/eliminator/eliminator-test.js
@@ -1,28 +1,29 @@
function f() {
- var __label__;
+ var unused;
var x = GLOB[1];
var y = x + 1;
var z = y / 2;
HEAP[123] = z;
}
var g = function (a1, a2) {
- var __label__;
var a = 1;
var b = a * 2;
var c = b - 1;
var qqq = "qwe";
- a++;
+ a = c;
foo(c);
var ww = 1, www, zzz = 2;
foo(zzz);
for (var i = 0; i < 5; i++) {
- var q = {a:1} + [2,3];
+ var q = {
+ a: 1
+ } + [ 2, 3 ];
}
for (var iterator in SOME_GLOBAL) {
quux(iterator);
}
var $0 = HEAP[5];
- HEAP[myglobal] = 123;
+ MAYBE_HEAP[myglobal] = 123;
var $1 = $0 < 0;
if ($1) {
__label__ = 1;
@@ -38,4 +39,55 @@ var g = function (a1, a2) {
unquoted: 3,
4: 5
};
+};
+function h() {
+ var out;
+ bar(hello);
+ var hello = 5;
+ if (0) {
+ var sb1 = 21;
+ }
+ out = sb1;
+ if (0) {
+ var sb2 = 23;
+ } else {
+ out = sb2;
+ }
+ if (0) {
+ out = sb3;
+ } else {
+ var sb3 = 23;
+ }
+ for (var it = 0; it < 5; it++) {
+ x = y ? x + 1 : 7;
+ var x = -5;
+ }
+ var oneUse = glob;
+ if (1) {
+ otherGlob = oneUse;
+ breakMe();
+ }
+ var oneUse2 = glob2;
+ while (1) {
+ otherGlob2 = oneUse2;
+ breakMe();
+ }
+ return out;
+}
+function strtok_part(b, j, f) {
+ var a;
+ for (;;) {
+ h = a == 13 ? h : 0;
+ a = HEAP[d + h];
+ if (a == g != 0) break;
+ var h = h + 1;
+ if (a != 0) a = 13;
+ }
+}
+function py() {
+ var $4 = HEAP[__PyThreadState_Current];
+ var $5 = $4 + 12;
+ var $7 = HEAP[$5] + 1;
+ var $8 = $4 + 12;
+ HEAP[$8] = $7;
}
diff --git a/tools/eliminator/eliminator.coffee b/tools/eliminator/eliminator.coffee
index 242394b7..c07e5974 100644
--- a/tools/eliminator/eliminator.coffee
+++ b/tools/eliminator/eliminator.coffee
@@ -5,17 +5,12 @@
Single-def
Uses only side-effect-free nodes
- Single-use
- Uses only local, single-def names
- *
- Uses non-local or non-single-def names
- No flow-controlling statements between def and use
- No references to any deps between def and use
- No indirect accesses (subscript, dot notation) between def and use
- *
- Multi-use
- Uses only local, single-def names
- *
+ Unused
+ *
+ Has at most MAX_USES uses
+ No mutations to any dependencies between def and last use
+ No global dependencies or no indirect accesses between def and use
+ *
TODO(max99x): Eliminate single-def undefined-initialized vars with no uses
between declaration and definition.
@@ -42,14 +37,17 @@ NODES_WITHOUT_SIDE_EFFECTS =
binary: true
sub: true
-# Nodes which may alter control flow.
+# Nodes which may break control flow. Moving a variable beyond them may have
+# side effects.
CONTROL_FLOW_NODES =
return: true
break: true
continue: true
new: true
+ throw: true
call: true
label: true
+ debugger: true
# Traverses a JavaScript syntax tree rooted at the given node calling the given
# callback for each node.
@@ -62,7 +60,7 @@ CONTROL_FLOW_NODES =
# was stopped, false. Otherwise undefined.
traverse = (node, callback) ->
type = node[0]
- if type
+ if typeof type == 'string'
result = callback node, type
if result? then return result
@@ -85,6 +83,8 @@ class Eliminator
@body = func[3]
# Identifier stats. Each of these objects is indexed by the identifier name.
+ # Whether the identifier is a local variable.
+ @isLocal = {}
# Whether the identifier is never modified after initialization.
@isSingleDef = {}
# How many times the identifier is used.
@@ -92,9 +92,8 @@ class Eliminator
# Whether the initial value of a single-def identifier uses only nodes
# evaluating which has no side effects.
@usesOnlySimpleNodes = {}
- # Whether the initial value of a single-def identifier uses only other
- # local single-def identifiers and/or literals.
- @usesOnlySingleDefs = {}
+ # Whether the identifier depends on any non-local name, perhaps indirectly.
+ @dependsOnAGlobal = {}
# Whether the dependencies of the single-def identifier may be mutated
# within its live range.
@depsMutatedInLiveRange = {}
@@ -133,7 +132,7 @@ class Eliminator
closureFound = false
traverse @body, (node, type) ->
- if type in ['defun', 'function']
+ if type in ['defun', 'function', 'with']
closureFound = true
return false
return undefined
@@ -141,6 +140,7 @@ class Eliminator
return closureFound
# Runs the basic variable scan pass. Fills the following member variables:
+ # isLocal
# isSingleDef
# useCount
# initialValue
@@ -148,13 +148,15 @@ class Eliminator
traverse @body, (node, type) =>
if type is 'var'
for [varName, varValue] in node[1]
+ @isLocal[varName] = true
if not varValue? then varValue = ['name', 'undefined']
@isSingleDef[varName] = not @isSingleDef.hasOwnProperty varName
@initialValue[varName] = varValue
@useCount[varName] = 0
else if type is 'name'
varName = node[1]
- if varName of @useCount then @useCount[varName]++
+ if @useCount.hasOwnProperty varName then @useCount[varName]++
+ else @isSingleDef[varName] = false
else if type in ['assign', 'unary-prefix', 'unary-postfix']
varName = node[2][1]
if @isSingleDef[varName] then @isSingleDef[varName] = false
@@ -164,13 +166,12 @@ class Eliminator
# Analyzes the initial values of single-def variables. Requires basic variable
# stats to have been calculated. Fills the following member variables:
# dependsOn
+ # dependsOnAGlobal
# usesOnlySimpleNodes
- # usesOnlySingleDefs
analyzeInitialValues: ->
for varName of @isSingleDef
if not @isSingleDef[varName] then continue
@usesOnlySimpleNodes[varName] = true
- @usesOnlySingleDefs[varName] = true
traverse @initialValue[varName], (node, type) =>
if type not of NODES_WITHOUT_SIDE_EFFECTS
@usesOnlySimpleNodes[varName] = false
@@ -178,13 +179,13 @@ class Eliminator
reference = node[1]
if reference != 'undefined'
if not @dependsOn[reference]? then @dependsOn[reference] = {}
+ if not @isLocal[reference] then @dependsOnAGlobal[varName] = true
@dependsOn[reference][varName] = true
- if not @isSingleDef[reference]
- @usesOnlySingleDefs[varName] = false
return undefined
return undefined
- # Updates the dependency graph (@dependsOn) to its transitive closure.
+ # Updates the dependency graph (@dependsOn) to its transitive closure and
+ # synchronizes @dependsOnAGlobal to the new dependencies.
calculateTransitiveDependencies: ->
incomplete = true
while incomplete
@@ -193,58 +194,91 @@ class Eliminator
for source of sources
for source2 of @dependsOn[source]
if not @dependsOn[target][source2]
- if not @isSingleDef[target]
- @usesOnlySingleDefs[source2] = false
+ if not @isLocal[target] then @dependsOnAGlobal[source2] = true
@dependsOn[target][source2] = true
incomplete = true
return undefined
- # Analyzes the live ranges of single-def single-use variables. Requires
- # dependencies to have been calculated. Fills the following member variables:
+ # Analyzes the live ranges of single-def variables. Requires dependencies to
+ # have been calculated. Fills the following member variables:
# depsMutatedInLiveRange
analyzeLiveRanges: ->
isLive = {}
+ # Checks if a given node may mutate any of the currently live variables.
checkForMutations = (node, type) =>
+ usedInThisStatement = {}
+ if type in ['assign', 'call']
+ traverse node.slice(2, 4), (node, type) =>
+ if type is 'name' then usedInThisStatement[node[1]] = true
+ return undefined
+
+ if type in ['assign', 'unary-prefix', 'unary-postfix']
+ if type is 'assign' or node[1] in ['--', '++']
+ reference = node[2]
+ while reference[0] != 'name'
+ reference = reference[1]
+ reference = reference[1]
+ if @dependsOn[reference]?
+ for varName of @dependsOn[reference]
+ if isLive[varName]
+ isLive[varName] = false
+
if type of CONTROL_FLOW_NODES
for varName of isLive
- @depsMutatedInLiveRange[varName] = true
- isLive = {}
+ if @dependsOnAGlobal[varName] or not usedInThisStatement[varName]
+ isLive[varName] = false
+ else if type is 'assign'
+ for varName of isLive
+ if @dependsOnAGlobal[varName] and not usedInThisStatement[varName]
+ isLive[varName] = false
else if type is 'name'
reference = node[1]
- if @dependsOn[reference]?
- for varName of @dependsOn[reference]
- if isLive[varName]
- @depsMutatedInLiveRange[varName] = true
- if isLive[reference]
- delete isLive[reference]
+ if @isSingleDef[reference]
+ if not isLive[reference]
+ @depsMutatedInLiveRange[reference] = true
return undefined
- traverse @body, (node, type) =>
- if type is 'var'
+ # Analyzes a block and all its children for variable ranges. Makes sure to
+ # account for the worst case of possible mutations.
+ analyzeBlock = (node, type) =>
+ if type in ['switch', 'if', 'try', 'do', 'while', 'for', 'for-in']
+ traverseChild = (child) ->
+ if typeof child == 'object' and child?.length
+ savedLive = {}
+ for name of isLive then savedLive[name] = true
+ traverse child, analyzeBlock
+ for name of isLive
+ if not isLive[name] then savedLive[name] = false
+ isLive = savedLive
+ if type is 'switch'
+ traverseChild node[1]
+ for child in node[2]
+ traverseChild child
+ else if type in ['if', 'try']
+ for child in node
+ traverseChild child
+ else
+ # Don't put anything from outside into the body of a loop.
+ savedLive = isLive
+ isLive = {}
+ for child in node then traverseChild child
+ for name of isLive
+ if not isLive[name] then savedLive[name] = false
+ isLive = savedLive
+ return node
+ else if type is 'var'
for [varName, varValue] in node[1]
if varValue? then traverse varValue, checkForMutations
- if @isSingleDef[varName] and @useCount[varName] == 1
+ if @isSingleDef[varName]
isLive[varName] = true
return node
- else if type is 'stat'
- usedInThisStatement = {}
- hasIndirectAccess = false
- traverse node, (node, type) =>
- if type is 'name'
- usedInThisStatement[node[1]] = true
- else if type in ['sub', 'dot']
- hasIndirectAccess = true
- return undefined
- if hasIndirectAccess
- for varName of isLive
- if not usedInThisStatement[varName]
- @depsMutatedInLiveRange[varName] = true
- delete isLive[varName]
else
checkForMutations node, type
return undefined
+ traverse @body, analyzeBlock
+
return undefined
# Determines whether a given variable can be safely eliminated. Requires all
@@ -253,11 +287,8 @@ class Eliminator
if @isSingleDef[varName] and @usesOnlySimpleNodes[varName]
if @useCount[varName] == 0
return true
- else if @useCount[varName] == 1
- return (@usesOnlySingleDefs[varName] or
- not @depsMutatedInLiveRange[varName])
else if @useCount[varName] <= MAX_USES
- return @usesOnlySingleDefs[varName]
+ return not @depsMutatedInLiveRange[varName]
return false
# Removes all var declarations for the specified variables.
@@ -265,7 +296,7 @@ class Eliminator
removeDeclarations: (toRemove) ->
traverse @body, (node, type) ->
if type is 'var'
- intactVars = (i for i in node[1] when i[0] not of toRemove)
+ intactVars = (i for i in node[1] when not toRemove.hasOwnProperty i[0])
if intactVars.length
node[1] = intactVars
return node
@@ -283,7 +314,7 @@ class Eliminator
incomplete = false
for varName, varValue of values
result = traverse varValue, (node, type) ->
- if type == 'name' and node[1] of values and node[1] != varName
+ if type == 'name' and values.hasOwnProperty(node[1]) and node[1] != varName
incomplete = true
return values[node[1]]
return undefined
@@ -295,7 +326,7 @@ class Eliminator
# @arg replacements: A map from variable names to AST expressions.
updateUses: (replacements) ->
traverse @body, (node, type) ->
- if type is 'name' and node[1] of replacements
+ if type is 'name' and replacements.hasOwnProperty node[1]
return replacements[node[1]]
return undefined
return undefined
diff --git a/tools/eliminator/node_modules/uglify-js/lib/process.js b/tools/eliminator/node_modules/uglify-js/lib/process.js
index 85709857..c3abb6f8 100644
--- a/tools/eliminator/node_modules/uglify-js/lib/process.js
+++ b/tools/eliminator/node_modules/uglify-js/lib/process.js
@@ -1751,7 +1751,9 @@ function gen_code(ast, options) {
out += " " + make_name(name);
}
out += "(" + add_commas(MAP(args, make_name)) + ")";
- return add_spaces([ out, make_block(body) ]);
+ var result = add_spaces([ out, make_block(body) ])
+ if (!name) result = "(" + result + ")";
+ return result;
};
function must_has_semicolon(node) {