Use ESTree-based optimizer for core JS passes, to support ES6+ inputs (#7973)
Fixes #6000 The key change here is to rewrite the JS optimizer passes that run in a normal `-O3` etc. build from the Uglify1 AST to ESTree. With ESTree we can use modern parsers etc. so that we support ES6+ inputs to js libraries, pre-jses, EM_ASM, etc. Aside from that rewrite, the other changes are less critical and can be altered later. Specifically, this uses acorn for parsing and terser for outputting, but we could switch to anything using ESTree very easily. Acorn is nice for parsing since it's small and standalone. For outputting I experimented with astring, which is small and nice, and escodegen, which looks very robust, but neither could output compact-enough JS to not regress our JS code sizes. This is not truly critical since for minimal code size people should use closure anyhow, however, it's nice for default builds to be small (and we don't run closure by default), and I didn't want to regress anything. Using the terser outputter achieves that. (Since it uses the Uglify2 AST internally, this means using their tool to convert ESTree to Uglify2.) They may be some minor code size changes with this PR, just because we use a different outputter now, but nothing major in either direction. Most changes seem positive actually. Sizes after closure are unchanged. This uses almost unmodified versions of acorn and terser, but they are stripped down to what we need, and I had to make two modifications, see these PRs: [acornjs/acorn#793](https://github.com/acornjs/acorn/pull/793) (quote the error on parse exceptions) and [mishoo/UglifyJS2#3323](https://github.com/mishoo/UglifyJS2/pull/3323) (preserve quoted properties). This may very slightly regress compile times when using those passes, as Uglify1 was just very fast. However, the change should be very small. This does _not_ rewrite every single JS optimizer pass. In particular the asm.js passes don't need to support ES6, and so don't need to be rewritten. There are also optional passes that do not run by default, that we can convert later depending on priority.
This commit is contained in:
Родитель
c3e6c600e0
Коммит
59f08fb3ab
|
@ -1,15 +1,20 @@
|
|||
|
||||
var z = fleefl();
|
||||
|
||||
var zz = fleefl();
|
||||
|
||||
function g(a) {
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
Module["g"] = g;
|
||||
|
||||
function h(a) {
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
print(h(123));
|
||||
((function() {
|
||||
|
||||
(function() {
|
||||
var z = fleefl();
|
||||
var zz = fleefl();
|
||||
function g(a) {
|
||||
|
@ -20,8 +25,9 @@ print(h(123));
|
|||
return a + 1;
|
||||
}
|
||||
print(hh(123));
|
||||
}))();
|
||||
})();
|
||||
|
||||
function glue() {
|
||||
}
|
||||
glue();
|
||||
|
||||
glue();
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
for (var i in x) {}
|
||||
|
||||
for (var j = 0; ;) {}
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
for (var i in x) {}
|
||||
for (var j = 0;;) {}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
function hasOwnProperty(obj, prop) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||
}
|
||||
|
||||
if (hasOwnProperty({}, "prop_name")) {
|
||||
console.log("yeah");
|
||||
}
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
|
||||
var z = fleefl();
|
||||
|
||||
var zz = fleefl();
|
||||
|
||||
var keeperObj = {
|
||||
x: fleefl()
|
||||
};
|
||||
|
||||
var keeperArray = [ 1, 2, "3", four() ];
|
||||
|
||||
function g(a) {
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
Module["g"] = g;
|
||||
|
||||
function h(a) {
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
print(h(123));
|
||||
((function() {
|
||||
|
||||
(function() {
|
||||
var z = fleefl();
|
||||
var zz = fleefl();
|
||||
function g(a) {
|
||||
|
@ -24,31 +31,38 @@ print(h(123));
|
|||
return a + 1;
|
||||
}
|
||||
print(hh(123));
|
||||
}))();
|
||||
})();
|
||||
|
||||
function glue() {
|
||||
function lookup() {
|
||||
throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
glue();
|
||||
|
||||
function _glCreateShader() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
function emulate() {
|
||||
_glCreateShader = function _glCreateShader(shaderType) {
|
||||
return glCreateShader();
|
||||
};
|
||||
}
|
||||
|
||||
emulate();
|
||||
|
||||
___cxa_find_matching_catch_before();
|
||||
|
||||
function ___cxa_find_matching_catch_before() {
|
||||
if (!___cxa_find_matching_catch_before.buffer) ___cxa_find_matching_catch_before.buffer = {};
|
||||
}
|
||||
|
||||
function ___cxa_find_matching_catch_after() {
|
||||
if (!___cxa_find_matching_catch_after.buffer) ___cxa_find_matching_catch_after.buffer = {};
|
||||
}
|
||||
|
||||
___cxa_find_matching_catch_after();
|
||||
|
||||
var dotOther = Side.effect;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
var defun = (function() {
|
||||
})();
|
||||
var name = (function() {
|
||||
})();
|
||||
var object = (function() {
|
||||
})();
|
||||
var non_reserved = (function() {
|
||||
})();
|
||||
function func_1() {
|
||||
}
|
||||
function func_2() {
|
||||
}
|
||||
function func_3() {
|
||||
}
|
||||
function func_4() {
|
||||
}
|
||||
function func_5() {
|
||||
}
|
||||
function func_6() {
|
||||
}
|
||||
function func_7() {
|
||||
}
|
||||
function func_8() {
|
||||
}
|
||||
function func_9() {
|
||||
}
|
||||
function func_10() {
|
||||
}
|
||||
var quotedObject = {
|
||||
"var": func_1,
|
||||
"defun": func_2,
|
||||
"function": func_3,
|
||||
"name": func_4,
|
||||
"non_reserved": func_5
|
||||
};
|
||||
var unquotedObject = {
|
||||
"var": func_6,
|
||||
defun: func_7,
|
||||
"function": func_8,
|
||||
name: func_9,
|
||||
non_reserved: func_10
|
||||
};
|
||||
var recursiveObject = {
|
||||
object: {
|
||||
func: (function() {
|
||||
}),
|
||||
object: {
|
||||
func: (function() {
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
quotedObject || unquotedObject || recursiveObject;
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
var defun = (function () { var a = 1; })();
|
||||
var name = (function () { var a = 1; })();
|
||||
var object = (function () { var a = 1; })();
|
||||
var non_reserved = (function () { var a = 1; })();
|
||||
|
||||
function func_1() { var a = 1; }
|
||||
function func_2() { var a = 1; }
|
||||
function func_3() { var a = 1; }
|
||||
function func_4() { var a = 1; }
|
||||
function func_5() { var a = 1; }
|
||||
function func_6() { var a = 1; }
|
||||
function func_7() { var a = 1; }
|
||||
function func_8() { var a = 1; }
|
||||
function func_9() { var a = 1; }
|
||||
function func_10() { var a = 1; }
|
||||
function func_deleted() { var a = 1; }
|
||||
|
||||
var quotedObject = {
|
||||
"var": func_1,
|
||||
"defun": func_2,
|
||||
"function": func_3,
|
||||
"name": func_4,
|
||||
"non_reserved": func_5
|
||||
};
|
||||
|
||||
var unquotedObject = {
|
||||
var: func_6,
|
||||
defun: func_7,
|
||||
function: func_8,
|
||||
name: func_9,
|
||||
non_reserved: func_10
|
||||
};
|
||||
|
||||
var recursiveObject = {
|
||||
object: {
|
||||
func: function () {
|
||||
var a = 1;
|
||||
},
|
||||
object: {
|
||||
func: function () {
|
||||
var b = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quotedObject || unquotedObject || recursiveObject; // fake uses
|
|
@ -1,26 +1,40 @@
|
|||
var name;
|
||||
|
||||
var asmLibraryArg = {
|
||||
"save1": 1,
|
||||
"save2": 2
|
||||
};
|
||||
var expD1 = Module["expD1"] = asm["expD1"];
|
||||
var expD2 = Module["expD2"] = asm["expD2"];
|
||||
var expD3 = Module["expD3"] = asm["expD3"];
|
||||
var expD4 = undefined;
|
||||
var expI1 = Module["expI1"] = (function() {
|
||||
return Module["asm"]["expI1"].apply(null, arguments);
|
||||
});
|
||||
var expI2 = Module["expI2"] = (function() {
|
||||
return Module["asm"]["expI2"].apply(null, arguments);
|
||||
});
|
||||
var expI3 = Module["expI3"] = (function() {
|
||||
return Module["asm"]["expI3"].apply(null, arguments);
|
||||
});
|
||||
var expI4 = undefined;
|
||||
expD1;
|
||||
Module["expD2"];
|
||||
asm["expD3"];
|
||||
expI1;
|
||||
Module["expI2"];
|
||||
asm["expI3"];
|
||||
|
||||
var expD1 = Module["expD1"] = asm["expD1"];
|
||||
|
||||
var expD2 = Module["expD2"] = asm["expD2"];
|
||||
|
||||
var expD3 = Module["expD3"] = asm["expD3"];
|
||||
|
||||
var expD4;
|
||||
|
||||
var expI1 = Module["expI1"] = function() {
|
||||
return Module["asm"]["expI1"].apply(null, arguments);
|
||||
};
|
||||
|
||||
var expI2 = Module["expI2"] = function() {
|
||||
return Module["asm"]["expI2"].apply(null, arguments);
|
||||
};
|
||||
|
||||
var expI3 = Module["expI3"] = function() {
|
||||
return Module["asm"]["expI3"].apply(null, arguments);
|
||||
};
|
||||
|
||||
var expI4;
|
||||
|
||||
expD1;
|
||||
|
||||
Module["expD2"];
|
||||
|
||||
asm["expD3"];
|
||||
|
||||
expI1;
|
||||
|
||||
Module["expI2"];
|
||||
|
||||
asm["expI3"];
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var name;
|
||||
|
||||
var asmLibraryArg = {
|
||||
"a": 1,
|
||||
"A": 33,
|
||||
|
@ -12,10 +13,9 @@ var asmLibraryArg = {
|
|||
"d": ___syscall140,
|
||||
"q": ___syscall146
|
||||
};
|
||||
|
||||
var expD1 = Module["expD1"] = asm["c"];
|
||||
var expI1 = Module["expI1"] = (function() {
|
||||
|
||||
var expI1 = Module["expI1"] = function() {
|
||||
return Module["asm"]["d"].apply(null, arguments);
|
||||
});
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
var Module;
|
||||
if (!Module) Module = "__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__";
|
||||
var ENVIRONMENT_IS_NODE = typeof process === "object";
|
||||
if (ENVIRONMENT_IS_NODE) {
|
||||
var fs = require("fs");
|
||||
Module["wasm"] = fs.readFileSync(__dirname + "/a.wasm");
|
||||
}
|
||||
function out(text) {
|
||||
console.log(text);
|
||||
}
|
||||
function err(text) {
|
||||
console.error(text);
|
||||
}
|
||||
function ready() {
|
||||
run();
|
||||
}
|
||||
function abort(what) {
|
||||
throw what;
|
||||
}
|
||||
var UTF8Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined;
|
||||
function UTF8ArrayToString(u8Array, idx, maxBytesToRead) {
|
||||
var endIdx = idx + maxBytesToRead;
|
||||
var endPtr = idx;
|
||||
while (u8Array[endPtr] && !(endPtr >= endIdx)) ++endPtr;
|
||||
if (endPtr - idx > 16 && u8Array.subarray && UTF8Decoder) {
|
||||
return UTF8Decoder.decode(u8Array.subarray(idx, endPtr));
|
||||
} else {
|
||||
var str = "";
|
||||
while (idx < endPtr) {
|
||||
var u0 = u8Array[idx++];
|
||||
if (!(u0 & 128)) {
|
||||
str += String.fromCharCode(u0);
|
||||
continue;
|
||||
}
|
||||
var u1 = u8Array[idx++] & 63;
|
||||
if ((u0 & 224) == 192) {
|
||||
str += String.fromCharCode((u0 & 31) << 6 | u1);
|
||||
continue;
|
||||
}
|
||||
var u2 = u8Array[idx++] & 63;
|
||||
if ((u0 & 240) == 224) {
|
||||
u0 = (u0 & 15) << 12 | u1 << 6 | u2;
|
||||
} else {
|
||||
u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | u8Array[idx++] & 63;
|
||||
}
|
||||
if (u0 < 65536) {
|
||||
str += String.fromCharCode(u0);
|
||||
} else {
|
||||
var ch = u0 - 65536;
|
||||
str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
function UTF8ToString(ptr, maxBytesToRead) {
|
||||
return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
|
||||
}
|
||||
var TOTAL_MEMORY = 16777216, STATIC_BASE = 1024, DYNAMICTOP_PTR = 6016;
|
||||
var wasmMaximumMemory = TOTAL_MEMORY;
|
||||
var wasmMemory = new WebAssembly.Memory({
|
||||
"initial": TOTAL_MEMORY >> 16,
|
||||
"maximum": wasmMaximumMemory >> 16
|
||||
});
|
||||
var buffer = wasmMemory.buffer;
|
||||
var HEAP8 = new Int8Array(buffer);
|
||||
var HEAP16 = new Int16Array(buffer);
|
||||
var HEAP32 = new Int32Array(buffer);
|
||||
var HEAPU8 = new Uint8Array(buffer);
|
||||
var HEAPU16 = new Uint16Array(buffer);
|
||||
var HEAPU32 = new Uint32Array(buffer);
|
||||
var HEAPF32 = new Float32Array(buffer);
|
||||
var HEAPF64 = new Float64Array(buffer);
|
||||
HEAP32[DYNAMICTOP_PTR >> 2] = 5249152;
|
||||
var SYSCALLS = {
|
||||
buffers: [ null, [], [] ],
|
||||
printChar: function(stream, curr) {
|
||||
var buffer = SYSCALLS.buffers[stream];
|
||||
if (curr === 0 || curr === 10) {
|
||||
(stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
|
||||
buffer.length = 0;
|
||||
} else {
|
||||
buffer.push(curr);
|
||||
}
|
||||
},
|
||||
varargs: 0,
|
||||
get: function(varargs) {
|
||||
SYSCALLS.varargs += 4;
|
||||
var ret = HEAP32[SYSCALLS.varargs - 4 >> 2];
|
||||
return ret;
|
||||
},
|
||||
getStr: function() {
|
||||
var ret = UTF8ToString(SYSCALLS.get());
|
||||
return ret;
|
||||
},
|
||||
get64: function() {
|
||||
var low = SYSCALLS.get(), high = SYSCALLS.get();
|
||||
return low;
|
||||
},
|
||||
getZero: function() {
|
||||
SYSCALLS.get();
|
||||
}
|
||||
};
|
||||
function ___syscall140(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
var stream = SYSCALLS.getStreamFromFD(), offset_high = SYSCALLS.get(), offset_low = SYSCALLS.get(), result = SYSCALLS.get(), whence = SYSCALLS.get();
|
||||
var offset = offset_low;
|
||||
FS.llseek(stream, offset, whence);
|
||||
HEAP32[result >> 2] = stream.position;
|
||||
if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null;
|
||||
return 0;
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno;
|
||||
}
|
||||
}
|
||||
function ___syscall146(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
var stream = SYSCALLS.get(), iov = SYSCALLS.get(), iovcnt = SYSCALLS.get();
|
||||
var ret = 0;
|
||||
for (var i = 0; i < iovcnt; i++) {
|
||||
var ptr = HEAP32[iov + i * 8 >> 2];
|
||||
var len = HEAP32[iov + (i * 8 + 4) >> 2];
|
||||
for (var j = 0; j < len; j++) {
|
||||
SYSCALLS.printChar(stream, HEAPU8[ptr + j]);
|
||||
}
|
||||
ret += len;
|
||||
}
|
||||
return ret;
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno;
|
||||
}
|
||||
}
|
||||
function ___syscall54(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
return 0;
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno;
|
||||
}
|
||||
}
|
||||
function ___syscall6(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
var stream = SYSCALLS.getStreamFromFD();
|
||||
FS.close(stream);
|
||||
return 0;
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno;
|
||||
}
|
||||
}
|
||||
function _emscripten_get_now() {
|
||||
abort();
|
||||
}
|
||||
function _emscripten_random() {
|
||||
return Math.random();
|
||||
}
|
||||
function _emscripten_memcpy_big(dest, src, num) {
|
||||
HEAPU8.set(HEAPU8.subarray(src, src + num), dest);
|
||||
}
|
||||
if (ENVIRONMENT_IS_NODE) {
|
||||
_emscripten_get_now = function _emscripten_get_now_actual() {
|
||||
var t = process["hrtime"]();
|
||||
return t[0] * 1e3 + t[1] / 1e6;
|
||||
};
|
||||
} else if (typeof dateNow !== "undefined") {
|
||||
_emscripten_get_now = dateNow;
|
||||
} else if (typeof self === "object" && self["performance"] && typeof self["performance"]["now"] === "function") {
|
||||
_emscripten_get_now = function() {
|
||||
return self["performance"]["now"]();
|
||||
};
|
||||
} else if (typeof performance === "object" && typeof performance["now"] === "function") {
|
||||
_emscripten_get_now = function() {
|
||||
return performance["now"]();
|
||||
};
|
||||
} else {
|
||||
_emscripten_get_now = Date.now;
|
||||
}
|
||||
var asmLibraryArg = {
|
||||
"b": abort,
|
||||
"h": ___syscall140,
|
||||
"a": ___syscall146,
|
||||
"g": ___syscall54,
|
||||
"f": ___syscall6,
|
||||
"e": _emscripten_get_now,
|
||||
"d": _emscripten_memcpy_big,
|
||||
"c": _emscripten_random
|
||||
};
|
||||
function run() {
|
||||
var ret = _main();
|
||||
}
|
||||
function initRuntime(asm) {
|
||||
asm["i"]();
|
||||
}
|
||||
var env = asmLibraryArg;
|
||||
env["memory"] = wasmMemory;
|
||||
env["table"] = new WebAssembly.Table({
|
||||
"initial": 6,
|
||||
"maximum": 6,
|
||||
"element": "anyfunc"
|
||||
});
|
||||
env["__memory_base"] = STATIC_BASE;
|
||||
env["__table_base"] = 0;
|
||||
var imports = {
|
||||
"env": env,
|
||||
"global": {
|
||||
"NaN": NaN,
|
||||
Infinity: Infinity
|
||||
},
|
||||
"global.Math": Math,
|
||||
"asm2wasm": {
|
||||
"f64-rem": function(x, y) {
|
||||
return x % y;
|
||||
},
|
||||
"debugger": function() {
|
||||
debugger;
|
||||
}
|
||||
}
|
||||
};
|
||||
var ___errno_location, _llvm_bswap_i32, _main, _memcpy, _memset, dynCall_ii, dynCall_iiii;
|
||||
WebAssembly.instantiate(Module["wasm"], imports).then(function(output) {
|
||||
var asm = output.instance.exports;
|
||||
___errno_location = asm["j"];
|
||||
_llvm_bswap_i32 = asm["k"];
|
||||
_main = asm["l"];
|
||||
_memcpy = asm["m"];
|
||||
_memset = asm["n"];
|
||||
dynCall_ii = asm["o"];
|
||||
dynCall_iiii = asm["p"];
|
||||
initRuntime(asm);
|
||||
ready();
|
||||
});
|
|
@ -0,0 +1,267 @@
|
|||
var Module;
|
||||
if (!Module) Module = "__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__";
|
||||
var ENVIRONMENT_IS_NODE = typeof process === "object";
|
||||
if (ENVIRONMENT_IS_NODE) {
|
||||
var fs = require("fs");
|
||||
Module["wasm"] = fs.readFileSync(__dirname + "/a.wasm")
|
||||
}
|
||||
|
||||
function out(text) {
|
||||
console.log(text)
|
||||
}
|
||||
|
||||
function err(text) {
|
||||
console.error(text)
|
||||
}
|
||||
|
||||
function ready() {
|
||||
run()
|
||||
}
|
||||
|
||||
function abort(what) {
|
||||
throw what
|
||||
}
|
||||
var UTF8Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined;
|
||||
|
||||
function UTF8ArrayToString(u8Array, idx, maxBytesToRead) {
|
||||
var endIdx = idx + maxBytesToRead;
|
||||
var endPtr = idx;
|
||||
while (u8Array[endPtr] && !(endPtr >= endIdx)) ++endPtr;
|
||||
if (endPtr - idx > 16 && u8Array.subarray && UTF8Decoder) {
|
||||
return UTF8Decoder.decode(u8Array.subarray(idx, endPtr))
|
||||
} else {
|
||||
var str = "";
|
||||
while (idx < endPtr) {
|
||||
var u0 = u8Array[idx++];
|
||||
if (!(u0 & 128)) {
|
||||
str += String.fromCharCode(u0);
|
||||
continue
|
||||
}
|
||||
var u1 = u8Array[idx++] & 63;
|
||||
if ((u0 & 224) == 192) {
|
||||
str += String.fromCharCode((u0 & 31) << 6 | u1);
|
||||
continue
|
||||
}
|
||||
var u2 = u8Array[idx++] & 63;
|
||||
if ((u0 & 240) == 224) {
|
||||
u0 = (u0 & 15) << 12 | u1 << 6 | u2
|
||||
} else {
|
||||
u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | u8Array[idx++] & 63
|
||||
}
|
||||
if (u0 < 65536) {
|
||||
str += String.fromCharCode(u0)
|
||||
} else {
|
||||
var ch = u0 - 65536;
|
||||
str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023)
|
||||
}
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function UTF8ToString(ptr, maxBytesToRead) {
|
||||
return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ""
|
||||
}
|
||||
var TOTAL_MEMORY = 16777216,
|
||||
STATIC_BASE = 1024,
|
||||
DYNAMICTOP_PTR = 6016;
|
||||
var wasmMaximumMemory = TOTAL_MEMORY;
|
||||
var wasmMemory = new WebAssembly.Memory({
|
||||
"initial": TOTAL_MEMORY >> 16,
|
||||
"maximum": wasmMaximumMemory >> 16
|
||||
});
|
||||
var buffer = wasmMemory.buffer;
|
||||
var HEAP8 = new Int8Array(buffer);
|
||||
var HEAP16 = new Int16Array(buffer);
|
||||
var HEAP32 = new Int32Array(buffer);
|
||||
var HEAPU8 = new Uint8Array(buffer);
|
||||
var HEAPU16 = new Uint16Array(buffer);
|
||||
var HEAPU32 = new Uint32Array(buffer);
|
||||
var HEAPF32 = new Float32Array(buffer);
|
||||
var HEAPF64 = new Float64Array(buffer);
|
||||
HEAP32[DYNAMICTOP_PTR >> 2] = 5249152;
|
||||
var SYSCALLS = {
|
||||
buffers: [null, [],
|
||||
[]
|
||||
],
|
||||
printChar: (function(stream, curr) {
|
||||
var buffer = SYSCALLS.buffers[stream];
|
||||
if (curr === 0 || curr === 10) {
|
||||
(stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
|
||||
buffer.length = 0
|
||||
} else {
|
||||
buffer.push(curr)
|
||||
}
|
||||
}),
|
||||
varargs: 0,
|
||||
get: (function(varargs) {
|
||||
SYSCALLS.varargs += 4;
|
||||
var ret = HEAP32[SYSCALLS.varargs - 4 >> 2];
|
||||
return ret
|
||||
}),
|
||||
getStr: (function() {
|
||||
var ret = UTF8ToString(SYSCALLS.get());
|
||||
return ret
|
||||
}),
|
||||
get64: (function() {
|
||||
var low = SYSCALLS.get(),
|
||||
high = SYSCALLS.get();
|
||||
return low
|
||||
}),
|
||||
getZero: (function() {
|
||||
SYSCALLS.get()
|
||||
})
|
||||
};
|
||||
|
||||
function ___syscall140(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
var stream = SYSCALLS.getStreamFromFD(),
|
||||
offset_high = SYSCALLS.get(),
|
||||
offset_low = SYSCALLS.get(),
|
||||
result = SYSCALLS.get(),
|
||||
whence = SYSCALLS.get();
|
||||
var offset = offset_low;
|
||||
FS.llseek(stream, offset, whence);
|
||||
HEAP32[result >> 2] = stream.position;
|
||||
if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null;
|
||||
return 0
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno
|
||||
}
|
||||
}
|
||||
|
||||
function ___syscall146(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
var stream = SYSCALLS.get(),
|
||||
iov = SYSCALLS.get(),
|
||||
iovcnt = SYSCALLS.get();
|
||||
var ret = 0;
|
||||
for (var i = 0; i < iovcnt; i++) {
|
||||
var ptr = HEAP32[iov + i * 8 >> 2];
|
||||
var len = HEAP32[iov + (i * 8 + 4) >> 2];
|
||||
for (var j = 0; j < len; j++) {
|
||||
SYSCALLS.printChar(stream, HEAPU8[ptr + j])
|
||||
}
|
||||
ret += len
|
||||
}
|
||||
return ret
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno
|
||||
}
|
||||
}
|
||||
|
||||
function ___syscall54(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
return 0
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno
|
||||
}
|
||||
}
|
||||
|
||||
function ___syscall6(which, varargs) {
|
||||
SYSCALLS.varargs = varargs;
|
||||
try {
|
||||
var stream = SYSCALLS.getStreamFromFD();
|
||||
FS.close(stream);
|
||||
return 0
|
||||
} catch (e) {
|
||||
if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);
|
||||
return -e.errno
|
||||
}
|
||||
}
|
||||
|
||||
function _emscripten_get_now() {
|
||||
abort()
|
||||
}
|
||||
|
||||
function _emscripten_random() {
|
||||
return Math.random()
|
||||
}
|
||||
|
||||
function _emscripten_memcpy_big(dest, src, num) {
|
||||
HEAPU8.set(HEAPU8.subarray(src, src + num), dest)
|
||||
}
|
||||
if (ENVIRONMENT_IS_NODE) {
|
||||
_emscripten_get_now = function _emscripten_get_now_actual() {
|
||||
var t = process["hrtime"]();
|
||||
return t[0] * 1e3 + t[1] / 1e6
|
||||
}
|
||||
} else if (typeof dateNow !== "undefined") {
|
||||
_emscripten_get_now = dateNow
|
||||
} else if (typeof self === "object" && self["performance"] && typeof self["performance"]["now"] === "function") {
|
||||
_emscripten_get_now = (function() {
|
||||
return self["performance"]["now"]()
|
||||
})
|
||||
} else if (typeof performance === "object" && typeof performance["now"] === "function") {
|
||||
_emscripten_get_now = (function() {
|
||||
return performance["now"]()
|
||||
})
|
||||
} else {
|
||||
_emscripten_get_now = Date.now
|
||||
}
|
||||
var asmLibraryArg = {
|
||||
"abort": abort,
|
||||
"___syscall140": ___syscall140,
|
||||
"___syscall146": ___syscall146,
|
||||
"___syscall54": ___syscall54,
|
||||
"___syscall6": ___syscall6,
|
||||
"_emscripten_get_now": _emscripten_get_now,
|
||||
"_emscripten_memcpy_big": _emscripten_memcpy_big,
|
||||
"_emscripten_random": _emscripten_random
|
||||
};
|
||||
|
||||
function run() {
|
||||
var ret = _main()
|
||||
}
|
||||
|
||||
function initRuntime(asm) {
|
||||
asm["__GLOBAL__sub_I_test_global_initializer_cpp"]()
|
||||
}
|
||||
var env = asmLibraryArg;
|
||||
env["memory"] = wasmMemory;
|
||||
env["table"] = new WebAssembly.Table({
|
||||
"initial": 6,
|
||||
"maximum": 6,
|
||||
"element": "anyfunc"
|
||||
});
|
||||
env["__memory_base"] = STATIC_BASE;
|
||||
env["__table_base"] = 0;
|
||||
var imports = {
|
||||
"env": env,
|
||||
"global": {
|
||||
"NaN": NaN,
|
||||
"Infinity": Infinity
|
||||
},
|
||||
"global.Math": Math,
|
||||
"asm2wasm": {
|
||||
"f64-rem": (function(x, y) {
|
||||
return x % y
|
||||
}),
|
||||
"debugger": (function() {
|
||||
debugger
|
||||
})
|
||||
}
|
||||
};
|
||||
var ___errno_location, _llvm_bswap_i32, _main, _memcpy, _memset, dynCall_ii, dynCall_iiii;
|
||||
WebAssembly.instantiate(Module["wasm"], imports).then((function(output) {
|
||||
var asm = output.instance.exports;
|
||||
___errno_location = asm["___errno_location"];
|
||||
_llvm_bswap_i32 = asm["_llvm_bswap_i32"];
|
||||
_main = asm["_main"];
|
||||
_memcpy = asm["_memcpy"];
|
||||
_memset = asm["_memset"];
|
||||
dynCall_ii = asm["dynCall_ii"];
|
||||
dynCall_iiii = asm["dynCall_iiii"];
|
||||
initRuntime(asm);
|
||||
ready()
|
||||
}))
|
||||
|
||||
|
||||
|
||||
// EXTRA_INFO: {"mapping": {"_llvm_bswap_i32": "k", "_emscripten_random": "c", "dynCall_ii": "o", "__GLOBAL__sub_I_test_global_initializer_cpp": "i", "___errno_location": "j", "dynCall_iiii": "p", "___syscall6": "f", "_memset": "n", "_memcpy": "m", "abort": "b", "___syscall146": "a", "_emscripten_memcpy_big": "d", "___syscall54": "g", "___syscall140": "h", "_emscripten_get_now": "e", "_main": "l"}}
|
|
@ -2106,7 +2106,9 @@ int f() {
|
|||
assert 'hello, world!' in run_js('two.js')
|
||||
|
||||
def test_js_optimizer(self):
|
||||
ACORN_PASSES = ['JSDCE', 'AJSDCE', 'applyImportAndExportNameChanges', 'emitDCEGraph', 'applyDCEGraphRemovals']
|
||||
for input, expected, passes in [
|
||||
|
||||
(path_from_root('tests', 'optimizer', 'eliminateDeadGlobals.js'), open(path_from_root('tests', 'optimizer', 'eliminateDeadGlobals-output.js')).read(),
|
||||
['eliminateDeadGlobals']),
|
||||
(path_from_root('tests', 'optimizer', 'test-js-optimizer.js'), open(path_from_root('tests', 'optimizer', 'test-js-optimizer-output.js')).read(),
|
||||
|
@ -2179,30 +2181,32 @@ int f() {
|
|||
['splitMemory']),
|
||||
(path_from_root('tests', 'optimizer', 'JSDCE.js'), open(path_from_root('tests', 'optimizer', 'JSDCE-output.js')).read(),
|
||||
['JSDCE']),
|
||||
(path_from_root('tests', 'optimizer', 'JSDCE-uglifyjsNodeTypes.js'), open(path_from_root('tests', 'optimizer', 'JSDCE-uglifyjsNodeTypes-output.js')).read(),
|
||||
['JSDCE']),
|
||||
(path_from_root('tests', 'optimizer', 'JSDCE-hasOwnProperty.js'), open(path_from_root('tests', 'optimizer', 'JSDCE-hasOwnProperty-output.js')).read(),
|
||||
['JSDCE']),
|
||||
(path_from_root('tests', 'optimizer', 'JSDCE-fors.js'), open(path_from_root('tests', 'optimizer', 'JSDCE-fors-output.js')).read(),
|
||||
['JSDCE']),
|
||||
(path_from_root('tests', 'optimizer', 'AJSDCE.js'), open(path_from_root('tests', 'optimizer', 'AJSDCE-output.js')).read(),
|
||||
['AJSDCE']),
|
||||
(path_from_root('tests', 'optimizer', 'emitDCEGraph.js'), open(path_from_root('tests', 'optimizer', 'emitDCEGraph-output.js')).read(),
|
||||
['emitDCEGraph', 'noEmitAst']),
|
||||
['emitDCEGraph', 'noPrint']),
|
||||
(path_from_root('tests', 'optimizer', 'emitDCEGraph2.js'), open(path_from_root('tests', 'optimizer', 'emitDCEGraph2-output.js')).read(),
|
||||
['emitDCEGraph', 'noEmitAst']),
|
||||
['emitDCEGraph', 'noPrint']),
|
||||
(path_from_root('tests', 'optimizer', 'emitDCEGraph3.js'), open(path_from_root('tests', 'optimizer', 'emitDCEGraph3-output.js')).read(),
|
||||
['emitDCEGraph', 'noEmitAst']),
|
||||
['emitDCEGraph', 'noPrint']),
|
||||
(path_from_root('tests', 'optimizer', 'emitDCEGraph4.js'), open(path_from_root('tests', 'optimizer', 'emitDCEGraph4-output.js')).read(),
|
||||
['emitDCEGraph', 'noEmitAst']),
|
||||
['emitDCEGraph', 'noPrint']),
|
||||
(path_from_root('tests', 'optimizer', 'emitDCEGraph5.js'), open(path_from_root('tests', 'optimizer', 'emitDCEGraph5-output.js')).read(),
|
||||
['emitDCEGraph', 'noEmitAst']),
|
||||
['emitDCEGraph', 'noPrint']),
|
||||
(path_from_root('tests', 'optimizer', 'applyDCEGraphRemovals.js'), open(path_from_root('tests', 'optimizer', 'applyDCEGraphRemovals-output.js')).read(),
|
||||
['applyDCEGraphRemovals']),
|
||||
(path_from_root('tests', 'optimizer', 'applyImportAndExportNameChanges.js'), open(path_from_root('tests', 'optimizer', 'applyImportAndExportNameChanges-output.js')).read(),
|
||||
['applyImportAndExportNameChanges']),
|
||||
(path_from_root('tests', 'optimizer', 'applyImportAndExportNameChanges2.js'), open(path_from_root('tests', 'optimizer', 'applyImportAndExportNameChanges2-output.js')).read(),
|
||||
['applyImportAndExportNameChanges']),
|
||||
(path_from_root('tests', 'optimizer', 'detectSign-modulus-emterpretify.js'), open(path_from_root('tests', 'optimizer', 'detectSign-modulus-emterpretify-output.js')).read(),
|
||||
['noPrintMetadata', 'emterpretify', 'noEmitAst']),
|
||||
(path_from_root('tests', 'optimizer', 'minimal-runtime-emitDCEGraph.js'), open(path_from_root('tests', 'optimizer', 'minimal-runtime-emitDCEGraph-output.js')).read(),
|
||||
['emitDCEGraph', 'noEmitAst']),
|
||||
['emitDCEGraph', 'noPrint']),
|
||||
]:
|
||||
print(input, passes)
|
||||
|
||||
|
@ -2210,9 +2214,15 @@ int f() {
|
|||
expected = [expected]
|
||||
expected = [out.replace('\n\n', '\n').replace('\n\n', '\n') for out in expected]
|
||||
|
||||
# test calling js optimizer
|
||||
print(' js')
|
||||
output = run_process(NODE_JS + [path_from_root('tools', 'js-optimizer.js'), input] + passes, stdin=PIPE, stdout=PIPE).stdout
|
||||
acorn = any([p for p in passes if p in ACORN_PASSES])
|
||||
|
||||
# test calling optimizer
|
||||
if not acorn:
|
||||
print(' js')
|
||||
output = run_process(NODE_JS + [path_from_root('tools', 'js-optimizer.js'), input] + passes, stdin=PIPE, stdout=PIPE).stdout
|
||||
else:
|
||||
print(' acorn')
|
||||
output = run_process(NODE_JS + [path_from_root('tools', 'acorn-optimizer.js'), input] + passes, stdin=PIPE, stdout=PIPE).stdout
|
||||
|
||||
def check_js(js, expected):
|
||||
# print >> sys.stderr, 'chak\n==========================\n', js, '\n===========================\n'
|
||||
|
@ -2345,11 +2355,10 @@ int f() {
|
|||
else:
|
||||
self.assertNotIn(' -g ', finalize)
|
||||
else:
|
||||
opts = '\n'.join([l for l in lines if os.path.sep + 'opt' in l])
|
||||
if expect_debug:
|
||||
self.assertNotIn('strip-debug', opts)
|
||||
self.assertNotIn('strip-debug', err)
|
||||
else:
|
||||
self.assertIn('strip-debug', opts)
|
||||
self.assertIn('strip-debug', err)
|
||||
|
||||
@unittest.skipIf(not scons_path, 'scons not found in PATH')
|
||||
@with_env_modify({'EMSCRIPTEN_ROOT': path_from_root()})
|
||||
|
@ -8345,10 +8354,28 @@ int main() {
|
|||
}
|
||||
''')
|
||||
output = run_process([PYTHON, EMCC, 'src.cpp', '-O2'], stdout=PIPE, stderr=PIPE, check=False)
|
||||
self.assertContained('''
|
||||
# wasm backend output doesn't have spaces in the EM_ASM function bodies
|
||||
self.assertContained(('''
|
||||
var ASM_CONSTS = [function() { var x = !<->5.; }];
|
||||
^
|
||||
''', output.stderr)
|
||||
''', '''
|
||||
var ASM_CONSTS = [function() {var x = !<->5.;}];
|
||||
^
|
||||
'''), output.stderr)
|
||||
|
||||
def test_EM_ASM_ES6(self):
|
||||
# check we show a proper understandable error for JS parse problems
|
||||
create_test_file('src.cpp', r'''
|
||||
#include <emscripten.h>
|
||||
int main() {
|
||||
EM_ASM({
|
||||
var x = (a, b) => 5; // valid ES6!
|
||||
out('hello!');
|
||||
});
|
||||
}
|
||||
''')
|
||||
run_process([PYTHON, EMCC, 'src.cpp', '-O2'])
|
||||
self.assertContained('hello!', run_js('a.out.js'))
|
||||
|
||||
def test_check_sourcemapurl(self):
|
||||
if not self.is_wasm():
|
||||
|
|
|
@ -0,0 +1,800 @@
|
|||
var acorn = require('acorn');
|
||||
var terser = require("terser");
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Setup
|
||||
|
||||
var print = function(x) {
|
||||
process.stdout.write(x + '\n');
|
||||
};
|
||||
|
||||
var printErr = function(x) {
|
||||
process.stderr.write(x + '\n');
|
||||
};
|
||||
|
||||
var read = function(x) {
|
||||
return fs.readFileSync(x).toString();
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
function assert(condition, text) {
|
||||
if (!condition) throw text + ' : ' + new Error().stack;
|
||||
}
|
||||
|
||||
function set(args) {
|
||||
var ret = {};
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
ret[args[i]] = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Visits and walks
|
||||
// (We don't use acorn-walk because it ignores x in 'x = y'.)
|
||||
|
||||
function visitChildren(node, c) {
|
||||
// emptyOut() and temporary ignoring may mark nodes as empty,
|
||||
// while they have properties with children we should ignore.
|
||||
if (node.type === 'EmptyStatement') {
|
||||
return;
|
||||
}
|
||||
function maybeChild(child) {
|
||||
if (child && typeof child === 'object' && typeof child.type === 'string') {
|
||||
c(child);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (var key in node) {
|
||||
var child = node[key];
|
||||
// Check for a child.
|
||||
if (!maybeChild(child)) {
|
||||
// Check for an array of children.
|
||||
if (Array.isArray(child)) {
|
||||
child.forEach(maybeChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple post-order walk, calling properties on an object by node type,
|
||||
// if the type exists.
|
||||
function simpleWalk(node, cs) {
|
||||
visitChildren(node, function(child) {
|
||||
simpleWalk(child, cs);
|
||||
});
|
||||
if (node.type in cs) {
|
||||
cs[node.type](node);
|
||||
}
|
||||
}
|
||||
|
||||
// Full post-order walk, calling a single function for all types.
|
||||
function fullWalk(node, c) {
|
||||
visitChildren(node, function(child) {
|
||||
fullWalk(child, c);
|
||||
});
|
||||
c(node);
|
||||
}
|
||||
|
||||
// Recursive post-order walk, calling properties on an object by node type,
|
||||
// if the type exists, and if so leaving recursion to that function.
|
||||
function recursiveWalk(node, cs) {
|
||||
//print('recw1 ' + JSON.stringify(node));
|
||||
(function c(node) {
|
||||
if (!(node.type in cs)) {
|
||||
visitChildren(node, function(child) {
|
||||
recursiveWalk(child, cs);
|
||||
});
|
||||
} else {
|
||||
cs[node.type](node, c);
|
||||
}
|
||||
})(node);
|
||||
}
|
||||
|
||||
// AST Utilities
|
||||
|
||||
function emptyOut(node) {
|
||||
node.type = 'EmptyStatement';
|
||||
}
|
||||
|
||||
function nullify(node) {
|
||||
node.type = 'Literal';
|
||||
node.value = null;
|
||||
node.raw = 'null';
|
||||
}
|
||||
|
||||
function undefinedify(node) {
|
||||
node.type = 'Literal';
|
||||
node.value = undefined;
|
||||
node.raw = 'undefined';
|
||||
}
|
||||
|
||||
function setLiteralValue(item, value) {
|
||||
item.value = value;
|
||||
item.raw = "'" + value + "'";
|
||||
}
|
||||
|
||||
function isLiteralString(node) {
|
||||
return node.type === 'Literal' &&
|
||||
(node.raw[0] === '"' || node.raw[0] === "'");
|
||||
}
|
||||
|
||||
function dump(node, text) {
|
||||
if (text) print(text);
|
||||
print(JSON.stringify(node, null, ' '));
|
||||
}
|
||||
|
||||
// Mark inner scopes temporarily as empty statements. Returns
|
||||
// a special object that must be used to restore them.
|
||||
function ignoreInnerScopes(node) {
|
||||
var map = new WeakMap();
|
||||
function ignore(node) {
|
||||
map.set(node, node.type);
|
||||
node.type = 'EmptyStatement';
|
||||
}
|
||||
simpleWalk(node, {
|
||||
FunctionDeclaration(node) {
|
||||
ignore(node);
|
||||
},
|
||||
FunctionExpression(node) {
|
||||
ignore(node);
|
||||
},
|
||||
ArrowFunctionExpression(node) {
|
||||
ignore(node);
|
||||
},
|
||||
// TODO: arrow etc.
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
// Mark inner scopes temporarily as empty statements.
|
||||
function restoreInnerScopes(node, map) {
|
||||
fullWalk(node, function(node) {
|
||||
if (map.has(node)) {
|
||||
node.type = map.get(node);
|
||||
map.delete(node);
|
||||
restoreInnerScopes(node, map);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If we empty out a var from
|
||||
// for (var i in x) {}
|
||||
// for (var j = 0;;) {}
|
||||
// then it will be invalid. We saved it on the side;
|
||||
// restore it here.
|
||||
function restoreForVars(node) {
|
||||
var restored = 0;
|
||||
function fix(init) {
|
||||
if (init && init.type === 'EmptyStatement') {
|
||||
assert(init.oldDeclarations);
|
||||
init.type = 'VariableDeclaration';
|
||||
init.declarations = init.oldDeclarations;
|
||||
restored++;
|
||||
}
|
||||
}
|
||||
simpleWalk(node, {
|
||||
ForStatement(node) { fix(node.init) },
|
||||
ForInStatement(node) { fix(node.left) },
|
||||
});
|
||||
return restored;
|
||||
}
|
||||
|
||||
function hasSideEffects(node) {
|
||||
// Conservative analysis.
|
||||
var map = ignoreInnerScopes(node);
|
||||
var has = false;
|
||||
fullWalk(node, function(node) {
|
||||
switch (node.type) {
|
||||
// TODO: go through all the ESTree spec
|
||||
case 'Literal':
|
||||
case 'Identifier':
|
||||
case 'UnaryExpression':
|
||||
case 'BinaryExpresson':
|
||||
case 'ExpressionStatement':
|
||||
case 'UpdateOperator':
|
||||
case 'ConditionalExpression':
|
||||
case 'FunctionDeclaration':
|
||||
case 'FunctionExpression':
|
||||
case 'ArrowFunctionExpression':
|
||||
case 'VariableDeclaration':
|
||||
case 'VariableDeclarator':
|
||||
case 'ObjectExpression':
|
||||
case 'Property':
|
||||
case 'BlockStatement':
|
||||
case 'ArrayExpression':
|
||||
case 'EmptyStatement': {
|
||||
break; // safe
|
||||
}
|
||||
case 'MemberExpression': {
|
||||
// safe if on Math (or other familiar objects, TODO)
|
||||
if (node.object.type !== 'Identifier' ||
|
||||
node.object.name !== 'Math') {
|
||||
//console.error('because member on ' + node.object.name);
|
||||
has = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
has = true;
|
||||
//console.error('because ' + node.type);
|
||||
}
|
||||
}
|
||||
});
|
||||
restoreInnerScopes(node, map);
|
||||
return has;
|
||||
}
|
||||
|
||||
// Passes
|
||||
|
||||
// Removes obviously-unused code. Similar to closure compiler in its rules -
|
||||
// export e.g. by Module['..'] = theThing; , or use it somewhere, otherwise
|
||||
// it goes away.
|
||||
//
|
||||
// Note that this is somewhat conservative, since the ESTree AST does not
|
||||
// have a simple separation between definitions and uses, e.g.
|
||||
// Identifier is used both for the x in function foo(x) {
|
||||
// and for y = x + 1 . That means we need to consider new ES6+ constructs
|
||||
// as they appear (like ArrowFunctionExpression). Instead, we do a conservative
|
||||
// analysis here.
|
||||
|
||||
function JSDCE(ast, multipleIterations) {
|
||||
function iteration() {
|
||||
var removed = 0;
|
||||
var scopes = [{}]; // begin with empty toplevel scope
|
||||
function DUMP() {
|
||||
printErr('vvvvvvvvvvvvvv');
|
||||
for (var i = 0; i < scopes.length; i++) {
|
||||
printErr(i + ' : ' + JSON.stringify(scopes[i]));
|
||||
}
|
||||
printErr('^^^^^^^^^^^^^^');
|
||||
}
|
||||
function ensureData(scope, name) {
|
||||
if (Object.prototype.hasOwnProperty.call(scope, name)) return scope[name];
|
||||
scope[name] = {
|
||||
def: 0,
|
||||
use: 0,
|
||||
param: 0 // true for function params, which cannot be eliminated
|
||||
};
|
||||
return scope[name];
|
||||
}
|
||||
function cleanUp(ast, names) {
|
||||
recursiveWalk(ast, {
|
||||
VariableDeclaration(node, c) {
|
||||
var old = node.declarations;
|
||||
var removedHere = 0;
|
||||
node.declarations = node.declarations.filter(function(node) {
|
||||
var curr = node.id.name
|
||||
var value = node.init;
|
||||
var keep = !(curr in names) || (value && hasSideEffects(value));
|
||||
if (!keep) removedHere = 1;
|
||||
return keep;
|
||||
});
|
||||
removed += removedHere;
|
||||
if (node.declarations.length === 0) {
|
||||
emptyOut(node);
|
||||
// If this is in a for, we may need to restore it.
|
||||
node.oldDeclarations = old;
|
||||
}
|
||||
},
|
||||
FunctionDeclaration(node, c) {
|
||||
if (Object.prototype.hasOwnProperty.call(names, node.id.name)) {
|
||||
removed++;
|
||||
emptyOut(node);
|
||||
return;
|
||||
}
|
||||
// do not recurse into other scopes
|
||||
},
|
||||
// do not recurse into other scopes
|
||||
FunctionExpression() {},
|
||||
ArrowFunctionExpression() {},
|
||||
});
|
||||
removed -= restoreForVars(ast);
|
||||
}
|
||||
|
||||
function handleFunction(node, c, defun) {
|
||||
// defun names matter - function names (the y in var x = function y() {..}) are just for stack traces.
|
||||
if (defun) {
|
||||
ensureData(scopes[scopes.length-1], node.id.name).def = 1;
|
||||
}
|
||||
var scope = {};
|
||||
node.params.forEach(function(param) {
|
||||
var name = param.name;
|
||||
ensureData(scope, name).def = 1;
|
||||
scope[name].param = 1;
|
||||
});
|
||||
scopes.push(scope);
|
||||
c(node.body);
|
||||
// we can ignore self-references, i.e., references to ourselves inside
|
||||
// ourselves, for named defined (defun) functions
|
||||
var ownName = defun ? node.id.name : '';
|
||||
var scope = scopes.pop();
|
||||
var names = {};
|
||||
for (var name in scope) {
|
||||
if (name === ownName) continue;
|
||||
var data = scope[name];
|
||||
if (data.use && !data.def) {
|
||||
// this is used from a higher scope, propagate the use down
|
||||
ensureData(scopes[scopes.length-1], name).use = 1;
|
||||
continue;
|
||||
}
|
||||
if (data.def && !data.use && !data.param) {
|
||||
// this is eliminateable!
|
||||
names[name] = 0;
|
||||
}
|
||||
}
|
||||
cleanUp(node.body, names);
|
||||
}
|
||||
|
||||
recursiveWalk(ast, {
|
||||
VariableDeclarator(node, c) {
|
||||
var name = node.id.name;
|
||||
ensureData(scopes[scopes.length-1], name).def = 1;
|
||||
if (node.init) c(node.init);
|
||||
},
|
||||
ObjectExpression(node, c) {
|
||||
// ignore the property identifiers
|
||||
node.properties.forEach(function(node) {
|
||||
c(node.value);
|
||||
});
|
||||
},
|
||||
FunctionDeclaration(node, c) {
|
||||
handleFunction(node, c, true /* defun */);
|
||||
},
|
||||
FunctionExpression(node, c) {
|
||||
handleFunction(node, c);
|
||||
},
|
||||
ArrowFunctionExpression(node, c) {
|
||||
handleFunction(node, c);
|
||||
},
|
||||
Identifier(node, c) {
|
||||
var name = node.name;
|
||||
ensureData(scopes[scopes.length-1], name).use = 1;
|
||||
},
|
||||
});
|
||||
|
||||
// toplevel
|
||||
var scope = scopes.pop();
|
||||
assert(scopes.length === 0);
|
||||
|
||||
var names = {};
|
||||
for (var name in scope) {
|
||||
var data = scope[name];
|
||||
if (data.def && !data.use) {
|
||||
assert(!data.param); // can't be
|
||||
// this is eliminateable!
|
||||
names[name] = 0;
|
||||
}
|
||||
}
|
||||
cleanUp(ast, names);
|
||||
return removed;
|
||||
}
|
||||
while (iteration() && multipleIterations) { }
|
||||
}
|
||||
|
||||
// Aggressive JSDCE - multiple iterations
|
||||
function AJSDCE(ast) {
|
||||
JSDCE(ast, /* multipleIterations= */ true);
|
||||
}
|
||||
|
||||
function isAsmLibraryArgAssign(node) { // var asmLibraryArg = ..
|
||||
return node.type === 'VariableDeclaration' &&
|
||||
node.declarations.length === 1 &&
|
||||
node.declarations[0].id.name === 'asmLibraryArg' &&
|
||||
node.declarations[0].init &&
|
||||
node.declarations[0].init.type === 'ObjectExpression';
|
||||
}
|
||||
|
||||
function getAsmLibraryArgValue(node) {
|
||||
return node.declarations[0].init;
|
||||
}
|
||||
|
||||
function isAsmUse(node) {
|
||||
return node.type === 'MemberExpression' &&
|
||||
((node.object.type === 'Identifier' && // asm['X']
|
||||
node.object.name === 'asm' &&
|
||||
node.property.type === 'Literal') ||
|
||||
(node.object.type === 'MemberExpression' && // Module['asm']['X']
|
||||
node.object.object.type === 'Identifier' &&
|
||||
node.object.object.name === 'Module' &&
|
||||
node.object.property.type === 'Literal' &&
|
||||
node.object.property.value === 'asm' &&
|
||||
isLiteralString(node.property)));
|
||||
}
|
||||
|
||||
function getAsmOrModuleUseName(node) {
|
||||
return node.property.value;
|
||||
}
|
||||
|
||||
function isModuleUse(node) {
|
||||
return node.type === 'MemberExpression' && // Module['X']
|
||||
node.object.type === 'Identifier' &&
|
||||
node.object.name === 'Module' &&
|
||||
isLiteralString(node.property);
|
||||
}
|
||||
|
||||
function isModuleAsmUse(node) { // Module['asm'][..string..]
|
||||
return node.type === 'MemberExpression' &&
|
||||
node.object.type === 'MemberExpression' &&
|
||||
node.object.object.type === 'Identifier' &&
|
||||
node.object.object.name === 'Module' &&
|
||||
node.object.property.type === 'Literal' &&
|
||||
node.object.property.value === 'asm' &&
|
||||
isLiteralString(node.property);
|
||||
}
|
||||
|
||||
// Apply import/export name changes (after minifying them)
|
||||
function applyImportAndExportNameChanges(ast) {
|
||||
var mapping = extraInfo.mapping;
|
||||
fullWalk(ast, function(node) {
|
||||
if (isAsmLibraryArgAssign(node)) {
|
||||
var assignedObject = getAsmLibraryArgValue(node);
|
||||
assignedObject.properties.forEach(function(item) {
|
||||
if (mapping[item.key.value]) {
|
||||
setLiteralValue(item.key, mapping[item.key.value]);
|
||||
}
|
||||
});
|
||||
} else if (node.type === 'AssignmentExpression') {
|
||||
var target = node.left;
|
||||
var value = node.right;
|
||||
if (isAsmUse(value)) {
|
||||
var name = value.property.value;
|
||||
if (mapping[name]) {
|
||||
setLiteralValue(value.property, mapping[name]);
|
||||
}
|
||||
}
|
||||
} else if (isModuleAsmUse(node)) {
|
||||
var prop = node.property;
|
||||
var name = prop.value;
|
||||
if (mapping[name]) {
|
||||
setLiteralValue(prop, mapping[name]);
|
||||
}
|
||||
} else if (isAsmUse(node)) {
|
||||
var prop = node.property;
|
||||
var name = prop.value;
|
||||
if (mapping[name]) {
|
||||
setLiteralValue(prop, mapping[name]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// A static dyncall is dynCall('vii', ..), which is actually static even
|
||||
// though we call dynCall() - we see the string signature statically.
|
||||
function isStaticDynCall(node) {
|
||||
return node.type === 'CallExpression' &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'dynCall' &&
|
||||
isLiteralString(node.arguments[0])
|
||||
}
|
||||
|
||||
function getStaticDynCallName(node) {
|
||||
return 'dynCall_' + node.arguments[0].value;
|
||||
}
|
||||
|
||||
// a dynamic dyncall is one in which all we know is *some* dynCall may
|
||||
// be called, but not who. This can be either
|
||||
// dynCall(*not a string*, ..)
|
||||
// or, to be conservative,
|
||||
// "dynCall_"
|
||||
// as that prefix means we may be constructing a dynamic dyncall name
|
||||
// (dynCall and embind's requireFunction do this internally).
|
||||
function isDynamicDynCall(node) {
|
||||
return (node.type === 'CallExpression' &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'dynCall' &&
|
||||
!isLiteralString(node.arguments[0])) ||
|
||||
(isLiteralString(node) &&
|
||||
node.value === 'dynCall_');
|
||||
}
|
||||
|
||||
//
|
||||
// Emit the DCE graph, to help optimize the combined JS+wasm.
|
||||
// This finds where JS depends on wasm, and where wasm depends
|
||||
// on JS, and prints that out.
|
||||
//
|
||||
// The analysis here is simplified, and not completely general. It
|
||||
// is enough to optimize the common case of JS library and runtime
|
||||
// functions involved in loops with wasm, but not more complicated
|
||||
// things like JS objects and sub-functions. Specifically we
|
||||
// analyze as follows:
|
||||
//
|
||||
// * We consider (1) the toplevel scope, and (2) the scopes of toplevel defined
|
||||
// functions (defun, not function; i.e., function X() {} where
|
||||
// X can be called later, and not y = function Z() {} where Z is
|
||||
// just a name for stack traces). We also consider the wasm, which
|
||||
// we can see things going to and arriving from.
|
||||
// * Anything used in a defun creates a link in the DCE graph, either
|
||||
// to another defun, or the wasm.
|
||||
// * Anything used in the toplevel scope is rooted, as it is code
|
||||
// we assume will execute. The exceptions are
|
||||
// * when we receive something from wasm; those are "free" and
|
||||
// do not cause rooting. (They will become roots if they are
|
||||
// exported, the metadce logic will handle that.)
|
||||
// * when we send something to wasm; sending a defun causes a
|
||||
// link in the DCE graph.
|
||||
// * Anything not in the toplevel or not in a toplevel defun is
|
||||
// considering rooted. We don't optimize those cases.
|
||||
//
|
||||
// Special handling:
|
||||
//
|
||||
// * dynCall('vii', ..) are dynamic dynCalls, but we analyze them
|
||||
// statically, to preserve the dynCall_vii etc. method they depend on.
|
||||
// Truly dynamic dynCalls (not to a string constant) will not work,
|
||||
// and require the user to export them.
|
||||
// * Truly dynamic dynCalls are assumed to reach any dynCall_*.
|
||||
//
|
||||
// XXX this modifies the input AST. if you want to keep using it,
|
||||
// that should be fixed. Currently the main use case here does
|
||||
// not require that. TODO FIXME
|
||||
//
|
||||
function emitDCEGraph(ast) {
|
||||
// First pass: find the wasm imports and exports, and the toplevel
|
||||
// defuns, and save them on the side, removing them from the AST,
|
||||
// which makes the second pass simpler.
|
||||
//
|
||||
// The imports that wasm receives look like this:
|
||||
//
|
||||
// Module.asmLibraryArg = { "abort": abort, "assert": assert, [..] };
|
||||
//
|
||||
// The exports are trickier, as they have a different form whether or not
|
||||
// async compilation is enabled. It can be either:
|
||||
//
|
||||
// var _malloc = Module["_malloc"] = asm["_malloc"];
|
||||
//
|
||||
// or
|
||||
//
|
||||
// var _malloc = Module["_malloc"] = (function() {
|
||||
// return Module["asm"]["_malloc"].apply(null, arguments);
|
||||
// });
|
||||
//
|
||||
var imports = [];
|
||||
var defuns = [];
|
||||
var dynCallNames = [];
|
||||
var nameToGraphName = {};
|
||||
var modulePropertyToGraphName = {};
|
||||
var exportNameToGraphName = {}; // identical to asm['..'] nameToGraphName
|
||||
var foundAsmLibraryArgAssign = false;
|
||||
var graph = [];
|
||||
|
||||
function saveAsmExport(name, asmName) {
|
||||
// the asmName is what the wasm provides directly; the outside JS
|
||||
// name may be slightly different (extra "_" in wasm backend)
|
||||
var graphName = getGraphName(name, 'export');
|
||||
nameToGraphName[name] = graphName;
|
||||
modulePropertyToGraphName[name] = graphName;
|
||||
exportNameToGraphName[asmName] = graphName;
|
||||
if (/^dynCall_/.test(name)) {
|
||||
dynCallNames.push(graphName);
|
||||
}
|
||||
}
|
||||
|
||||
fullWalk(ast, function(node) {
|
||||
if (isAsmLibraryArgAssign(node)) {
|
||||
var assignedObject = getAsmLibraryArgValue(node);
|
||||
assignedObject.properties.forEach(function(item) {
|
||||
var value = item.value;
|
||||
assert(value.type === 'Identifier');
|
||||
imports.push(value.name); // the name doesn't matter, only the value which is that actual thing we are importing
|
||||
});
|
||||
foundAsmLibraryArgAssign = true;
|
||||
emptyOut(node); // ignore this in the second pass; this does not root
|
||||
} else if (node.type === 'VariableDeclaration') {
|
||||
if (node.declarations.length === 1) {
|
||||
var item = node.declarations[0];
|
||||
var name = item.id.name;
|
||||
var value = item.init;
|
||||
if (value && value.type === 'AssignmentExpression') {
|
||||
var assigned = value.left;
|
||||
if (isModuleUse(assigned) && getAsmOrModuleUseName(assigned) === name) {
|
||||
// this is
|
||||
// var x = Module['x'] = ?
|
||||
// which looks like a wasm export being received. confirm with the asm use
|
||||
var found = 0;
|
||||
var asmName;
|
||||
fullWalk(value.right, function(node) {
|
||||
if (isAsmUse(node)) {
|
||||
found++;
|
||||
asmName = getAsmOrModuleUseName(node);
|
||||
}
|
||||
});
|
||||
// in the wasm backend, the asm name may have one fewer "_" prefixed
|
||||
if (found === 1) {
|
||||
// this is indeed an export
|
||||
// the asmName is what the wasm provides directly; the outside JS
|
||||
// name may be slightly different (extra "_" in wasm backend)
|
||||
saveAsmExport(name, asmName);
|
||||
emptyOut(node); // ignore this in the second pass; this does not root
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.type === 'FunctionDeclaration') {
|
||||
defuns.push(node);
|
||||
var name = node.id.name;
|
||||
nameToGraphName[name] = getGraphName(name, 'defun');
|
||||
emptyOut(node); // ignore this in the second pass; we scan defuns separately
|
||||
}
|
||||
});
|
||||
// must find the info we need
|
||||
assert(foundAsmLibraryArgAssign, 'could not find the assigment to "asmLibraryArg". perhaps --pre-js or --post-js code moved it out of the global scope? (things like that should be done after emcc runs, as they do not need to be run through the optimizer which is the special thing about --pre-js/--post-js code)');
|
||||
// Read exports that were declared in extraInfo
|
||||
if (extraInfo) {
|
||||
for (var e in extraInfo.exports) {
|
||||
var exp = extraInfo.exports[e];
|
||||
saveAsmExport(exp[0], exp[1]);
|
||||
}
|
||||
}
|
||||
// Second pass: everything used in the toplevel scope is rooted;
|
||||
// things used in defun scopes create links
|
||||
function getGraphName(name, what) {
|
||||
return 'emcc$' + what + '$' + name;
|
||||
}
|
||||
var infos = {}; // the graph name of the item => info for it
|
||||
imports.forEach(function(import_) {
|
||||
var name = getGraphName(import_, 'import');
|
||||
var info = infos[name] = {
|
||||
name: name,
|
||||
import: ['env', import_],
|
||||
reaches: {}
|
||||
};
|
||||
if (nameToGraphName.hasOwnProperty(import_)) {
|
||||
info.reaches[nameToGraphName[import_]] = 1;
|
||||
} // otherwise, it's a number, ignore
|
||||
});
|
||||
for (var e in exportNameToGraphName) {
|
||||
var name = exportNameToGraphName[e];
|
||||
infos[name] = {
|
||||
name: name,
|
||||
export: e,
|
||||
reaches: {}
|
||||
};
|
||||
}
|
||||
// a function that handles a node we visit, in either a defun or
|
||||
// the toplevel scope (in which case the second param is not provided)
|
||||
function visitNode(node, defunInfo) {
|
||||
// TODO: scope awareness here. for now we just assume all uses are
|
||||
// from the top scope, which might create more uses than needed
|
||||
var reached;
|
||||
if (node.type === 'Identifier') {
|
||||
var name = node.name;
|
||||
if (nameToGraphName.hasOwnProperty(name)) {
|
||||
reached = nameToGraphName[name];
|
||||
}
|
||||
} else if (isModuleUse(node)) {
|
||||
var name = getAsmOrModuleUseName(node);
|
||||
if (modulePropertyToGraphName.hasOwnProperty(name)) {
|
||||
reached = modulePropertyToGraphName[name];
|
||||
}
|
||||
} else if (isStaticDynCall(node)) {
|
||||
reached = getGraphName(getStaticDynCallName(node), 'export');
|
||||
} else if (isDynamicDynCall(node)) {
|
||||
// this can reach *all* dynCall_* targets, we can't narrow it down
|
||||
reached = dynCallNames;
|
||||
} else if (isAsmUse(node)) {
|
||||
// any remaining asm uses are always rooted in any case
|
||||
var name = getAsmOrModuleUseName(node);
|
||||
if (exportNameToGraphName.hasOwnProperty(name)) {
|
||||
infos[exportNameToGraphName[name]].root = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (reached) {
|
||||
function addReach(reached) {
|
||||
if (defunInfo) {
|
||||
defunInfo.reaches[reached] = 1; // defun reaches it
|
||||
} else {
|
||||
infos[reached].root = true; // in global scope, root it
|
||||
}
|
||||
}
|
||||
if (typeof reached === 'string') {
|
||||
addReach(reached);
|
||||
} else {
|
||||
reached.forEach(addReach);
|
||||
}
|
||||
}
|
||||
}
|
||||
defuns.forEach(function(defun) {
|
||||
var name = getGraphName(defun.id.name, 'defun');
|
||||
var info = infos[name] = {
|
||||
name: name,
|
||||
reaches: {}
|
||||
};
|
||||
fullWalk(defun.body, function(node) {
|
||||
visitNode(node, info);
|
||||
});
|
||||
});
|
||||
fullWalk(ast, function(node) {
|
||||
visitNode(node, null);
|
||||
});
|
||||
// Final work: print out the graph
|
||||
// sort for determinism
|
||||
function sortedNamesFromMap(map) {
|
||||
var names = [];
|
||||
for (var name in map) {
|
||||
names.push(name);
|
||||
}
|
||||
names.sort();
|
||||
return names;
|
||||
}
|
||||
sortedNamesFromMap(infos).forEach(function(name) {
|
||||
var info = infos[name];
|
||||
info.reaches = sortedNamesFromMap(info.reaches);
|
||||
graph.push(info);
|
||||
});
|
||||
print(JSON.stringify(graph, null, ' '));
|
||||
}
|
||||
|
||||
// Apply graph removals from running wasm-metadce
|
||||
function applyDCEGraphRemovals(ast) {
|
||||
var unused = set(extraInfo.unused);
|
||||
|
||||
fullWalk(ast, function(node) {
|
||||
if (isAsmLibraryArgAssign(node)) {
|
||||
var assignedObject = getAsmLibraryArgValue(node);
|
||||
assignedObject.properties = assignedObject.properties.filter(function(item) {
|
||||
var name = item.key.value;
|
||||
var value = item.value;
|
||||
var full = 'emcc$import$' + name;
|
||||
return !((full in unused) && !hasSideEffects(value));
|
||||
});
|
||||
} else if (node.type === 'AssignmentExpression') {
|
||||
// when we assign to a thing we don't need, we can just remove the assign
|
||||
var target = node.left;
|
||||
if (isAsmUse(target) || isModuleUse(target)) {
|
||||
var name = getAsmOrModuleUseName(target);
|
||||
var full = 'emcc$export$' + name;
|
||||
var value = node.right;
|
||||
if ((full in unused) &&
|
||||
(isAsmUse(value) || !hasSideEffects(value))) {
|
||||
undefinedify(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Main
|
||||
|
||||
var arguments = process['argv'].slice(2);;
|
||||
var infile = arguments[0];
|
||||
var passes = arguments.slice(1);
|
||||
|
||||
var input = read(infile);
|
||||
var extraInfoStart = input.lastIndexOf('// EXTRA_INFO:')
|
||||
var extraInfo = null;
|
||||
if (extraInfoStart > 0) {
|
||||
extraInfo = JSON.parse(input.substr(extraInfoStart + 14));
|
||||
}
|
||||
var ast = acorn.parse(input, { ecmaVersion: 6 });
|
||||
|
||||
var minifyWhitespace = false;
|
||||
var noPrint = false;
|
||||
|
||||
var registry = {
|
||||
JSDCE: JSDCE,
|
||||
AJSDCE: AJSDCE,
|
||||
applyImportAndExportNameChanges: applyImportAndExportNameChanges,
|
||||
emitDCEGraph: emitDCEGraph,
|
||||
applyDCEGraphRemovals: applyDCEGraphRemovals,
|
||||
minifyWhitespace: function() { minifyWhitespace = true },
|
||||
noPrint: function() { noPrint = true },
|
||||
dump: function() { dump(ast) },
|
||||
};
|
||||
|
||||
passes.forEach(function(pass) {
|
||||
registry[pass](ast);
|
||||
});
|
||||
|
||||
if (!noPrint) {
|
||||
var terserAst = terser.AST_Node.from_mozilla_ast(ast);
|
||||
var output = terserAst.print_to_string({
|
||||
beautify: !minifyWhitespace,
|
||||
indent_level: minifyWhitespace ? 0 : 1,
|
||||
keep_quoted_props: true, // for closure
|
||||
});
|
||||
print(output);
|
||||
}
|
||||
|
|
@ -7914,121 +7914,6 @@ function eliminateDeadGlobals(ast) {
|
|||
});
|
||||
}
|
||||
|
||||
// Removes obviously-unused code. Similar to closure compiler in its rules -
|
||||
// export e.g. by Module['..'] = theThing; , or use it somewhere, otherwise
|
||||
// it goes away.
|
||||
function JSDCE(ast, multipleIterations) {
|
||||
function iteration() {
|
||||
var removed = false;
|
||||
var scopes = [{}]; // begin with empty toplevel scope
|
||||
function DUMP() {
|
||||
printErr('vvvvvvvvvvvvvv');
|
||||
for (var i = 0; i < scopes.length; i++) {
|
||||
printErr(i + ' : ' + JSON.stringify(scopes[i]));
|
||||
}
|
||||
printErr('^^^^^^^^^^^^^^');
|
||||
}
|
||||
function ensureData(scope, name) {
|
||||
if (Object.prototype.hasOwnProperty.call(scope, name)) return scope[name];
|
||||
scope[name] = {
|
||||
def: 0,
|
||||
use: 0,
|
||||
param: 0 // true for function params, which cannot be eliminated
|
||||
};
|
||||
return scope[name];
|
||||
}
|
||||
function cleanUp(ast, names) {
|
||||
traverse(ast, function(node, type) {
|
||||
if (type === 'defun' && Object.prototype.hasOwnProperty.call(names, node[1])) {
|
||||
removed = true;
|
||||
return emptyNode();
|
||||
}
|
||||
if (type === 'defun' || type === 'function') return null; // do not enter other scopes
|
||||
if (type === 'var') {
|
||||
node[1] = node[1].filter(function(varItem, j) {
|
||||
var curr = varItem[0];
|
||||
var value = varItem[1];
|
||||
var keep = !(curr in names) || (value && hasSideEffects(value));
|
||||
if (!keep) removed = true;
|
||||
return keep;
|
||||
});
|
||||
if (node[1].length === 0) return emptyNode();
|
||||
}
|
||||
});
|
||||
return ast;
|
||||
}
|
||||
traverse(ast, function(node, type) {
|
||||
if (type === 'var') {
|
||||
node[1].forEach(function(varItem, j) {
|
||||
var name = varItem[0];
|
||||
ensureData(scopes[scopes.length-1], name).def = 1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === 'object') {
|
||||
return;
|
||||
}
|
||||
if (type === 'defun' || type === 'function') {
|
||||
// defun names matter - function names (the y in var x = function y() {..}) are just for stack traces.
|
||||
if (type === 'defun') ensureData(scopes[scopes.length-1], node[1]).def = 1;
|
||||
var scope = {};
|
||||
node[2].forEach(function(param) {
|
||||
ensureData(scope, param).def = 1;
|
||||
scope[param].param = 1;
|
||||
});
|
||||
scopes.push(scope);
|
||||
return;
|
||||
}
|
||||
if (type === 'name') {
|
||||
ensureData(scopes[scopes.length-1], node[1]).use = 1;
|
||||
}
|
||||
}, function(node, type) {
|
||||
if (type === 'defun' || type === 'function') {
|
||||
// we can ignore self-references, i.e., references to ourselves inside
|
||||
// ourselves, for named defined (defun) functions
|
||||
var ownName = type === 'defun' ? node[1] : '';
|
||||
var scope = scopes.pop();
|
||||
var names = set();
|
||||
for (name in scope) {
|
||||
if (name === ownName) continue;
|
||||
var data = scope[name];
|
||||
if (data.use && !data.def) {
|
||||
// this is used from a higher scope, propagate the use down
|
||||
ensureData(scopes[scopes.length-1], name).use = 1;
|
||||
continue;
|
||||
}
|
||||
if (data.def && !data.use && !data.param) {
|
||||
// this is eliminateable!
|
||||
names[name] = 0;
|
||||
}
|
||||
}
|
||||
cleanUp(node[3], names);
|
||||
}
|
||||
});
|
||||
// toplevel
|
||||
var scope = scopes.pop();
|
||||
assert(scopes.length === 0);
|
||||
|
||||
var names = set();
|
||||
for (var name in scope) {
|
||||
var data = scope[name];
|
||||
if (data.def && !data.use) {
|
||||
assert(!data.param); // can't be
|
||||
// this is eliminateable!
|
||||
names[name] = 0;
|
||||
}
|
||||
}
|
||||
cleanUp(ast, names);
|
||||
return removed;
|
||||
}
|
||||
while (iteration() && multipleIterations) { }
|
||||
}
|
||||
|
||||
// Aggressive JSDCE - multiple iterations
|
||||
function AJSDCE(ast) {
|
||||
JSDCE(ast, /* multipleIterations= */ true);
|
||||
}
|
||||
|
||||
function isAsmLibraryArgAssign(node) {
|
||||
return node[0] === 'var' && node[1][0] && node[1][0][0] == 'asmLibraryArg';
|
||||
}
|
||||
|
@ -8086,330 +7971,6 @@ function isDynamicDynCall(node) {
|
|||
(node[0] === 'string' && node[1] === 'dynCall_');
|
||||
}
|
||||
|
||||
//
|
||||
// Emit the DCE graph, to help optimize the combined JS+wasm.
|
||||
// This finds where JS depends on wasm, and where wasm depends
|
||||
// on JS, and prints that out.
|
||||
//
|
||||
// The analysis here is simplified, and not completely general. It
|
||||
// is enough to optimize the common case of JS library and runtime
|
||||
// functions involved in loops with wasm, but not more complicated
|
||||
// things like JS objects and sub-functions. Specifically we
|
||||
// analyze as follows:
|
||||
//
|
||||
// * We consider (1) the toplevel scope, and (2) the scopes of toplevel defined
|
||||
// functions (defun, not function; i.e., function X() {} where
|
||||
// X can be called later, and not y = function Z() {} where Z is
|
||||
// just a name for stack traces). We also consider the wasm, which
|
||||
// we can see things going to and arriving from.
|
||||
// * Anything used in a defun creates a link in the DCE graph, either
|
||||
// to another defun, or the wasm.
|
||||
// * Anything used in the toplevel scope is rooted, as it is code
|
||||
// we assume will execute. The exceptions are
|
||||
// * when we receive something from wasm; those are "free" and
|
||||
// do not cause rooting. (They will become roots if they are
|
||||
// exported, the metadce logic will handle that.)
|
||||
// * when we send something to wasm; sending a defun causes a
|
||||
// link in the DCE graph.
|
||||
// * Anything not in the toplevel or not in a toplevel defun is
|
||||
// considering rooted. We don't optimize those cases.
|
||||
//
|
||||
// Special handling:
|
||||
//
|
||||
// * dynCall('vii', ..) are dynamic dynCalls, but we analyze them
|
||||
// statically, to preserve the dynCall_vii etc. method they depend on.
|
||||
// Truly dynamic dynCalls (not to a string constant) will not work,
|
||||
// and require the user to export them.
|
||||
// * Truly dynamic dynCalls are assumed to reach any dynCall_*.
|
||||
//
|
||||
// XXX this modifies the input AST. if you want to keep using it,
|
||||
// that should be fixed. Currently the main use case here does
|
||||
// not require that. TODO FIXME
|
||||
//
|
||||
function emitDCEGraph(ast) {
|
||||
// First pass: find the wasm imports and exports, and the toplevel
|
||||
// defuns, and save them on the side, removing them from the AST,
|
||||
// which makes the second pass simpler.
|
||||
//
|
||||
// The imports that wasm receives look like this:
|
||||
//
|
||||
// var asmLibraryArg = { "abort": abort, "assert": assert, [..] };
|
||||
//
|
||||
// The exports are trickier, as they have a different form whether or not
|
||||
// async compilation is enabled. It can be either:
|
||||
//
|
||||
// var _malloc = Module["_malloc"] = asm["_malloc"];
|
||||
//
|
||||
// or
|
||||
//
|
||||
// var _malloc = Module["_malloc"] = (function() {
|
||||
// return Module["asm"]["_malloc"].apply(null, arguments);
|
||||
// });
|
||||
//
|
||||
var imports = [];
|
||||
var defuns = [];
|
||||
var dynCallNames = [];
|
||||
var nameToGraphName = {};
|
||||
var modulePropertyToGraphName = {};
|
||||
var exportNameToGraphName = {}; // identical to asm['..'] nameToGraphName
|
||||
var foundAsmLibraryArgAssign = false;
|
||||
var graph = [];
|
||||
|
||||
function saveAsmExport(name, asmName) {
|
||||
var graphName = getGraphName(name, 'export');
|
||||
nameToGraphName[name] = graphName;
|
||||
modulePropertyToGraphName[name] = graphName;
|
||||
exportNameToGraphName[asmName] = graphName;
|
||||
if (/^dynCall_/.test(name) && dynCallNames.indexOf(name) < 0) {
|
||||
dynCallNames.push(graphName);
|
||||
}
|
||||
}
|
||||
|
||||
traverse(ast, function(node, type) {
|
||||
if (isAsmLibraryArgAssign(node)) {
|
||||
var items = asmLibraryArgs(node)[1];
|
||||
items.forEach(function(item) {
|
||||
imports.push(item[1][1]); // the name doesn't matter, only the value which is that actual thing we are importing
|
||||
});
|
||||
foundAsmLibraryArgAssign = true;
|
||||
return emptyNode(); // ignore this in the second pass; this does not root
|
||||
} else if (type === 'var') {
|
||||
if (node[1] && node[1].length === 1) {
|
||||
var item = node[1][0];
|
||||
var name = item[0];
|
||||
var value = item[1];
|
||||
if (Array.isArray(value) && value[0] === 'assign') {
|
||||
var assigned = value[2];
|
||||
if (isModuleUse(assigned) && getModuleUseName(assigned) === name) {
|
||||
// this is
|
||||
// var x = Module['x'] = ?
|
||||
// which looks like a wasm export being received. confirm with the asm use
|
||||
var found = 0;
|
||||
var asmName;
|
||||
traverse(value[3], function(node, type) {
|
||||
if (isAsmUse(node)) {
|
||||
found++;
|
||||
asmName = getAsmUseName(node);
|
||||
}
|
||||
});
|
||||
// in the wasm backend, the asm name may have one fewer "_" prefixed
|
||||
if (found === 1) {
|
||||
// this is indeed an export
|
||||
// the asmName is what the wasm provides directly; the outside JS
|
||||
// name may be slightly different (extra "_" in wasm backend)
|
||||
saveAsmExport(name, asmName);
|
||||
return emptyNode(); // ignore this in the second pass; this does not root
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type === 'defun') {
|
||||
defuns.push(node);
|
||||
var name = node[1];
|
||||
nameToGraphName[name] = getGraphName(name, 'defun');
|
||||
return emptyNode(); // ignore this in the second pass; we scan defuns separately
|
||||
} else if (type === 'function') {
|
||||
return null; // don't look inside
|
||||
}
|
||||
});
|
||||
// must find the info we need
|
||||
assert(foundAsmLibraryArgAssign, 'could not find the assigment to "asmLibraryArg". perhaps --pre-js or --post-js code moved it out of the global scope? (things like that should be done after emcc runs, as they do not need to be run through the optimizer which is the special thing about --pre-js/--post-js code)');
|
||||
|
||||
// Read exports that were declared in extraInfo:
|
||||
if (extraInfo) {
|
||||
for(var e in extraInfo.exports) {
|
||||
var exp = extraInfo.exports[e];
|
||||
saveAsmExport(exp[0], exp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: everything used in the toplevel scope is rooted;
|
||||
// things used in defun scopes create links
|
||||
function getGraphName(name, what) {
|
||||
return 'emcc$' + what + '$' + name;
|
||||
}
|
||||
var infos = {}; // the graph name of the item => info for it
|
||||
imports.forEach(function(import_) {
|
||||
var name = getGraphName(import_, 'import');
|
||||
var info = infos[name] = {
|
||||
name: name,
|
||||
import: ['env', import_],
|
||||
reaches: {}
|
||||
};
|
||||
if (nameToGraphName.hasOwnProperty(import_)) {
|
||||
info.reaches[nameToGraphName[import_]] = 1;
|
||||
} // otherwise, it's a number, ignore
|
||||
});
|
||||
for (var e in exportNameToGraphName) {
|
||||
var name = exportNameToGraphName[e];
|
||||
infos[name] = {
|
||||
name: name,
|
||||
export: e,
|
||||
reaches: {}
|
||||
};
|
||||
}
|
||||
// a function that handles a node we visit, in either a defun or
|
||||
// the toplevel scope (in which case the second param is not provided)
|
||||
function visitNode(node, defunInfo) {
|
||||
// TODO: scope awareness here. for now we just assume all uses are
|
||||
// from the top scope, which might create more uses than needed
|
||||
var reached;
|
||||
if (node[0] === 'name') {
|
||||
var name = node[1];
|
||||
if (nameToGraphName.hasOwnProperty(name)) {
|
||||
reached = nameToGraphName[name];
|
||||
}
|
||||
} else if (isModuleUse(node)) {
|
||||
var name = getModuleUseName(node);
|
||||
if (modulePropertyToGraphName.hasOwnProperty(name)) {
|
||||
reached = modulePropertyToGraphName[name];
|
||||
}
|
||||
} else if (isStaticDynCall(node)) {
|
||||
reached = getGraphName(getStaticDynCallName(node), 'export');
|
||||
} else if (isDynamicDynCall(node)) {
|
||||
// this can reach *all* dynCall_* targets, we can't narrow it down
|
||||
reached = dynCallNames;
|
||||
} else if (isAsmUse(node)) {
|
||||
// any remaining asm uses are always rooted in any case
|
||||
var name = getAsmUseName(node);
|
||||
if (exportNameToGraphName.hasOwnProperty(name)) {
|
||||
infos[exportNameToGraphName[name]].root = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (reached) {
|
||||
function addReach(reached) {
|
||||
if (defunInfo) {
|
||||
defunInfo.reaches[reached] = 1; // defun reaches it
|
||||
} else {
|
||||
infos[reached].root = true; // in global scope, root it
|
||||
}
|
||||
}
|
||||
if (typeof reached === 'string') {
|
||||
addReach(reached);
|
||||
} else {
|
||||
reached.forEach(addReach);
|
||||
}
|
||||
}
|
||||
}
|
||||
defuns.forEach(function(defun) {
|
||||
var name = getGraphName(defun[1], 'defun');
|
||||
var info = infos[name] = {
|
||||
name: name,
|
||||
reaches: {}
|
||||
};
|
||||
traverse(defun[3], function(node, type) {
|
||||
visitNode(node, info);
|
||||
});
|
||||
});
|
||||
traverse(ast, function(node, type) {
|
||||
visitNode(node, null);
|
||||
});
|
||||
// Final work: print out the graph
|
||||
// sort for determinism
|
||||
function sortedNamesFromMap(map) {
|
||||
var names = [];
|
||||
for (var name in map) {
|
||||
names.push(name);
|
||||
}
|
||||
names.sort();
|
||||
return names;
|
||||
}
|
||||
sortedNamesFromMap(infos).forEach(function(name) {
|
||||
var info = infos[name];
|
||||
info.reaches = sortedNamesFromMap(info.reaches);
|
||||
graph.push(info);
|
||||
});
|
||||
print(JSON.stringify(graph, null, ' '));
|
||||
}
|
||||
|
||||
// Apply graph removals from running wasm-metadce
|
||||
function applyDCEGraphRemovals(ast) {
|
||||
var unused = set(extraInfo.unused);
|
||||
|
||||
traverse(ast, function(node, type) {
|
||||
if (isAsmLibraryArgAssign(node)) {
|
||||
var args = asmLibraryArgs(node);
|
||||
args[1] = args[1].filter(function(item) {
|
||||
var name = item[0];
|
||||
var value = item[1];
|
||||
var full = 'emcc$import$' + name;
|
||||
return !((full in unused) && !hasSideEffects(value));
|
||||
});
|
||||
} else if (type === 'assign') {
|
||||
// when we assign to a thing we don't need, we can just remove the assign
|
||||
var target = node[2];
|
||||
if (isAsmUse(target) || isModuleUse(target)) {
|
||||
var name = target[2][1];
|
||||
var full = 'emcc$export$' + name;
|
||||
var value = node[3];
|
||||
if ((full in unused) && !hasSideEffects(value)) {
|
||||
return ['name', 'undefined'];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply import/export name changes (after minifying them)
|
||||
function applyImportAndExportNameChanges(ast) {
|
||||
var mapping = extraInfo.mapping;
|
||||
traverse(ast, function(node, type) {
|
||||
if (isAsmLibraryArgAssign(node)) {
|
||||
var args = asmLibraryArgs(node);
|
||||
args[1] = args[1].map(function(item) {
|
||||
var name = item[0];
|
||||
var value = item[1];
|
||||
if (mapping[name]) {
|
||||
var ret = [mapping[name], value];
|
||||
// Uglify uses this property to tell it to emit
|
||||
// { "quotedname": .. }
|
||||
// as opposed to
|
||||
// { quotedname: }
|
||||
// We need quoting for closure compiler to work
|
||||
// TODO: disable otherwise
|
||||
ret.quoted = true;
|
||||
return ret;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else if (type === 'var') { // var foo = asm["foo"];
|
||||
var children = node[1];
|
||||
var target = children[0][0];
|
||||
var value = children[0][1];
|
||||
if (value && isAsmUse(value)) {
|
||||
var name = value[2][1];
|
||||
if (mapping[name]) {
|
||||
value[2][1] = mapping[name];
|
||||
}
|
||||
}
|
||||
} else if (type === 'assign') { // foo = asm["foo"];
|
||||
var target = node[2];
|
||||
var value = node[3];
|
||||
if (isAsmUse(value)) {
|
||||
var name = value[2][1];
|
||||
if (mapping[name]) {
|
||||
value[2][1] = mapping[name];
|
||||
}
|
||||
}
|
||||
} else if (type === 'call') { // asm["foo"]();
|
||||
var target = node[1];
|
||||
if (isAsmUse(target)) {
|
||||
var name = target[2][1];
|
||||
if (mapping[name]) {
|
||||
target[2][1] = mapping[name];
|
||||
}
|
||||
}
|
||||
} else if (isModuleAsmUse(node)) { // Module["asm"]["foo"]
|
||||
var prop = node[2];
|
||||
var name = prop[1];
|
||||
if (mapping[name]) {
|
||||
prop[1] = mapping[name];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeFuncs(ast) {
|
||||
assert(ast[0] === 'toplevel');
|
||||
var keep = set(extraInfo.keep);
|
||||
|
@ -8457,11 +8018,6 @@ var passes = {
|
|||
findReachable: findReachable,
|
||||
dumpCallGraph: dumpCallGraph,
|
||||
asmLastOpts: asmLastOpts,
|
||||
JSDCE: JSDCE,
|
||||
AJSDCE: AJSDCE,
|
||||
emitDCEGraph: emitDCEGraph,
|
||||
applyDCEGraphRemovals: applyDCEGraphRemovals,
|
||||
applyImportAndExportNameChanges: applyImportAndExportNameChanges,
|
||||
removeFuncs: removeFuncs,
|
||||
noop: function() {},
|
||||
|
||||
|
|
|
@ -499,12 +499,11 @@ EMSCRIPTEN_FUNCS();
|
|||
temp_files.note(cld)
|
||||
elif cleanup:
|
||||
if DEBUG: print('running cleanup on shell code', file=sys.stderr)
|
||||
next = cld + '.cl.js'
|
||||
temp_files.note(next)
|
||||
proc = subprocess.Popen(js_engine + [JS_OPTIMIZER, cld, 'noPrintMetadata', 'JSDCE'] + (['minifyWhitespace'] if 'minifyWhitespace' in passes else []), stdout=open(next, 'w'))
|
||||
proc.communicate()
|
||||
assert proc.returncode == 0
|
||||
cld = next
|
||||
passes = ['JSDCE']
|
||||
if 'minifyWhitespace' in passes:
|
||||
passes.append('minifyWhitespace')
|
||||
cld = shared.Building.acorn_optimizer(cld, passes)
|
||||
temp_files.note(cld)
|
||||
coutput = open(cld).read()
|
||||
|
||||
coutput = coutput.replace('wakaUnknownBefore();', start_asm)
|
||||
|
|
|
@ -0,0 +1,462 @@
|
|||
## 6.0.5 (2019-01-02)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix TypeScript type for `Parser.extend` and add `allowAwaitOutsideFunction` to options type.
|
||||
|
||||
Don't treat `let` as a keyword when the next token is `{` on the next line.
|
||||
|
||||
Fix bug that broke checking for parentheses around an object pattern in a destructuring assignment when `preserveParens` was on.
|
||||
|
||||
## 6.0.4 (2018-11-05)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Further improvements to tokenizing regular expressions in corner cases.
|
||||
|
||||
## 6.0.3 (2018-11-04)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix bug in tokenizing an expression-less return followed by a function followed by a regular expression.
|
||||
|
||||
Remove stray symlink in the package tarball.
|
||||
|
||||
## 6.0.2 (2018-09-26)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix bug where default expressions could fail to parse inside an object destructuring assignment expression.
|
||||
|
||||
## 6.0.1 (2018-09-14)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix wrong value in `version` export.
|
||||
|
||||
## 6.0.0 (2018-09-14)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Better handle variable-redefinition checks for catch bindings and functions directly under if statements.
|
||||
|
||||
Forbid `new.target` in top-level arrow functions.
|
||||
|
||||
Fix issue with parsing a regexp after `yield` in some contexts.
|
||||
|
||||
### New features
|
||||
|
||||
The package now comes with TypeScript definitions.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
The default value of the `ecmaVersion` option is now 9 (2018).
|
||||
|
||||
Plugins work differently, and will have to be rewritten to work with this version.
|
||||
|
||||
The loose parser and walker have been moved into separate packages (`acorn-loose` and `acorn-walk`).
|
||||
|
||||
## 5.7.3 (2018-09-10)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix failure to tokenize regexps after expressions like `x.of`.
|
||||
|
||||
Better error message for unterminated template literals.
|
||||
|
||||
## 5.7.2 (2018-08-24)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Properly handle `allowAwaitOutsideFunction` in for statements.
|
||||
|
||||
Treat function declarations at the top level of modules like let bindings.
|
||||
|
||||
Don't allow async function declarations as the only statement under a label.
|
||||
|
||||
## 5.7.0 (2018-06-15)
|
||||
|
||||
### New features
|
||||
|
||||
Upgraded to Unicode 11.
|
||||
|
||||
## 5.6.0 (2018-05-31)
|
||||
|
||||
### New features
|
||||
|
||||
Allow U+2028 and U+2029 in string when ECMAVersion >= 10.
|
||||
|
||||
Allow binding-less catch statements when ECMAVersion >= 10.
|
||||
|
||||
Add `allowAwaitOutsideFunction` option for parsing top-level `await`.
|
||||
|
||||
## 5.5.3 (2018-03-08)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
A _second_ republish of the code in 5.5.1, this time with yarn, to hopefully get valid timestamps.
|
||||
|
||||
## 5.5.2 (2018-03-08)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
A republish of the code in 5.5.1 in an attempt to solve an issue with the file timestamps in the npm package being 0.
|
||||
|
||||
## 5.5.1 (2018-03-06)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix misleading error message for octal escapes in template strings.
|
||||
|
||||
## 5.5.0 (2018-02-27)
|
||||
|
||||
### New features
|
||||
|
||||
The identifier character categorization is now based on Unicode version 10.
|
||||
|
||||
Acorn will now validate the content of regular expressions, including new ES9 features.
|
||||
|
||||
## 5.4.0 (2018-02-01)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Disallow duplicate or escaped flags on regular expressions.
|
||||
|
||||
Disallow octal escapes in strings in strict mode.
|
||||
|
||||
### New features
|
||||
|
||||
Add support for async iteration.
|
||||
|
||||
Add support for object spread and rest.
|
||||
|
||||
## 5.3.0 (2017-12-28)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix parsing of floating point literals with leading zeroes in loose mode.
|
||||
|
||||
Allow duplicate property names in object patterns.
|
||||
|
||||
Don't allow static class methods named `prototype`.
|
||||
|
||||
Disallow async functions directly under `if` or `else`.
|
||||
|
||||
Parse right-hand-side of `for`/`of` as an assignment expression.
|
||||
|
||||
Stricter parsing of `for`/`in`.
|
||||
|
||||
Don't allow unicode escapes in contextual keywords.
|
||||
|
||||
### New features
|
||||
|
||||
Parsing class members was factored into smaller methods to allow plugins to hook into it.
|
||||
|
||||
## 5.2.1 (2017-10-30)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix a token context corruption bug.
|
||||
|
||||
## 5.2.0 (2017-10-30)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix token context tracking for `class` and `function` in property-name position.
|
||||
|
||||
Make sure `%*` isn't parsed as a valid operator.
|
||||
|
||||
Allow shorthand properties `get` and `set` to be followed by default values.
|
||||
|
||||
Disallow `super` when not in callee or object position.
|
||||
|
||||
### New features
|
||||
|
||||
Support [`directive` property](https://github.com/estree/estree/compare/b3de58c9997504d6fba04b72f76e6dd1619ee4eb...1da8e603237144f44710360f8feb7a9977e905e0) on directive expression statements.
|
||||
|
||||
## 5.1.2 (2017-09-04)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Disable parsing of legacy HTML-style comments in modules.
|
||||
|
||||
Fix parsing of async methods whose names are keywords.
|
||||
|
||||
## 5.1.1 (2017-07-06)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix problem with disambiguating regexp and division after a class.
|
||||
|
||||
## 5.1.0 (2017-07-05)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix tokenizing of regexps in an object-desctructuring `for`/`of` loop and after `yield`.
|
||||
|
||||
Parse zero-prefixed numbers with non-octal digits as decimal.
|
||||
|
||||
Allow object/array patterns in rest parameters.
|
||||
|
||||
Don't error when `yield` is used as a property name.
|
||||
|
||||
Allow `async` as a shorthand object property.
|
||||
|
||||
### New features
|
||||
|
||||
Implement the [template literal revision proposal](https://github.com/tc39/proposal-template-literal-revision) for ES9.
|
||||
|
||||
## 5.0.3 (2017-04-01)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix spurious duplicate variable definition errors for named functions.
|
||||
|
||||
## 5.0.2 (2017-03-30)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
A binary operator after a parenthesized arrow expression is no longer incorrectly treated as an error.
|
||||
|
||||
## 5.0.0 (2017-03-28)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Raise an error for duplicated lexical bindings.
|
||||
|
||||
Fix spurious error when an assignement expression occurred after a spread expression.
|
||||
|
||||
Accept regular expressions after `of` (in `for`/`of`), `yield` (in a generator), and braced arrow functions.
|
||||
|
||||
Allow labels in front or `var` declarations, even in strict mode.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
Parse declarations following `export default` as declaration nodes, not expressions. This means that class and function declarations nodes can now have `null` as their `id`.
|
||||
|
||||
## 4.0.11 (2017-02-07)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Allow all forms of member expressions to be parenthesized as lvalue.
|
||||
|
||||
## 4.0.10 (2017-02-07)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Don't expect semicolons after default-exported functions or classes, even when they are expressions.
|
||||
|
||||
Check for use of `'use strict'` directives in non-simple parameter functions, even when already in strict mode.
|
||||
|
||||
## 4.0.9 (2017-02-06)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix incorrect error raised for parenthesized simple assignment targets, so that `(x) = 1` parses again.
|
||||
|
||||
## 4.0.8 (2017-02-03)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Solve spurious parenthesized pattern errors by temporarily erring on the side of accepting programs that our delayed errors don't handle correctly yet.
|
||||
|
||||
## 4.0.7 (2017-02-02)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Accept invalidly rejected code like `(x).y = 2` again.
|
||||
|
||||
Don't raise an error when a function _inside_ strict code has a non-simple parameter list.
|
||||
|
||||
## 4.0.6 (2017-02-02)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix exponential behavior (manifesting itself as a complete hang for even relatively small source files) introduced by the new 'use strict' check.
|
||||
|
||||
## 4.0.5 (2017-02-02)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Disallow parenthesized pattern expressions.
|
||||
|
||||
Allow keywords as export names.
|
||||
|
||||
Don't allow the `async` keyword to be parenthesized.
|
||||
|
||||
Properly raise an error when a keyword contains a character escape.
|
||||
|
||||
Allow `"use strict"` to appear after other string literal expressions.
|
||||
|
||||
Disallow labeled declarations.
|
||||
|
||||
## 4.0.4 (2016-12-19)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix crash when `export` was followed by a keyword that can't be
|
||||
exported.
|
||||
|
||||
## 4.0.3 (2016-08-16)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Allow regular function declarations inside single-statement `if` branches in loose mode. Forbid them entirely in strict mode.
|
||||
|
||||
Properly parse properties named `async` in ES2017 mode.
|
||||
|
||||
Fix bug where reserved words were broken in ES2017 mode.
|
||||
|
||||
## 4.0.2 (2016-08-11)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Don't ignore period or 'e' characters after octal numbers.
|
||||
|
||||
Fix broken parsing for call expressions in default parameter values of arrow functions.
|
||||
|
||||
## 4.0.1 (2016-08-08)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix false positives in duplicated export name errors.
|
||||
|
||||
## 4.0.0 (2016-08-07)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
The default `ecmaVersion` option value is now 7.
|
||||
|
||||
A number of internal method signatures changed, so plugins might need to be updated.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
The parser now raises errors on duplicated export names.
|
||||
|
||||
`arguments` and `eval` can now be used in shorthand properties.
|
||||
|
||||
Duplicate parameter names in non-simple argument lists now always produce an error.
|
||||
|
||||
### New features
|
||||
|
||||
The `ecmaVersion` option now also accepts year-style version numbers
|
||||
(2015, etc).
|
||||
|
||||
Support for `async`/`await` syntax when `ecmaVersion` is >= 8.
|
||||
|
||||
Support for trailing commas in call expressions when `ecmaVersion` is >= 8.
|
||||
|
||||
## 3.3.0 (2016-07-25)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix bug in tokenizing of regexp operator after a function declaration.
|
||||
|
||||
Fix parser crash when parsing an array pattern with a hole.
|
||||
|
||||
### New features
|
||||
|
||||
Implement check against complex argument lists in functions that enable strict mode in ES7.
|
||||
|
||||
## 3.2.0 (2016-06-07)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Improve handling of lack of unicode regexp support in host
|
||||
environment.
|
||||
|
||||
Properly reject shorthand properties whose name is a keyword.
|
||||
|
||||
### New features
|
||||
|
||||
Visitors created with `visit.make` now have their base as _prototype_, rather than copying properties into a fresh object.
|
||||
|
||||
## 3.1.0 (2016-04-18)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Properly tokenize the division operator directly after a function expression.
|
||||
|
||||
Allow trailing comma in destructuring arrays.
|
||||
|
||||
## 3.0.4 (2016-02-25)
|
||||
|
||||
### Fixes
|
||||
|
||||
Allow update expressions as left-hand-side of the ES7 exponential operator.
|
||||
|
||||
## 3.0.2 (2016-02-10)
|
||||
|
||||
### Fixes
|
||||
|
||||
Fix bug that accidentally made `undefined` a reserved word when parsing ES7.
|
||||
|
||||
## 3.0.0 (2016-02-10)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
The default value of the `ecmaVersion` option is now 6 (used to be 5).
|
||||
|
||||
Support for comprehension syntax (which was dropped from the draft spec) has been removed.
|
||||
|
||||
### Fixes
|
||||
|
||||
`let` and `yield` are now “contextual keywords”, meaning you can mostly use them as identifiers in ES5 non-strict code.
|
||||
|
||||
A parenthesized class or function expression after `export default` is now parsed correctly.
|
||||
|
||||
### New features
|
||||
|
||||
When `ecmaVersion` is set to 7, Acorn will parse the exponentiation operator (`**`).
|
||||
|
||||
The identifier character ranges are now based on Unicode 8.0.0.
|
||||
|
||||
Plugins can now override the `raiseRecoverable` method to override the way non-critical errors are handled.
|
||||
|
||||
## 2.7.0 (2016-01-04)
|
||||
|
||||
### Fixes
|
||||
|
||||
Stop allowing rest parameters in setters.
|
||||
|
||||
Disallow `y` rexexp flag in ES5.
|
||||
|
||||
Disallow `\00` and `\000` escapes in strict mode.
|
||||
|
||||
Raise an error when an import name is a reserved word.
|
||||
|
||||
## 2.6.2 (2015-11-10)
|
||||
|
||||
### Fixes
|
||||
|
||||
Don't crash when no options object is passed.
|
||||
|
||||
## 2.6.0 (2015-11-09)
|
||||
|
||||
### Fixes
|
||||
|
||||
Add `await` as a reserved word in module sources.
|
||||
|
||||
Disallow `yield` in a parameter default value for a generator.
|
||||
|
||||
Forbid using a comma after a rest pattern in an array destructuring.
|
||||
|
||||
### New features
|
||||
|
||||
Support parsing stdin in command-line tool.
|
||||
|
||||
## 2.5.0 (2015-10-27)
|
||||
|
||||
### Fixes
|
||||
|
||||
Fix tokenizer support in the command-line tool.
|
||||
|
||||
Stop allowing `new.target` outside of functions.
|
||||
|
||||
Remove legacy `guard` and `guardedHandler` properties from try nodes.
|
||||
|
||||
Stop allowing multiple `__proto__` properties on an object literal in strict mode.
|
||||
|
||||
Don't allow rest parameters to be non-identifier patterns.
|
||||
|
||||
Check for duplicate paramter names in arrow functions.
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2012-2018 by various contributors (see AUTHORS)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,274 @@
|
|||
# Acorn
|
||||
|
||||
A tiny, fast JavaScript parser written in JavaScript.
|
||||
|
||||
## Community
|
||||
|
||||
Acorn is open source software released under an
|
||||
[MIT license](https://github.com/acornjs/acorn/blob/master/LICENSE).
|
||||
|
||||
You are welcome to
|
||||
[report bugs](https://github.com/acornjs/acorn/issues) or create pull
|
||||
requests on [github](https://github.com/acornjs/acorn). For questions
|
||||
and discussion, please use the
|
||||
[Tern discussion forum](https://discuss.ternjs.net).
|
||||
|
||||
## Installation
|
||||
|
||||
The easiest way to install acorn is from [`npm`](https://www.npmjs.com/):
|
||||
|
||||
```sh
|
||||
npm install acorn
|
||||
```
|
||||
|
||||
Alternately, you can download the source and build acorn yourself:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/acornjs/acorn.git
|
||||
cd acorn
|
||||
npm install
|
||||
```
|
||||
|
||||
## Interface
|
||||
|
||||
**parse**`(input, options)` is the main interface to the library. The
|
||||
`input` parameter is a string, `options` can be undefined or an object
|
||||
setting some of the options listed below. The return value will be an
|
||||
abstract syntax tree object as specified by the [ESTree
|
||||
spec](https://github.com/estree/estree).
|
||||
|
||||
```javascript
|
||||
let acorn = require("acorn");
|
||||
console.log(acorn.parse("1 + 1"));
|
||||
```
|
||||
|
||||
When encountering a syntax error, the parser will raise a
|
||||
`SyntaxError` object with a meaningful message. The error object will
|
||||
have a `pos` property that indicates the string offset at which the
|
||||
error occurred, and a `loc` object that contains a `{line, column}`
|
||||
object referring to that same position.
|
||||
|
||||
Options can be provided by passing a second argument, which should be
|
||||
an object containing any of these fields:
|
||||
|
||||
- **ecmaVersion**: Indicates the ECMAScript version to parse. Must be
|
||||
either 3, 5, 6 (2015), 7 (2016), 8 (2017), 9 (2018) or 10 (2019, partial
|
||||
support). This influences support for strict mode, the set of
|
||||
reserved words, and support for new syntax features. Default is 7.
|
||||
|
||||
**NOTE**: Only 'stage 4' (finalized) ECMAScript features are being
|
||||
implemented by Acorn. Other proposed new features can be implemented
|
||||
through plugins.
|
||||
|
||||
- **sourceType**: Indicate the mode the code should be parsed in. Can be
|
||||
either `"script"` or `"module"`. This influences global strict mode
|
||||
and parsing of `import` and `export` declarations.
|
||||
|
||||
- **onInsertedSemicolon**: If given a callback, that callback will be
|
||||
called whenever a missing semicolon is inserted by the parser. The
|
||||
callback will be given the character offset of the point where the
|
||||
semicolon is inserted as argument, and if `locations` is on, also a
|
||||
`{line, column}` object representing this position.
|
||||
|
||||
- **onTrailingComma**: Like `onInsertedSemicolon`, but for trailing
|
||||
commas.
|
||||
|
||||
- **allowReserved**: If `false`, using a reserved word will generate
|
||||
an error. Defaults to `true` for `ecmaVersion` 3, `false` for higher
|
||||
versions. When given the value `"never"`, reserved words and
|
||||
keywords can also not be used as property names (as in Internet
|
||||
Explorer's old parser).
|
||||
|
||||
- **allowReturnOutsideFunction**: By default, a return statement at
|
||||
the top level raises an error. Set this to `true` to accept such
|
||||
code.
|
||||
|
||||
- **allowImportExportEverywhere**: By default, `import` and `export`
|
||||
declarations can only appear at a program's top level. Setting this
|
||||
option to `true` allows them anywhere where a statement is allowed.
|
||||
|
||||
- **allowAwaitOutsideFunction**: By default, `await` expressions can
|
||||
only appear inside `async` functions. Setting this option to
|
||||
`true` allows to have top-level `await` expressions. They are
|
||||
still not allowed in non-`async` functions, though.
|
||||
|
||||
- **allowHashBang**: When this is enabled (off by default), if the
|
||||
code starts with the characters `#!` (as in a shellscript), the
|
||||
first line will be treated as a comment.
|
||||
|
||||
- **locations**: When `true`, each node has a `loc` object attached
|
||||
with `start` and `end` subobjects, each of which contains the
|
||||
one-based line and zero-based column numbers in `{line, column}`
|
||||
form. Default is `false`.
|
||||
|
||||
- **onToken**: If a function is passed for this option, each found
|
||||
token will be passed in same format as tokens returned from
|
||||
`tokenizer().getToken()`.
|
||||
|
||||
If array is passed, each found token is pushed to it.
|
||||
|
||||
Note that you are not allowed to call the parser from the
|
||||
callback—that will corrupt its internal state.
|
||||
|
||||
- **onComment**: If a function is passed for this option, whenever a
|
||||
comment is encountered the function will be called with the
|
||||
following parameters:
|
||||
|
||||
- `block`: `true` if the comment is a block comment, false if it
|
||||
is a line comment.
|
||||
- `text`: The content of the comment.
|
||||
- `start`: Character offset of the start of the comment.
|
||||
- `end`: Character offset of the end of the comment.
|
||||
|
||||
When the `locations` options is on, the `{line, column}` locations
|
||||
of the comment’s start and end are passed as two additional
|
||||
parameters.
|
||||
|
||||
If array is passed for this option, each found comment is pushed
|
||||
to it as object in Esprima format:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "Line" | "Block",
|
||||
"value": "comment text",
|
||||
"start": Number,
|
||||
"end": Number,
|
||||
// If `locations` option is on:
|
||||
"loc": {
|
||||
"start": {line: Number, column: Number}
|
||||
"end": {line: Number, column: Number}
|
||||
},
|
||||
// If `ranges` option is on:
|
||||
"range": [Number, Number]
|
||||
}
|
||||
```
|
||||
|
||||
Note that you are not allowed to call the parser from the
|
||||
callback—that will corrupt its internal state.
|
||||
|
||||
- **ranges**: Nodes have their start and end characters offsets
|
||||
recorded in `start` and `end` properties (directly on the node,
|
||||
rather than the `loc` object, which holds line/column data. To also
|
||||
add a
|
||||
[semi-standardized](https://bugzilla.mozilla.org/show_bug.cgi?id=745678)
|
||||
`range` property holding a `[start, end]` array with the same
|
||||
numbers, set the `ranges` option to `true`.
|
||||
|
||||
- **program**: It is possible to parse multiple files into a single
|
||||
AST by passing the tree produced by parsing the first file as the
|
||||
`program` option in subsequent parses. This will add the toplevel
|
||||
forms of the parsed file to the "Program" (top) node of an existing
|
||||
parse tree.
|
||||
|
||||
- **sourceFile**: When the `locations` option is `true`, you can pass
|
||||
this option to add a `source` attribute in every node’s `loc`
|
||||
object. Note that the contents of this option are not examined or
|
||||
processed in any way; you are free to use whatever format you
|
||||
choose.
|
||||
|
||||
- **directSourceFile**: Like `sourceFile`, but a `sourceFile` property
|
||||
will be added (regardless of the `location` option) directly to the
|
||||
nodes, rather than the `loc` object.
|
||||
|
||||
- **preserveParens**: If this option is `true`, parenthesized expressions
|
||||
are represented by (non-standard) `ParenthesizedExpression` nodes
|
||||
that have a single `expression` property containing the expression
|
||||
inside parentheses.
|
||||
|
||||
**parseExpressionAt**`(input, offset, options)` will parse a single
|
||||
expression in a string, and return its AST. It will not complain if
|
||||
there is more of the string left after the expression.
|
||||
|
||||
**tokenizer**`(input, options)` returns an object with a `getToken`
|
||||
method that can be called repeatedly to get the next token, a `{start,
|
||||
end, type, value}` object (with added `loc` property when the
|
||||
`locations` option is enabled and `range` property when the `ranges`
|
||||
option is enabled). When the token's type is `tokTypes.eof`, you
|
||||
should stop calling the method, since it will keep returning that same
|
||||
token forever.
|
||||
|
||||
In ES6 environment, returned result can be used as any other
|
||||
protocol-compliant iterable:
|
||||
|
||||
```javascript
|
||||
for (let token of acorn.tokenizer(str)) {
|
||||
// iterate over the tokens
|
||||
}
|
||||
|
||||
// transform code to array of tokens:
|
||||
var tokens = [...acorn.tokenizer(str)];
|
||||
```
|
||||
|
||||
**tokTypes** holds an object mapping names to the token type objects
|
||||
that end up in the `type` properties of tokens.
|
||||
|
||||
**getLineInfo**`(input, offset)` can be used to get a `{line,
|
||||
column}` object for a given program string and offset.
|
||||
|
||||
### The `Parser` class
|
||||
|
||||
Instances of the **`Parser`** class contain all the state and logic
|
||||
that drives a parse. It has static methods `parse`,
|
||||
`parseExpressionAt`, and `tokenizer` that match the top-level
|
||||
functions by the same name.
|
||||
|
||||
When extending the parser with plugins, you need to call these methods
|
||||
on the extended version of the class. To extend a parser with plugins,
|
||||
you can use its static `extend` method.
|
||||
|
||||
```javascript
|
||||
var acorn = require("acorn");
|
||||
var jsx = require("acorn-jsx");
|
||||
var JSXParser = acorn.Parser.extend(jsx());
|
||||
JSXParser.parse("foo(<bar/>)");
|
||||
```
|
||||
|
||||
The `extend` method takes any number of plugin values, and returns a
|
||||
new `Parser` class that includes the extra parser logic provided by
|
||||
the plugins.
|
||||
|
||||
## Command line interface
|
||||
|
||||
The `bin/acorn` utility can be used to parse a file from the command
|
||||
line. It accepts as arguments its input file and the following
|
||||
options:
|
||||
|
||||
- `--ecma3|--ecma5|--ecma6|--ecma7|--ecma8|--ecma9|--ecma10`: Sets the ECMAScript version
|
||||
to parse. Default is version 9.
|
||||
|
||||
- `--module`: Sets the parsing mode to `"module"`. Is set to `"script"` otherwise.
|
||||
|
||||
- `--locations`: Attaches a "loc" object to each node with "start" and
|
||||
"end" subobjects, each of which contains the one-based line and
|
||||
zero-based column numbers in `{line, column}` form.
|
||||
|
||||
- `--allow-hash-bang`: If the code starts with the characters #! (as
|
||||
in a shellscript), the first line will be treated as a comment.
|
||||
|
||||
- `--compact`: No whitespace is used in the AST output.
|
||||
|
||||
- `--silent`: Do not output the AST, just return the exit status.
|
||||
|
||||
- `--help`: Print the usage information and quit.
|
||||
|
||||
The utility spits out the syntax tree as JSON data.
|
||||
|
||||
## Existing plugins
|
||||
|
||||
- [`acorn-jsx`](https://github.com/RReverser/acorn-jsx): Parse [Facebook JSX syntax extensions](https://github.com/facebook/jsx)
|
||||
|
||||
Plugins for ECMAScript proposals:
|
||||
|
||||
- [`acorn-stage3`](https://github.com/acornjs/acorn-stage3): Parse most stage 3 proposals, bundling:
|
||||
- [`acorn-async-iteration`](https://github.com/acornjs/acorn-async-iteration): Parse [async iteration proposal](https://github.com/tc39/proposal-async-iteration)
|
||||
- [`acorn-bigint`](https://github.com/acornjs/acorn-bigint): Parse [BigInt proposal](https://github.com/tc39/proposal-bigint)
|
||||
- [`acorn-class-fields`](https://github.com/acornjs/acorn-class-fields): Parse [class fields proposal](https://github.com/tc39/proposal-class-fields)
|
||||
- [`acorn-dynamic-import`](https://github.com/kesne/acorn-dynamic-import): Parse [import() proposal](https://github.com/tc39/proposal-dynamic-import)
|
||||
- [`acorn-import-meta`](https://github.com/acornjs/acorn-import-meta): Parse [import.meta proposal](https://github.com/tc39/proposal-import-meta)
|
||||
- [`acorn-numeric-separator`](https://github.com/acornjs/acorn-numeric-separator): Parse [numeric separator proposal](https://github.com/tc39/proposal-numeric-separator)
|
||||
- [`acorn-private-methods`](https://github.com/acornjs/acorn-private-methods): parse [private methods, getters and setters proposal](https://github.com/tc39/proposal-private-methods)n
|
||||
|
||||
# @LOCALMOD XXX EMSCRIPTEN
|
||||
|
||||
Add a quote of the erroring text on parse errors, and point to where it is, search for `XXX EMSCRIPTEN` in `acorn.js`, https://github.com/acornjs/acorn/pull/793
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"_args": [
|
||||
[
|
||||
"acorn",
|
||||
"/home/alon/Dev/emscripten/tools"
|
||||
]
|
||||
],
|
||||
"_from": "acorn@latest",
|
||||
"_hasShrinkwrap": false,
|
||||
"_id": "acorn@6.0.5",
|
||||
"_inCache": true,
|
||||
"_installable": true,
|
||||
"_location": "/acorn",
|
||||
"_nodeVersion": "10.11.0",
|
||||
"_npmOperationalInternal": {
|
||||
"host": "s3://npm-registry-packages",
|
||||
"tmp": "tmp/acorn_6.0.5_1546429664768_0.2904693881325544"
|
||||
},
|
||||
"_npmUser": {
|
||||
"email": "marijnh@gmail.com",
|
||||
"name": "marijn"
|
||||
},
|
||||
"_npmVersion": "6.5.0",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"name": "acorn",
|
||||
"raw": "acorn",
|
||||
"rawSpec": "",
|
||||
"scope": null,
|
||||
"spec": "latest",
|
||||
"type": "tag"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz",
|
||||
"_shasum": "81730c0815f3f3b34d8efa95cb7430965f4d887a",
|
||||
"_shrinkwrap": null,
|
||||
"_spec": "acorn",
|
||||
"_where": "/home/alon/Dev/emscripten/tools",
|
||||
"bin": {
|
||||
"acorn": "./bin/acorn"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/acornjs/acorn/issues"
|
||||
},
|
||||
"dependencies": {},
|
||||
"description": "ECMAScript parser",
|
||||
"devDependencies": {},
|
||||
"directories": {},
|
||||
"dist": {
|
||||
"fileCount": 11,
|
||||
"integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==",
|
||||
"npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcLKThCRA9TVsSAnZWagAAPdMQAJRvN+u8N+Brr9vEHhxL\nhhCFbe+WC8MA0OzCC7eYj9H8cP+HbmL9gXXteyl/OvKT8TL19JiHgrE2wREV\nte054iG3K7xmciPuJw9KYHTrxcQMYRBJdbFgRspikJqfcZTXI0bXSh7K7d6S\nYG4rQEt38J2Gxsz53acstm8/6P/qU2clUODmsJdUMmVJwW4UdZ6yDVOmrxfV\ncs8rQTUkamCsBaNcDfQNqnqoc9aigXuSCFNR6650XOAZMnYvEhX+vOgNBD49\ny1WNHmswvETnei2+aWENYbsjVBmKLSdKiCpmgB5S5VLhcHzB2/3dAxP+5mCA\n+fYp3PGdnilWKB/TYsgwe+odI8k2P5UTXqCVeQsHApS1Gz8xmRv7f2olh24D\n14YBwwZpZLOoC8DDyelcK1uJ9eHpv7wRD2961GSVN9Zm64tYXWm24472PFBk\nBFRwNgTG3qrKsEB4WbfY63wkHkomJtQby7EVMtRJaDVU561pYmKOXFhuNeXO\nvbKrfsxM1cOcdWpZDf4+Tl20ud6D+/YmxnwVBwsqVb+qHRZC52Sfse4McBUg\nkKS48IquB/U95vXqYhKnmB+PmV2ONR7X4z8h8GhQXQwnWvm2NMH2OIe+VN55\ne4j9F0sYYQ3zMhCbiz1BXT+2U0aAqCPbDXfn7xnEWHiLhXRVkQ4I89PisOQM\n72uj\r\n=PbvZ\r\n-----END PGP SIGNATURE-----\r\n",
|
||||
"shasum": "81730c0815f3f3b34d8efa95cb7430965f4d887a",
|
||||
"tarball": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz",
|
||||
"unpackedSize": 1084980
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
},
|
||||
"homepage": "https://github.com/acornjs/acorn",
|
||||
"license": "MIT",
|
||||
"main": "dist/acorn.js",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "adrianheine",
|
||||
"email": "mail@adrianheine.de"
|
||||
},
|
||||
{
|
||||
"name": "marijn",
|
||||
"email": "marijnh@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "rreverser",
|
||||
"email": "me@rreverser.com"
|
||||
}
|
||||
],
|
||||
"module": "dist/acorn.mjs",
|
||||
"name": "acorn",
|
||||
"optionalDependencies": {},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/acornjs/acorn.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "cd ..; npm run build:main && npm run build:bin"
|
||||
},
|
||||
"version": "6.0.5"
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
UglifyJS is released under the BSD license:
|
||||
|
||||
Copyright 2012-2018 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,156 @@
|
|||
{
|
||||
"_from": "terser",
|
||||
"_id": "terser@3.14.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-NSo3E99QDbYSMeJaEk9YW2lTg3qS9V0aKGlb+PlOrei1X02r1wSBHCNX/O+yeTRFSWPKPIGj6MqvvdqV4rnVGw==",
|
||||
"_location": "/terser",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"registry": true,
|
||||
"raw": "terser",
|
||||
"name": "terser",
|
||||
"escapedName": "terser",
|
||||
"rawSpec": "",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/terser/-/terser-3.14.1.tgz",
|
||||
"_shasum": "cc4764014af570bc79c79742358bd46926018a32",
|
||||
"_spec": "terser",
|
||||
"_where": "/usr/local/google/home/azakai/Dev/emscripten/tools",
|
||||
"author": {
|
||||
"name": "Mihai Bazon",
|
||||
"email": "mihai.bazon@gmail.com",
|
||||
"url": "http://lisperator.net/"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/uglifyjs"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/fabiosantoscode/terser/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"commander": "~2.17.1",
|
||||
"source-map": "~0.6.1",
|
||||
"source-map-support": "~0.5.6"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit for ES6+",
|
||||
"devDependencies": {
|
||||
"acorn": "^6.0.4",
|
||||
"coveralls": "^3.0.2",
|
||||
"csv": "^5.0.0",
|
||||
"es6-promise": "^4.2.5",
|
||||
"escodegen": "^1.9.1",
|
||||
"eslint": "^4.19.1",
|
||||
"eslump": "^2.0.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"mocha": "^3.0.0",
|
||||
"mochallel": "^1.8.2",
|
||||
"pre-commit": "^1.2.2",
|
||||
"semver": "~5.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"rules": {
|
||||
"brace-style": [
|
||||
"error",
|
||||
"1tbs",
|
||||
{
|
||||
"allowSingleLine": true
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
"avoid-escape"
|
||||
],
|
||||
"no-debugger": "error",
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-extra-semi": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"space-before-blocks": [
|
||||
"error",
|
||||
"always"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"dist",
|
||||
"!dist/bundle.instrumented.js",
|
||||
"tools",
|
||||
"LICENSE"
|
||||
],
|
||||
"homepage": "https://github.com/fabiosantoscode/terser",
|
||||
"keywords": [
|
||||
"uglify",
|
||||
"terser",
|
||||
"uglify-es",
|
||||
"uglify-js",
|
||||
"minify",
|
||||
"minifier",
|
||||
"javascript",
|
||||
"ecmascript",
|
||||
"es5",
|
||||
"es6",
|
||||
"es7",
|
||||
"es8",
|
||||
"es2015",
|
||||
"es2016",
|
||||
"es2017",
|
||||
"async",
|
||||
"await"
|
||||
],
|
||||
"license": "BSD-2-Clause",
|
||||
|
||||
"main": "terser.js",
|
||||
|
||||
"zmain": "dist/bundle.js",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Fábio Santos",
|
||||
"email": "fabiosantosart@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Alex Lam",
|
||||
"email": "alexlamsl@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Mihai Bazon",
|
||||
"email": "mihai.bazon@gmail.com",
|
||||
"url": "http://lisperator.net/"
|
||||
}
|
||||
],
|
||||
"name": "terser",
|
||||
"pre-commit": [
|
||||
"lint-fix",
|
||||
"test"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/fabiosantoscode/terser.git"
|
||||
},
|
||||
"scripts": {
|
||||
"coverage": "istanbul cover test/run-tests.js",
|
||||
"coveralls": "coveralls < coverage/lcov.info",
|
||||
"lint": "eslint lib",
|
||||
"lint-fix": "eslint --fix lib",
|
||||
"prepare": "cd dist && TERSER_NO_BUNDLE=1 ../bin/uglifyjs ../tools/domprops.js ../lib/utils.js ../lib/ast.js ../lib/parse.js ../lib/transform.js ../lib/scope.js ../lib/output.js ../lib/compress.js ../lib/sourcemap.js ../lib/mozilla-ast.js ../lib/propmangle.js ../lib/minify.js ../tools/exports.js -mc -d \"MOZ_SourceMap=require('source-map')\" --source-map \"includeSources=true,url='bundle.js.map'\" -e \"exports:(typeof module != 'undefined' ? module.exports : Terser = {})\" -b beautify=false,ascii_only --comments /license/ -o ../dist/bundle.js",
|
||||
"test": "rm -f dist/* && npm run prepare && istanbul instrument dist/bundle.js > dist/bundle.instrumented.js && node test/run-tests.js"
|
||||
},
|
||||
"types": "tools/terser.d.ts",
|
||||
"version": "3.14.1"
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -2346,8 +2346,11 @@ class Building(object):
|
|||
|
||||
# run JS optimizer on some JS, ignoring asm.js contents if any - just run on it all
|
||||
@staticmethod
|
||||
def js_optimizer_no_asmjs(filename, passes, return_output=False, extra_info=None):
|
||||
from . import js_optimizer
|
||||
def js_optimizer_no_asmjs(filename, passes, return_output=False, extra_info=None, acorn=False):
|
||||
if not acorn:
|
||||
optimizer = path_from_root('tools', 'js-optimizer.js')
|
||||
else:
|
||||
optimizer = path_from_root('tools', 'acorn-optimizer.js')
|
||||
original_filename = filename
|
||||
if extra_info is not None:
|
||||
temp_files = configuration.get_temp_files()
|
||||
|
@ -2356,13 +2359,18 @@ class Building(object):
|
|||
with open(temp, 'a') as f:
|
||||
f.write('// EXTRA_INFO: ' + extra_info)
|
||||
filename = temp
|
||||
cmd = NODE_JS + [optimizer, filename] + passes
|
||||
if not return_output:
|
||||
next = original_filename + '.jso.js'
|
||||
configuration.get_temp_files().note(next)
|
||||
check_call(NODE_JS + [js_optimizer.JS_OPTIMIZER, filename] + passes, stdout=open(next, 'w'))
|
||||
run_process(cmd, stdout=open(next, 'w'))
|
||||
return next
|
||||
else:
|
||||
return run_process(NODE_JS + [js_optimizer.JS_OPTIMIZER, filename] + passes, stdout=PIPE).stdout
|
||||
return run_process(cmd, stdout=PIPE).stdout
|
||||
|
||||
@staticmethod
|
||||
def acorn_optimizer(filename, passes, extra_info=None, return_output=False):
|
||||
return Building.js_optimizer_no_asmjs(filename, passes, extra_info=extra_info, return_output=return_output, acorn=True)
|
||||
|
||||
# evals ctors. if binaryen_bin is provided, it is the dir of the binaryen tool for this, and we are in wasm mode
|
||||
@staticmethod
|
||||
|
@ -2531,7 +2539,7 @@ class Building(object):
|
|||
passes.append('minifyWhitespace')
|
||||
if passes:
|
||||
logger.debug('running cleanup on shell code: ' + ' '.join(passes))
|
||||
js_file = Building.js_optimizer_no_asmjs(js_file, ['noPrintMetadata'] + passes)
|
||||
js_file = Building.acorn_optimizer(js_file, passes)
|
||||
# if we can optimize this js+wasm combination under the assumption no one else
|
||||
# will see the internals, do so
|
||||
if not Settings.LINKABLE:
|
||||
|
@ -2541,11 +2549,11 @@ class Building(object):
|
|||
js_file = Building.metadce(js_file, wasm_file, minify_whitespace=minify_whitespace, debug_info=debug_info)
|
||||
# now that we removed unneeded communication between js and wasm, we can clean up
|
||||
# the js some more.
|
||||
passes = ['noPrintMetadata', 'AJSDCE']
|
||||
passes = ['AJSDCE']
|
||||
if minify_whitespace:
|
||||
passes.append('minifyWhitespace')
|
||||
logger.debug('running post-meta-DCE cleanup on shell code: ' + ' '.join(passes))
|
||||
js_file = Building.js_optimizer_no_asmjs(js_file, passes)
|
||||
js_file = Building.acorn_optimizer(js_file, passes)
|
||||
# also minify the names used between js and wasm, if we emitting JS (then the JS knows how to load the minified names)
|
||||
# If we are building with DECLARE_ASM_MODULE_EXPORTS=0, we must *not* minify the exports from the wasm module, since in DECLARE_ASM_MODULE_EXPORTS=0 mode, the code that
|
||||
# reads out the exports is compacted by design that it does not have a chance to unminify the functions. If we are building with DECLARE_ASM_MODULE_EXPORTS=1, we might
|
||||
|
@ -2565,7 +2573,7 @@ class Building(object):
|
|||
temp_files = configuration.get_temp_files()
|
||||
# first, get the JS part of the graph
|
||||
extra_info = '{ "exports": [' + ','.join(map(lambda x: '["' + x + '","' + x + '"]', Settings.MODULE_EXPORTS)) + ']}'
|
||||
txt = Building.js_optimizer_no_asmjs(js_file, ['emitDCEGraph', 'noEmitAst'], return_output=True, extra_info=extra_info)
|
||||
txt = Building.acorn_optimizer(js_file, ['emitDCEGraph', 'noPrint'], return_output=True, extra_info=extra_info)
|
||||
graph = json.loads(txt)
|
||||
# add exports based on the backend output, that are not present in the JS
|
||||
if not Settings.DECLARE_ASM_MODULE_EXPORTS:
|
||||
|
@ -2623,7 +2631,7 @@ class Building(object):
|
|||
if minify_whitespace:
|
||||
passes.append('minifyWhitespace')
|
||||
extra_info = {'unused': unused}
|
||||
return Building.js_optimizer_no_asmjs(js_file, passes, extra_info=json.dumps(extra_info))
|
||||
return Building.acorn_optimizer(js_file, passes, extra_info=json.dumps(extra_info))
|
||||
|
||||
@staticmethod
|
||||
def minify_wasm_imports_and_exports(js_file, wasm_file, minify_whitespace, minify_exports, debug_info):
|
||||
|
@ -2646,7 +2654,7 @@ class Building(object):
|
|||
if minify_whitespace:
|
||||
passes.append('minifyWhitespace')
|
||||
extra_info = {'mapping': mapping}
|
||||
return Building.js_optimizer_no_asmjs(js_file, passes, extra_info=json.dumps(extra_info))
|
||||
return Building.acorn_optimizer(js_file, passes, extra_info=json.dumps(extra_info))
|
||||
|
||||
# the exports the user requested
|
||||
user_requested_exports = []
|
||||
|
|
Загрузка…
Ссылка в новой задаче