Skip to content

WASM: Tree Shaking, Export and import name mangling#7275

Merged
sokra merged 24 commits intomasterfrom
feature/wasm-mangling
May 28, 2018
Merged

WASM: Tree Shaking, Export and import name mangling#7275
sokra merged 24 commits intomasterfrom
feature/wasm-mangling

Conversation

@sokra
Copy link
Copy Markdown
Member

@sokra sokra commented May 11, 2018

What kind of change does this PR introduce?
feature + bugfix

Did you add tests for your changes?
yes

If relevant, link to documentation update:
n/a

Summary

  • remove unused exports from WASM
  • replace export names with shorter names when used in a analyseable way
  • replace import names with shorter names
  • replace import modules with a
  • generate shorter code for the importsObject
  • enforce direct connection when using non-js types

Does this PR introduce a breaking change?
no

Other information

@sokra sokra requested a review from xtuc May 11, 2018 07:12
Comment thread lib/wasm/WebAssemblyGenerator.js Outdated
if (usedName) {
path.node.name = usedName;
// TODO remove this when fixed in @webassemblyjs
path.node.descr.id = t.numberLiteral(+path.node.descr.id.raw);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xtuc This seem to be a bug in edit. Identifiers are not accepted as id.

Comment thread lib/wasm/WebAssemblyGenerator.js Outdated
if (path.node.descr.id)
path.node.descr.id = t.numberLiteral(+path.node.descr.id.raw);
if (path.node.descr.name)
path.node.descr.name = t.numberLiteral(+path.node.descr.name.raw);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xtuc Same here for id and name

generate(module) {
const bin = module.originalSource().source();

const initFuncId = t.identifier(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we need to avoid colliding with an user's export, like __webpack_init__ is a legal export.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take the risk for now...

Comment thread lib/wasm/WebAssemblyParser.js Outdated
*/
const isTableImport = moduleImport => moduleImport.descr.type === "Table";

const JS_COMPAT_TYPES = new Set(["i32", "u32", "f32"]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u32 shouldn't be there, I believe it's an implementation details in webassemblyjs which showed up.

Also missing f64.

Comment thread lib/wasm/WebAssemblyGenerator.js Outdated
return add(bin, [func, moduleExport, funcindex, functype]);
};

const getImportMangleMap = module => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also allow an noMangle option in the rule? Might be easier to debug.

Copy link
Copy Markdown
Member

@Jessidhia Jessidhia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there's no infrastructure to compile the .wasm in the test suite yet?

return Template.asString([
`${JSON.stringify(module.id)}: function() {`,
Template.indent([
`return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't need to wait for a microtask you could just resolve the promise directly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used to delay the evaluation of the promise code, because wasm modules may not be created in the "correct" order. A microtask later all variables are filled.

But you are right we don't need the microtask delay if we would use the correct order. I'll add a TODO to topo-sort them instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I tried but it's impossible to sort them correctly, because of splitChunks.

webpack generates code like this to load multiple parts of a chunk group: Promise.all([__webpack_require__.e(1), __webpack_require__.e(2)]). For modules with a dependency chain of a -> b -> c and with a and c in chunk 1 and b in chunk 2, we can't solve this by sorting. So I'll stay with the microtask delay solution.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment into the code

`${JSON.stringify(module.id)}: function() {`,
Template.indent([
`return Promise.resolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
`return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

"// object to store loaded and loading wasm modules",
"var installedWasmModules = {};",
"",
"function promiseResolve() { return Promise.resolve(); }",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no Promise.resolve.bind(Promise)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have used var x = Promise.resolve() but the problem is that Promise may not be available on bootstrap, as the user could load the Promise polyfill in the entrypoint. This ensures that Promise is not used until import() is used.

We actually had issue reports about that...

Comment thread lib/wasm/WebAssemblyGenerator.js Outdated
if (result === undefined) {
path.remove();
} else {
path.node.module = "a";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this hardcoded? 🤔

@xtuc
Copy link
Copy Markdown
Member

xtuc commented May 11, 2018

@Kovensky

I guess there's no infrastructure to compile the .wasm in the test suite yet?

We could compile wast (text format) to wasm (binary format) files.

@sokra sokra force-pushed the feature/wasm-mangling branch from 062fe94 to 22ec604 Compare May 11, 2018 19:05
// TODO remove this when fixed in @webassemblyjs
path.node.descr.id = t.numberLiteral(+path.node.descr.id.raw);
} else {
path.remove();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only removes a couple of bytes from the export section. We can implement proper func elimination in the future , I'ill work on that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all work we need to do at this place. DCE will be the job of the WASM minimizer (to be defined/implemented), which will operate on asset level.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, sounds good. I have a couple of wasm optimizations that we could put there as well.

Comment thread lib/wasm/WebAssemblyGenerator.js Outdated
if (usedName) {
path.node.name = usedName;
// TODO remove this when fixed in @webassemblyjs
path.node.descr.id = t.numberLiteral(+path.node.descr.id.raw);
Copy link
Copy Markdown
Member

@xtuc xtuc May 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry but I don't understand why you are renaming the internal name here?

How are the usage detected? I'm not able to trigger the DCE.

import {test} from "./test.c.wasm";

test();

// generator.js

module.isUsed("test") // test
module.isUsed("nonexisting") // nonexisting

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to compile in production mode or enable optimization.usedExports.

I also don't understand why this line is needed, but it crashes without with some error about Identifiers is not allowed somewhere inside.

Copy link
Copy Markdown
Member

@xtuc xtuc May 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I couldn't reproduce it thanks. I'll clarify that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's already fixed, I'll check that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, still broken. To reproduce:

  • checkout this branch
  • yarn jest TestCasesProduction -t wasm --watch --no-verbose
  • tests are passing
  • remove this line
  • tests are failing
Error: C:\Users\tobia\Repos\webpack\test\cases\wasm\simple\wasm.wasm?1
    Unsupported node Identifier

      198 |  */
      199 | const rewriteExportNames = ({ ast, module }) => bin => {
    > 200 |     return editWithAST(ast, bin, {
      201 |             ModuleExport(path) {
      202 |                     const usedName = module.isUsed(path.node.name);
      203 |                     if (usedName) {

      at assertNotIdentifierNode (node_modules/@webassemblyjs/wasm-gen/lib/encoder/index.js:39:11)
      at Object.encodeModuleExport (node_modules/@webassemblyjs/wasm-gen/lib/encoder/index.js:205:3)
      at encodeNode (node_modules/@webassemblyjs/wasm-gen/lib/index.js:40:22)
      at applyUpdate (node_modules/@webassemblyjs/wasm-edit/lib/apply.js:42:54)
      at node_modules/@webassemblyjs/wasm-edit/lib/apply.js:224:17
          at Array.forEach (<anonymous>)
      at applyOperations (node_modules/@webassemblyjs/wasm-edit/lib/apply.js:218:7)
      at editWithAST (node_modules/@webassemblyjs/wasm-edit/lib/index.js:83:44)
      at bin (lib/wasm/WebAssemblyGenerator.js:200:9)
      at value (lib/wasm/WebAssemblyGenerator.js:26:19)
      at value (lib/wasm/WebAssemblyGenerator.js:26:26)
      at value (lib/wasm/WebAssemblyGenerator.js:26:26)
      at value (lib/wasm/WebAssemblyGenerator.js:26:26)
      at value (lib/wasm/WebAssemblyGenerator.js:26:26)
      at WebAssemblyGenerator.generate (lib/wasm/WebAssemblyGenerator.js:369:18)

Comment thread lib/wasm/WebAssemblyGenerator.js Outdated
if (path.node.descr.id)
path.node.descr.id = t.numberLiteral(+path.node.descr.id.raw);
if (path.node.descr.name)
path.node.descr.name = t.numberLiteral(+path.node.descr.name.raw);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure we need to rename the path.node.descr, it's only the internal reference and uses an index anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. Try to remove these lines and run the test suite

@xtuc
Copy link
Copy Markdown
Member

xtuc commented May 14, 2018

Could we transform this warning into an error? That will fail for sure at runtime.

3:0-3 "export 'foo' was not found in './test.c.wasm'

@webpack-bot
Copy link
Copy Markdown
Contributor

webpack-bot commented May 16, 2018

For maintainers only:

  • This need to be documented (issue in webpack/webpack.js.org will be filed when merged)

sokra added 2 commits May 16, 2018 16:51
# Conflicts:
#	package.json
#	yarn.lock
if (result === undefined) {
path.remove();
} else {
path.node.module = WebAssemblyUtils.MANGLED_MODULE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be variable, because otherwise every import will be from the same module.

module = file
name = named export

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or we need to update the ImportObject in the runtime but something isn't matching correctly

@webpack-bot
Copy link
Copy Markdown
Contributor

Thank you for your pull request! The most important CI builds succeeded, we’ll review the pull request soon.

@xtuc
Copy link
Copy Markdown
Member

xtuc commented May 25, 2018

🎉

@sokra sokra merged commit 29cbf98 into master May 28, 2018
@sokra sokra deleted the feature/wasm-mangling branch June 7, 2018 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants