-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Conversation
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'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is uuuuugly.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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']; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
tests/test_browser.py
Outdated
@@ -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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
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
around line 2263 of emcc.py though. |
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. |
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. |
This reverts commit 8049139.
OK, assuming it doesn't break any existing tests, this may be ready to land. Any more thoughts? |
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). |
src/pthread-main.js
Outdated
Module['wasmMemory'] = e.data.wasmMemory; | ||
buffer = Module['wasmMemory'].buffer; | ||
} else { | ||
buffer = e.data.buffer; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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)
src/pthread-main.js
Outdated
// 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? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/pthread-main.js
Outdated
Module['wasmModule'] = undefined; | ||
receiveInstance(instance); | ||
return instance.exports; | ||
} |
There was a problem hiding this comment.
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.
src/pthread-main.js
Outdated
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, done.
src/pthread-main.js
Outdated
Module['wasmMemory'] = e.data.wasmMemory; | ||
buffer = Module['wasmMemory'].buffer; | ||
} else { | ||
buffer = e.data.buffer; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
I had an idea, instead of adding |
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. |
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. |
Sorry, by "on by default" I meant the |
In that case, it sounds like setting |
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). |
Fair enough, we can leave test improvements for some other time. |
Working on it: WebAssembly/threads#52 |
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? |
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. |
OK, verified no new breakage. |
Great! Do you need to keep the branch around, or can it be deleted? |
Deleted. |
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.