Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback wanted: Allow custom "core JS" replacements, to *really* shrink minimal JS sizes #6803

Closed
wants to merge 31 commits into from

Conversation

kripken
Copy link
Member

@kripken kripken commented Jul 3, 2018

Note: This is just a proof of concept for feedback! Code needs a lot more work here. All proposed APIs are just some initial ideas.

The background here is that by default we include a lot of JS that supports things users might not need, like the various hooks during startup, the various input params on Module, etc. In very small projects their overhead is very noticeable.

What is proposed here is that we allow users to replace the "core JS" - the JS that sets up and starts up the application. In practice, that means the user provides some JS that replaces much of shell.js, preamble.js, and postamble.js. The "core JS" the user provides needs to implement two main methods:

  • setup(info): this sets up the wasm Memory and Table, prepares where the stack is, etc. It receives as a parameter the location of static data (as LLVM gave us) etc.
  • start(imports, onload): this starts up the application. imports contains the imports for the wasm module (properly connected to JS glue code, etc.), and onload should be called with the wasm instance as a parameter.

You can see an example core js file here: https://github.com/kripken/emscripten/blob/custom-core/src/corejs/minimal-web.js

// Environment setup

var out, err;
out = err = function(x) {
  console.log(x);
};

// Set up memory and table

function setup(info) {
  var memory = new WebAssembly.Memory({ initial: info.memorySize, maximum: info.memorySize });
  var table = new WebAssembly.Table({ initial: info.tableSize, maximum: info.tableSize, element: 'anyfunc' });
  var staticEnd = info.staticStart + info.staticSize;
  var stackStart = staticEnd;
  var stackMax = stackStart + info.stackSize;
  var sbrkStart = stackMax;
  var sbrkPtr = 16;
  (new Int32Array(memory.buffer))[sbrkPtr >> 2] = sbrkStart;
  return {
    memory: memory,
    table: table,
    stackStart: stackStart,
    sbrkStart: sbrkStart,
    sbrkPtr: sbrkPtr,
  };
}

// Compile and run

function start(imports, onload) {
  fetch('b.wasm', { credentials: 'same-origin' })
    .then(function(response) {
      return response.arrayBuffer();
    })
    .then(function(arrayBuffer) {
      return new Uint8Array(arrayBuffer);
    })
    .then(function(binary) {
      return WebAssembly.instantiate(binary, imports);
    })
    .then(function(pair) {
      var instance = pair['instance'];
      var exports = instance['exports'];
      onload(exports);
      exports['_main']();
    });
}

As you can see this is pretty short. It doesn't support all the stuff we support by default (like streaming compilation with a fallback, etc.), so it lets the user control exactly what they get.

The code size savings here are very good: a minimal hello world using EM_ASM built with -Os --closure 1 ends up with very little code aside from the core JS itself: around 1,469 bytes of JS, just 717 bytes with gzip! This is an order of magnitude better than the normal JS we emit. Of course, the effect is smaller as you include more, for example a hello world with printf (so it ends up using syscalls for I/O) is 4x smaller - still very significant.

The downside here is that we are adding a new option, so there's some more complexity overall. However, I think it's worth it for the benefits this provides for small outputs.

If you want to try this out, build with something like this:

./emcc tests/hello_world.c -s "CUSTOM_CORE_JS='/path/to/emscripten/src/corejs/minimal-multi.js'"

(note: a lot of stuff doesn't work yet)

@jgravelle-google
Copy link
Contributor

Oh wow that's radical. And a big win for EM_ASM hello world.
What's the intended use case here? I can see how this makes the small hello world case much simpler, though needing to learn the setup/start/-s CUSTOM_CORE_JS=... API means this isn't going to be someone's first experience with emcc.

Very interesting direction.

@kripken
Copy link
Member Author

kripken commented Jul 4, 2018

In terms of use cases, I think there are interesting programs that are pretty small. For example this can reduce the size of fannkuch's JS by 4x, so if you had some computation that was much faster in wasm (say it uses i64s), this makes it more practical to use, no silly 10K of boilerplate JS.

I'd also say that for larger programs, if this can save even 10% of the JS size, that's a good option too.

I agree this would not be something people start out with. Maybe we'd recommend something like: get your code working normally, and later if you have the need to really save on JS size, here's a practical way to do it.

@stale
Copy link

stale bot commented Sep 18, 2019

This issue has been automatically marked as stale because there has been no activity in the past year. It will be closed automatically if no further activity occurs in the next 7 days. Feel free to re-open at any time if this issue is still relevant.

@stale stale bot added the wontfix label Sep 18, 2019
@stale stale bot closed this Sep 25, 2019
@sbc100 sbc100 deleted the custom-core branch February 28, 2020 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants