Skip to content

Commit 5878814

Browse files
committed
Add a rollup plugin for tree shaking Emscripten output.
Rollup has built in tree shaking which can inform the plugin which exports are no longer used. If those exports correspond to wasm exports they can potentially be removed from wasm file too. Overview: While the modules are parsed, we collect info about the exports before any tree shaking occurs to build a map from ESM export name to Wasm export name and info about the local function name within the JS e.g. _foo. During bundle generation rollup provides a list of exports that are removed. We then try to match the removed exports to Wasm exports. However, they all cannot simply be removed though since they may be used internally in the file still. A new list of the used wasm exports is generated and passed to wasm-metadce to remove any unused exports. Note: there are a lot more things we could do there, but this seemed like a good stopping point. It also shows that even for specific output, tree shaking is hard to do soundly for dynamically loaded wasm.
1 parent 325b7d2 commit 5878814

File tree

10 files changed

+379
-0
lines changed

10 files changed

+379
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ tools/coverage/
2525
htmlcov/
2626
coverage.xml
2727

28+
/tools/rollup-plugin-emscripten/node_modules
29+
2830
.DS_Store
2931

3032
# Test output

test/rollup_plugin/index.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import init, { _used_externally } from './library.mjs';
2+
3+
await init();
4+
5+
console.log(_used_externally());
6+
console.log('done');

test/rollup_plugin/library.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <emscripten.h>
2+
3+
// Should NOT be removed (used by index.mjs).
4+
EMSCRIPTEN_KEEPALIVE int used_externally() {
5+
return 42;
6+
}
7+
8+
// Should NOT be removed (used by library.js).
9+
EMSCRIPTEN_KEEPALIVE int used_internally() {
10+
return 99;
11+
}
12+
13+
// Should be removed.
14+
EMSCRIPTEN_KEEPALIVE int unused() {
15+
return 0xDEAD;
16+
}

test/rollup_plugin/library.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
addToLibrary({
3+
$placeHolder__deps: ['used_internally'],
4+
$placeHolder: function() {
5+
_used_internally();
6+
}
7+
});
8+
9+
extraLibraryFuncs.push('$placeHolder');

test/rollup_plugin/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "test-rollup-plugin",
3+
"type": "module"
4+
}

test/rollup_plugin/rollup.config.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import emscriptenPlugin from 'rollup-plugin-emscripten';
2+
3+
export default {
4+
input: 'index.mjs',
5+
output: {
6+
dir: 'dist',
7+
format: 'es'
8+
},
9+
plugins: [
10+
emscriptenPlugin({
11+
'input': 'library.mjs'
12+
})
13+
]
14+
};

test/test_other.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15860,6 +15860,33 @@ def test_rollup(self):
1586015860
shutil.copy('hello.wasm', 'dist/')
1586115861
self.assertContained('hello, world!', self.run_js('dist/bundle.mjs'))
1586215862

15863+
@parameterized({
15864+
'': ('',),
15865+
'O1': ('-O1',),
15866+
'O2': ('-O2',),
15867+
'O3': ('-O3',),
15868+
})
15869+
def test_rollup_plugin(self, opt):
15870+
def get_exports(path):
15871+
with webassembly.Module(path) as module:
15872+
return [export.name for export in module.get_exports()]
15873+
15874+
copytree(test_file('rollup_plugin'), '.')
15875+
self.run_process([EMCC, 'library.c', opt, '--no-entry', '-sMODULARIZE=instance', '-sENVIRONMENT=node', '--js-library', 'library.js', '-o', 'library.mjs'])
15876+
self.run_process(['npm', 'install', path_from_root('tools/rollup-plugin-emscripten')])
15877+
self.run_process(shared.get_npm_cmd('rollup') + ['--config'])
15878+
exports_pre_rollup = get_exports('library.wasm')
15879+
self.assertContained('42\ndone\n', self.run_js('dist/index.js'))
15880+
exports = get_exports('dist/library.wasm')
15881+
self.assertTrue(len(exports) < len(exports_pre_rollup))
15882+
if opt != '-O3':
15883+
# In O3 wasm export names are minified.
15884+
self.assertTrue('used_externally' in exports)
15885+
self.assertTrue('used_internally' in exports)
15886+
self.assertTrue('unused' not in exports)
15887+
15888+
15889+
1586315890
def test_rlimit(self):
1586415891
self.do_other_test('test_rlimit.c', emcc_args=['-O1'])
1586515892

tools/rollup-plugin-emscripten/package-lock.json

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "rollup-plugin-emscripten",
3+
"version": "0.0.1",
4+
"description": "Rollup plugin for Emscripten output with tree shaking.",
5+
"main": "src/index.mjs",
6+
"dependencies": {
7+
"tmp": "^0.2.3",
8+
"which": "^5.0.0"
9+
}
10+
}

0 commit comments

Comments
 (0)