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

allow Memory.buffer to be a SharedArrayBuffer #950

Closed
bpowers opened this issue Jan 13, 2017 · 10 comments
Closed

allow Memory.buffer to be a SharedArrayBuffer #950

bpowers opened this issue Jan 13, 2017 · 10 comments

Comments

@bpowers
Copy link

bpowers commented Jan 13, 2017

Some background: I'm working on Browsix, a tool for running multiple C, C++, Go and node.js programs as processes in the browser. We do this for C programs by compiling with a (fork) of Emscripten and running the compiled "binary" in a Web Worker, where our fork mainly replaces src/library_syscalls.js with an implementation that calls into a kernel (library) that runs on the main browser thread.

Our preferred method for system calls works like this: When the process/Web Worker starts up, the backing buffer for the heap is initialized as a SharedArrayBuffer, and a reference is sent to the kernel (over a message sent with postMessage), along with an offset into that SharedArrayBuffer reserved as an address for Atomics.wait. When the program issues future system calls, the runtime sends a message (with postMessage) to the kernel with the system call number and arguments (integers or "pointer" offsets into the heap), and Atomics.wait() on the pre-arranged request. The kernel processes the system call, places results in the SharedArrayBuffer, and Atomics.wake()s the process.

This works great with asm.js code emitted from Emscripten (and is much faster than our first approach, which relied on the Emterpreter to save and restore the stack), but I don't think is possible with the current WebAssembly spec (I would love to be wrong!).

My proposal is to add an additional optional argument to the options object that WebAssembly.Memory takes, a boolean "isShared". When this argument is present and true, the buffer backing the Memory is created as a SharedArrayBuffer.

I've implemented this in v8 here: bpowers/v8@bda41b7 and so far in my limited testing seems to do the trick -- I am able to run small C programs compiled with a modified emscripten/binaryen as Browsix processes in Web Workers.

Thoughts/suggestions?

@jfbastien
Copy link
Member

This would fall under future features: threads. We definitely want to do this, and it's great if you want to explore what it looks like.

A few thoughts:

  • Can you create isShared from an exported Memory (i.e. not created with WebAssembly.Memory, but created and then exported through a memory section).
  • What are the semantics when importing such a shared memory into a WebAssembly instance?
  • What semantics do you propose for Memory.grow / grow_memory?
  • How does the integration with workers work? We need to clarify requirements on running wasm from different workers.
  • How does it interact with SharedArrayBuffer from JS?

@bpowers
Copy link
Author

bpowers commented Jan 13, 2017

I would love to explore what it looks like.

Off the top of my head I don't have detailed answers to those questions, but I'll look into the current spec and post back.

@lukewagner
Copy link
Member

Agreed with @jfbastien that this would be part of the threading future feature.

Given that, though: a shared flag on memories like @bpowers describes is exactly the approach I've been expecting. Addressing some of @jfbastien's questions:

  • Just like we've maintained a symmetry between the memory_type for module imports/definitions and the memory descriptor argument to the Memory JS ctor, I think we'd likewise add a shared flag to the flags field of memory_types.
  • I would think the shared flags have to match exactly for imports (no subsumption either way).
  • So first, a new validation rule would be that if you set the shared flag, you must set the maximum flag. This lets the engine can pre-reserve as much memory as it can up front and never do a moving resize thereafter. Then, when you do a grow of a shared memory, the only difference is that extant SharedArrayBuffers, instead of being detached, stay valid with their byteLength unchanged.
  • It'd be defined completely symmetric with SharedArrayBuffer in terms of how you'd postMessage it and the limitations on what types of workers, etc.
  • Given the above, just like the (non-shared) ArrayBuffer of a Memory.buffer interacts with JS.

@binji
Copy link
Member

binji commented Jan 13, 2017

I agree w/ @lukewagner's answers here. They seem like the most straightforward way of handling this stuff.

you must set the maximum flag. This lets the engine can pre-reserve as much memory as it can up front and never do a moving resize thereafter.

This seems reasonable except for the "as much as it can" part. I assume if we can't allocate enough address space for the maximum amount, then instantiation will fail (and throw), not silently give back less? Otherwise we may as well just accept memory without the maximum flag, because it's just a best effort anyway.

extant SharedArrayBuffers, instead of being detached, stay valid with their byteLength unchanged

Interesting, hadn't considered that we'd want two distinct lengths, but obviously we wouldn't want the byteLength to be changed out from under the JS's view. Though that means that we can have two SABs in the same JS context, referencing the same underlying data but with different lengths. I guess that's not really a big deal.

@lukewagner
Copy link
Member

This seems reasonable except for the "as much as it can" part. I assume if we can't allocate enough address space for the maximum amount, then instantiation will fail (and throw), not silently give back less?

Oops, you're right not an unbounded "as much as it can get" but, rather, "as much as it can get up to maximum". But, already by today's meaning of initial/maximum, an engine is supposed to succeed with any reservation greater or equal to initial. It is initial that is the hard lower bound and the expectation is that big apps (as opposed to small libraries) that generally use "a lot" of memory will just set maximum to something big like 2gb and let the engine grab as much as it can on 32-bit or other virtual-address-space-constrained platforms.

Interesting, hadn't considered that we'd want two distinct lengths, but obviously we wouldn't want the byteLength to be changed out from under the JS's view.

Yeah; my assumption here was that having the byteLength change (esp in a totally racy manner) would break tons of assumptions (inside the engine and in user code).

Though that means that we can have two SABs in the same JS context, referencing the same underlying data but with different lengths. I guess that's not really a big deal.

That's right. You can think of these (S)ABs as just views onto the raw memory owned by Memory.

@binji
Copy link
Member

binji commented May 8, 2017

Closing, as https://github.com/WebAssembly/threads is a better place for this issue now. Please open a new issue there if the current proposal doesn't meet these concerns.

@binji binji closed this as completed May 8, 2017
@domenic
Copy link
Member

domenic commented May 8, 2017

I guess it's not going to be possible to subscribe to just this repository if I want to stay up to date on JS API change proposals? :/

@binji
Copy link
Member

binji commented May 8, 2017

Hm, I suppose not. Are you interested in just the existence of new proposals, or following along with their development? If it is just the existence, we could perhaps have links in the design repo to the current proposals repos.

@domenic
Copy link
Member

domenic commented May 8, 2017

The development of anything that changes the JS API. I guess if a standard label is used across all I can set up a search and monitor that every day or so...

@binji
Copy link
Member

binji commented May 8, 2017

Good idea. I don't have permissions to do that, but I filed an issue so someone who does, can. :-)

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

No branches or pull requests

7 participants