Properly handle metadce of MINIMAL_RUNTIME exports (#10663)

We handled imports, but not exports. As a result, some things in
the wasm binary were not cleaned up by metadce. I noticed this
when comparing builds with minimal and normal runtime - the JS
was much smaller in minimal, but the binaries were a little bigger
due to stuff like unnecessary dynCalls, things like stackAlloc, etc.

This adds a metadce test for minimal runtime.

After this, minimal runtime has similar wasm sizes to the normal
runtime.
This commit is contained in:
Alon Zakai 2020-03-17 14:38:02 -07:00 коммит произвёл GitHub
Родитель 9f06f0a187
Коммит afba0e5191
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 125 добавлений и 50 удалений

Просмотреть файл

@ -3,8 +3,8 @@
"a.html.gz": 377,
"a.js": 5352,
"a.js.gz": 2540,
"a.wasm": 8364,
"a.wasm.gz": 4520,
"total": 14293,
"total_gz": 7437
"a.wasm": 8350,
"a.wasm.gz": 4509,
"total": 14285,
"total_gz": 7426
}

Просмотреть файл

@ -3,8 +3,8 @@
"a.html.gz": 377,
"a.js": 5133,
"a.js.gz": 2426,
"a.wasm": 11318,
"a.wasm.gz": 7100,
"total": 17014,
"total_gz": 9903
"a.wasm": 11262,
"a.wasm.gz": 7065,
"total": 16958,
"total_gz": 9868
}

Просмотреть файл

@ -1,10 +1,10 @@
{
"a.html": 588,
"a.html.gz": 386,
"a.js": 24667,
"a.js.gz": 9175,
"a.js": 24518,
"a.js.gz": 9119,
"a.mem": 3168,
"a.mem.gz": 2711,
"total": 28423,
"total_gz": 12272
"total": 28274,
"total_gz": 12216
}

Просмотреть файл

@ -3,8 +3,8 @@
"a.html.gz": 377,
"a.js": 4844,
"a.js.gz": 2365,
"a.wasm": 8364,
"a.wasm.gz": 4520,
"total": 13785,
"total_gz": 7262
"a.wasm": 8350,
"a.wasm.gz": 4509,
"total": 13777,
"total_gz": 7251
}

Просмотреть файл

@ -3,8 +3,8 @@
"a.html.gz": 377,
"a.js": 4624,
"a.js.gz": 2254,
"a.wasm": 11318,
"a.wasm.gz": 7100,
"total": 16505,
"total_gz": 9731
"a.wasm": 11262,
"a.wasm.gz": 7065,
"total": 16449,
"total_gz": 9696
}

Просмотреть файл

@ -1,10 +1,10 @@
{
"a.html": 588,
"a.html.gz": 386,
"a.js": 24162,
"a.js.gz": 9012,
"a.js": 24013,
"a.js.gz": 8955,
"a.mem": 3168,
"a.mem.gz": 2711,
"total": 27918,
"total_gz": 12109
"total": 27769,
"total_gz": 12052
}

Просмотреть файл

@ -3,8 +3,8 @@
"a.html.gz": 427,
"a.js": 450,
"a.js.gz": 321,
"a.wasm": 172,
"a.wasm.gz": 166,
"total": 1287,
"total_gz": 914
"a.wasm": 96,
"a.wasm.gz": 109,
"total": 1211,
"total_gz": 857
}

Просмотреть файл

@ -1,10 +1,10 @@
{
"a.html": 697,
"a.html.gz": 437,
"a.js": 2179,
"a.js.gz": 979,
"a.js": 2026,
"a.js.gz": 911,
"a.mem": 6,
"a.mem.gz": 32,
"total": 2882,
"total_gz": 1448
"total": 2729,
"total_gz": 1380
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"a.html": 2907,
"a.html.gz": 1231,
"total": 2913,
"total": 2907,
"total_gz": 1231
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"a.html": 13704,
"a.html.gz": 7262,
"total": 13744,
"total_gz": 7262
"a.html": 13468,
"a.html.gz": 7113,
"total": 13468,
"total_gz": 7113
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"a.html": 14100,
"a.html.gz": 7546,
"total": 14100,
"total_gz": 7546
"a.html": 13845,
"a.html.gz": 7353,
"total": 13845,
"total_gz": 7353
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"a.html": 20529,
"a.html.gz": 8546,
"total": 20529,
"total_gz": 8546
"a.html": 20054,
"a.html.gz": 8394,
"total": 20054,
"total_gz": 8394
}

Просмотреть файл

@ -36,8 +36,12 @@
{
"name": "emcc$export$_main",
"export": "_main",
"reaches": [],
"root": true
"reaches": []
},
{
"name": "emcc$export$_unused",
"export": "c",
"reaches": []
},
{
"name": "emcc$import$_emscripten_console_log",

Просмотреть файл

@ -64,12 +64,13 @@ var imports = {
})
}
};
var _main;
var _main, _unused;
WebAssembly.instantiate(Module["wasm"], imports).then((function(output) {
var asm = output.instance.exports;
_main = asm["b"];
_unused = asm["c"];
initRuntime();
ready();
}));
// EXTRA_INFO: { "exports": [["_main","_main"]]}
// EXTRA_INFO: { "exports": [["_main","_main"]]}

Просмотреть файл

@ -0,0 +1,2 @@
a
b

Просмотреть файл

@ -0,0 +1,2 @@
$__wasm_call_ctors
$add

Просмотреть файл

@ -0,0 +1 @@

Просмотреть файл

@ -0,0 +1,2 @@
memory
table

1
tests/test_other.py поставляемый
Просмотреть файл

@ -8257,6 +8257,7 @@ int main() {
'O3': (['-O3'], [], [], 85), # noqa
'Os': (['-Os'], [], [], 85), # noqa
'Oz': (['-Oz'], [], [], 85), # noqa
'Os_mr': (['-Os', '-s', 'MINIMAL_RUNTIME'], [], [], 85), # noqa
})
@no_fastcomp()
def test_metadce_minimal(self, *args):

Просмотреть файл

@ -550,7 +550,7 @@ function emitDCEGraph(ast) {
//
// The imports that wasm receives look like this:
//
// Module.asmLibraryArg = { "abort": abort, "assert": assert, [..] };
// 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:
@ -563,6 +563,14 @@ function emitDCEGraph(ast) {
// return Module["asm"]["_malloc"].apply(null, arguments);
// });
//
// or, in the minimal runtime, it looks like
//
// WebAssembly.instantiate(Module["wasm"], imports).then(function(output) {
// var asm = output.instance.exports;
// ..
// _malloc = asm["malloc"];
// ..
// });
var imports = [];
var defuns = [];
var dynCallNames = [];
@ -570,6 +578,7 @@ function emitDCEGraph(ast) {
var modulePropertyToGraphName = {};
var exportNameToGraphName = {}; // identical to asm['..'] nameToGraphName
var foundAsmLibraryArgAssign = false;
var foundMinimalRuntimeExports = false;
var graph = [];
function saveAsmExport(name, asmName) {
@ -628,15 +637,68 @@ function emitDCEGraph(ast) {
// name may be slightly different (extra "_" in wasm backend)
saveAsmExport(name, asmName);
emptyOut(node); // ignore this in the second pass; this does not root
return;
}
}
}
}
// A variable declaration that has no initial values can be ignored in
// the second pass, these are just declarations, not roots - an actual
// use must be found in order to root.
if (!node.declarations.reduce(function(hasInit, decl) {
return hasInit || !!decl.init
}, false)) {
emptyOut(node);
}
} 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
} else if (node.type === 'FunctionExpression') {
// Check if this is the minimal runtime exports function, which looks like
// function(output) { var asm = output.instance.exports;
if (node.params.length === 1 && node.params[0].type === 'Identifier' &&
node.params[0].name === 'output' && node.body.type === 'BlockStatement') {
var body = node.body.body;
if (body.length >= 1) {
var first = body[0];
if (first.type === 'VariableDeclaration' && first.declarations.length === 1) {
var decl = first.declarations[0];
if (decl.id.type === 'Identifier' && decl.id.name === 'asm') {
var value = decl.init;
if (value.type === 'MemberExpression' &&
value.object.type === 'MemberExpression' &&
value.object.object.type === 'Identifier' &&
value.object.object.name === 'output' &&
value.object.property.type === 'Identifier' &&
value.object.property.name === 'instance' &&
value.property.type === 'Identifier' &&
value.property.name === 'exports') {
// This looks very much like what we are looking for.
assert(!foundMinimalRuntimeExports);
for (var i = 1; i < body.length; i++) {
var item = body[i];
if (item.type === 'ExpressionStatement' &&
item.expression.type === 'AssignmentExpression' &&
item.expression.operator === '=' &&
item.expression.left.type === 'Identifier' &&
item.expression.right.type === 'MemberExpression' &&
item.expression.right.object.type === 'Identifier' &&
item.expression.right.object.name === 'asm' &&
item.expression.right.property.type === 'Literal') {
var name = item.expression.left.name;
var asmName = item.expression.right.property.value;
saveAsmExport(name, asmName);
emptyOut(item); // ignore all this in the second pass; this does not root
}
}
foundMinimalRuntimeExports = true;
}
}
}
}
}
}
});
// must find the info we need