diff options
author | Alon Zakai <alonzakai@gmail.com> | 2011-08-30 12:56:34 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2011-08-30 12:56:34 -0700 |
commit | 4943a80e08e0c5bfc97fad9de625d6a7d24548f9 (patch) | |
tree | 0cbda8a068d48590f54e9e89d8d67a93d3ea1d2c | |
parent | 639775b8a012a89e19ae5ba3ce459248b4cd98a9 (diff) | |
parent | c746d2005286626f8a468b32df59073c7ec88639 (diff) |
Merge pull request #75 from max99x/master
Eliminator update, strtox(), Closure compatibility and other fixes
-rw-r--r-- | src/library.js | 138 | ||||
-rw-r--r-- | src/preamble.js | 2 | ||||
-rw-r--r-- | tests/parseInt/output.txt | 134 | ||||
-rw-r--r-- | tests/parseInt/src.c | 64 | ||||
-rw-r--r-- | tests/runner.py | 22 | ||||
-rw-r--r-- | tools/eliminator/eliminator-test-output.js | 56 | ||||
-rw-r--r-- | tools/eliminator/eliminator-test.js | 62 | ||||
-rw-r--r-- | tools/eliminator/eliminator.coffee | 149 | ||||
-rw-r--r-- | tools/eliminator/node_modules/uglify-js/lib/process.js | 4 |
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; }; |