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

Initial support for pthreads with wasm #5710

Merged
merged 28 commits into from
Dec 1, 2017
Merged

Initial support for pthreads with wasm #5710

merged 28 commits into from
Dec 1, 2017

Conversation

dschuff
Copy link
Member

@dschuff dschuff commented Oct 25, 2017

Also adds support for testing with both wasm and JS+SAB, but there's no clean way to enable it yet.
Tested with EMSCRIPTEN_BROWSER='/path/to/chrome --user-data-dir=clean_dir --js-flags=--experimental-wasm-threads --enable-features=WebAssembly --disable-features=WebAssemblyTrapHandler' with a locally-built chrome but it should work with canary also.

src/preamble.js Outdated
@@ -1839,7 +1849,7 @@ function addRunDependency(id) {
#if USE_PTHREADS
// We should never get here in pthreads (could no-op this out if called in pthreads, but that might indicate a bug in caller side,
// so good to be very explicit)
assert(!ENVIRONMENT_IS_PTHREAD);
assert(!ENVIRONMENT_IS_PTHREAD || id === 'wasm-instantiate');
Copy link
Member Author

Choose a reason for hiding this comment

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

This is uuuuugly.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you make this change in order to get async instantiation working? It would probably be better if we avoided this altogether and follow the path "We can just use sync instantiation in the worker." that you wrote below?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I wrote it before I switched to sync instantiation, removed now.

Copy link
Member Author

@dschuff dschuff Nov 15, 2017

Choose a reason for hiding this comment

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

Actually that's not quite right; preamble.js adds the dependency regardless of whether a custom instantiator is used. We can bypass that in threads (see 9d97628) which I guess is a little bit nicer than this. I also noticed that right now the user can't override wasm-instantiate in a pthread right now (only on the main thread). Is that something we need?

@kripken kripken requested a review from juj October 25, 2017 18:36
src/preamble.js Outdated
@@ -1237,6 +1238,15 @@ if (typeof Atomics === 'undefined') {
Atomics['xor'] = function(t, i, v) { var w = t[i]; t[i] ^= v; return w; }
}

#else
// TODO: Something smart about BINARYEN_MEM_MAX. Some default, also rename it.
Copy link
Member

Choose a reason for hiding this comment

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

is it right that wasm can't support memory growth with shared memory? if so, we can just show an error if growth or max > initial?

Copy link
Member Author

Choose a reason for hiding this comment

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

It can support growth, just not unconstrained growth. Or in other words, you can have max > initial, but you can't set an initial without a max.

Copy link
Member

Choose a reason for hiding this comment

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

I see, ok.

I still don't understand this code, though. Why do we create the memory here instead of later down where we create it normally (which also handles MEM_MAX)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Basically just because of the overall structure of the ifdefs. All the pthreads stuff is first, and everything below is in the else (not pthreads). This whole nest of code is really ugly and complex because of all the options, should probably cleaned up and/or the options rethought.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Need to resolve this comment - I think we can make command line requirement to pass BINARYEN_MEM_MAX so that frontend makes sure in multithreaded mode this gets set?

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, made it a requirement to add WASM_MEM_MAX when using wasm+pthreads and memory growth. I tried to add a test but it doesn't look like memory growth on a thread is working right now. Will address that separately.

src/preamble.js Outdated
@@ -2303,6 +2313,10 @@ function integrateWasmJS() {
assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?');
trueModule = null;
#endif
#if USE_PTHREADS
// Keeep a reference to the compiled module so we can post it to the workers.
Module['wasmModule'] = output['module'];
Copy link
Member

Choose a reason for hiding this comment

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

the module should be saved as Module['asm'] already

Copy link
Collaborator

Choose a reason for hiding this comment

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

The file preamble.js has Module['asm'] = exports;, so 'asm' seems to be getting the exports object, rather than the Module. So it looks like we do need to store this manually(?)

I think what we'd like is to make receiveInstance() take a second parameter that is the module, so that applications that instantiate themselves will also get the module stored for threads.

I do hate this a bit that we do have to store the module to be able to instantiate a new thread - that wastes memory; and I was pondering if we could reuse IndexedDB and the thread pool idea to optimize, i.e. if application knows they only need fixed K threads at startup, the could precreate a pool of that size to be able to throw away the Module afterwards. If new threads want to spawn after that, they would take a slow path of reading the Module from IndexedDB for instantiation. Although not sure if that's worth the trouble to optimize, so let's leave that for later.

@@ -3108,19 +3108,26 @@ def prep_no_SAB(self):
</script>
'''))

# Run a browser test both with and without wasm
def btest_wasm(self, *args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

for running as both asm and wasm, see the also_proxied flag to btest which makes us test both regular and proxied mode. maybe we can do the same pattern for asm and wasm.

Copy link
Member

Choose a reason for hiding this comment

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

I still don't see why we need a new method btest_wasm, can't be do something parallel to also_proxied, like also_wasm?

otherwise lgtm

Copy link
Member

Choose a reason for hiding this comment

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

Or is this different because it is off for now?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, sorry I just hadn't gotten to that yet. Its switched now. I don't see the feature-detection mechanism you mentioned though. Since the tests run in a random order we can't just rely on the outcome of one of them to gate the rest. I thought I might just add a flag to runner.py for now.

Copy link
Member Author

Choose a reason for hiding this comment

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

BTW, chrome needs a flag to enable threads (which means modifying EMSCRIPTEN_BROWSER in the env) so I thought maybe just another env var for testing it since we have an env var anyway. Does nightly need a separate flag, or is it just on by default there?

Copy link
Member

Choose a reason for hiding this comment

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

Not sure about how we avoid pthreads tests if no pthreads support - @juj?

Also @juj would know about firefox nightly needing a flag for wasm threads or not as he tested it.

@kripken
Copy link
Member

kripken commented Oct 25, 2017

Need to think about the testing here. I think we have a mechanism to check if pthreads is supported and we skip those tests otherwise, maybe we need something similar for wasm-pthreads, since right now they aren't in firefox, so those tests would break.

@kripken
Copy link
Member

kripken commented Nov 1, 2017

There is now a firefox build with wasm threads support. I tested a few simple things and didn't see any issues. I did need to add

+    if shared.Settings.USE_PTHREADS:
+      cmd += ['--enable-threads']

around line 2263 of emcc.py though.

@juj
Copy link
Collaborator

juj commented Nov 2, 2017

Perfect, thanks @dschuff for getting this going!

I'm testing this against the Multithreading series of branches, which I'm looking to resume landing shortly. Currently running test suites on Firefox Nightly, so I'll get back with feedback on this in a moment.

@juj
Copy link
Collaborator

juj commented Nov 6, 2017

Test suites pass in FF Nightly, and also some code compiles; The tree https://github.com/juj/emscripten/commits/multithreading_5 is the current "latest state" for multithreading support that the Multithreading PRs are landing towards, see in particular juj@a1cf520 and juj@8690b9a that relate to this PR.

Unfortunately it looks like real world(tm) codebases don't still compile, I'm currently hunting some odd codegen related bug to understand this better. It could be related to the global data section initialization commit above, though not completely sure.

@dschuff
Copy link
Member Author

dschuff commented Nov 16, 2017

OK, assuming it doesn't break any existing tests, this may be ready to land. Any more thoughts?

@kripken
Copy link
Member

kripken commented Nov 16, 2017

lgtm. the new wasm-pthreads are off by default, looking for that env var, correct?

@juj should look at this code too (before or after landing is ok i think).

Module['wasmMemory'] = e.data.wasmMemory;
buffer = Module['wasmMemory'].buffer;
} else {
buffer = e.data.buffer;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would be #if !WASM, or an #else block to above.

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

src/shell.js Outdated
@@ -69,6 +69,8 @@ if (!ENVIRONMENT_IS_PTHREAD) ENVIRONMENT_IS_PTHREAD = false; // ENVIRONMENT_IS_P
var PthreadWorkerInit; // Collects together variables that are needed at initialization time for the web workers that host pthreads.
if (!ENVIRONMENT_IS_PTHREAD) PthreadWorkerInit = {};
var currentScriptUrl = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined;
#else
var ENVIRONMENT_IS_PTHREAD = false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's not add this in at all, in singlethreaded builds we want to never see a variable ENVIRONMENT_IS_PTHREAD anyhwere. I like to keep this strict to enforce a "don't pay for what you don't use" barrier on this feature.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree in principle but here's where we have a tradeoff. Gating the 2 lines of ENVIRONMENT_IS_PTHREAD check above on USING_PTHREADS makes the code up there super ugly because of the way the ifdefs interact with conditionals in the code. I went ahead and did it, but I guess apropos of our discussion in #5732 it would be much much more readable to either (for example) turn ENVIRONMENT_IS_PTHREAD into a macro, or go all-in on closure and make everything regular JS code.

Copy link
Member Author

@dschuff dschuff left a comment

Choose a reason for hiding this comment

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

should be all addressed except for improving handling of WASM_MEM_MAX (waiting for #5803)

// We can just use sync instantiation in the worker.
instance = new WebAssembly.Instance(Module['wasmModule'], info);
// We don't need the module anymore; new threads will be spawned from the main thread.
// XXX is this true?
Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Module['wasmModule'] = undefined;
receiveInstance(instance);
return instance.exports;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Done. In asm.js this code will never be called and is harmless, so I suppose gating it on WASM just reduces the code size. Not sure how many people will be shipping anything real with asm.js threads before then? But here it's easy and clean.

Module['TOTAL_MEMORY'] = TOTAL_MEMORY = e.data.TOTAL_MEMORY;
STATICTOP = e.data.STATICTOP;
DYNAMIC_BASE = e.data.DYNAMIC_BASE;
DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR;


if (e.data.wasmModule) {
Copy link
Member Author

Choose a reason for hiding this comment

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

good point, done.

Module['wasmMemory'] = e.data.wasmMemory;
buffer = Module['wasmMemory'].buffer;
} else {
buffer = e.data.buffer;
Copy link
Member Author

Choose a reason for hiding this comment

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

done.

src/shell.js Outdated
@@ -69,6 +69,8 @@ if (!ENVIRONMENT_IS_PTHREAD) ENVIRONMENT_IS_PTHREAD = false; // ENVIRONMENT_IS_P
var PthreadWorkerInit; // Collects together variables that are needed at initialization time for the web workers that host pthreads.
if (!ENVIRONMENT_IS_PTHREAD) PthreadWorkerInit = {};
var currentScriptUrl = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined;
#else
var ENVIRONMENT_IS_PTHREAD = false;
Copy link
Member Author

Choose a reason for hiding this comment

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

I agree in principle but here's where we have a tradeoff. Gating the 2 lines of ENVIRONMENT_IS_PTHREAD check above on USING_PTHREADS makes the code up there super ugly because of the way the ifdefs interact with conditionals in the code. I went ahead and did it, but I guess apropos of our discussion in #5732 it would be much much more readable to either (for example) turn ENVIRONMENT_IS_PTHREAD into a macro, or go all-in on closure and make everything regular JS code.

@kripken
Copy link
Member

kripken commented Nov 17, 2017

I had an idea, instead of adding , also_wasm=True to all those tests, could we apply that behavior if an env var is set? Then to run these tests, we'd do something like EMCC_BROWSER_ALSO_WASM=1 ./tests/runner.py browser.test_pthread*

@dschuff
Copy link
Member Author

dschuff commented Nov 21, 2017

I think having a way to make that the default behavior makes sense, although I also think having the flag makes sense as a way to disable it. But making it on instead of off by default does seem better.

@kripken
Copy link
Member

kripken commented Nov 21, 2017

I think it makes sense to have it on by default eventually, but maybe only when canary/nightly/etc. can run wasm-threads tests by default, which I don't think is the case yet? So existing waterfalls would need updating to disable the tests.

@dschuff
Copy link
Member Author

dschuff commented Nov 21, 2017

Sorry, by "on by default" I meant the btest function's also_wasm argument defaults to True (thus allowing callers to set it to False), but also gate running any of the wasm thread tests behind an env var.

@kripken
Copy link
Member

kripken commented Nov 21, 2017

In that case, it sounds like setting also_wasm to False is similar to the @no_wasm decorator?

@dschuff
Copy link
Member Author

dschuff commented Nov 29, 2017

It's similar. With the core tests we run the whole suite in a mode (asm2g, binaryen0, etc) which could be wasm or not, (and the flag is handled separately from the tests) and @no_wasm` skips the entire test case if it is. Currently the browser test system doesn't have the same notion of modes, so the test cases just exercise all the flags they want. Part of the reason is that the core tests want to exercise different compiler modes across a whole bunch of tests, whereas the browser tests are mostly trying to exercise the runtime, which is less affected by the compiler modes Therefore we don't want to run the whole suite across all modes because it would be redundant and slow. We could try to make the browser test model more like core; whether that makes sense probably depends on how much the things that the suite wants to exercise vary across the modes. My intuition is that it wouldn't be worth it. btw threads are a notable exception because they can't currently be tested in the shell. Really we would want to put most of those tests in core (if there were a good way to use workers in shells/node).

@kripken
Copy link
Member

kripken commented Nov 29, 2017

Fair enough, we can leave test improvements for some other time.

@lars-t-hansen
Copy link

btw threads are a notable exception because they can't currently be tested in the shell. Really we would want to put most of those tests in core (if there were a good way to use workers in shells/node).

Working on it: WebAssembly/threads#52

@dschuff
Copy link
Member Author

dschuff commented Nov 30, 2017

OK I think i've addressed all the comments and have checked that nothing existing breaks. Shoudl be good, but I'll wait for CI. The circleci build seems to have completed without doing anything; is there something I need to do to make that work?

@kripken
Copy link
Member

kripken commented Nov 30, 2017

CircleCI will work once you get the changes on incoming, but not worth merging incoming into here for that, as they overlap the travis tests now. (Although they are faster, so it might actually be worth it ;)

Please run the browser tests locally, though, as they don't run on the bots.

@dschuff
Copy link
Member Author

dschuff commented Dec 1, 2017

OK, verified no new breakage.

@dschuff dschuff merged commit dee8676 into incoming Dec 1, 2017
@kripken
Copy link
Member

kripken commented Dec 1, 2017

Great!

Do you need to keep the branch around, or can it be deleted?

@dschuff dschuff deleted the threads-hack branch December 5, 2017 00:43
@dschuff
Copy link
Member Author

dschuff commented Dec 5, 2017

Deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants