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

Shrinking memory and swap/switch memory or clone the instance it self to 'force' gc collecting the exponentially grown memory #1427

Open
rafi16jan opened this issue Jul 31, 2021 · 3 comments

Comments

@rafi16jan
Copy link

I experimented with wasm2js to see the difference in their design of using and growing the WebAssembly.memory. It turns out that they implement the reallocation and growing logic loosely making it even possible to instead of only growing but also shrinking it, because the implementation is simply: allocate a new and bigger ArrayBuffer and then set the content with the old heap. And the program works just fine when it already have memory freed (zero'd) then shrinked (with the exact steps except the ArrayBuffer is ofcourse, smaller). This is something that doesn't exist yet on current WebAssembly design, and I don't know if there is a difference in WebAssembly's internal implementation that prevents the memory works that way. Because if there's no difference, making the buffer property of WebAssembly.memory to be configurable would solve this issue completely.

Furthermore, I try to find a way to mitigate the limitation of non-shrinkable memory of wasm. The obvious solution is, you guessed it, running wasm on a web worker (or worker-thread on nodejs) so the whole memory and the worker itself can be copied then destroyed easily. While this should be a non-issue on nodejs as every nodejs' feature works in worker-thread, pretty much every DOM api even something comical like DOMParser (which should work fine in worker as it doesn't have any connection to DOM manipulation) doesn't work on web worker. And yes there is some project like worker-dom or comlink that tries to create a bridge to DOM api or any main-thread only api but the majority of it is still WIP or work differently (comlink is async only). Another solution is to keep reference or simply copy the buffer of currently running wasm instance, then unreference the instance (null will work fine) and reinstantiate a new wasm instance but don't run the main function just now, create a TypedArray view of the current memory buffer (instance.exports.memory.buffer), then set the content with the copied buffer in the early steps. With this steps the program will run just fine but... it is so anti-intuitive it feels like using comlink is now more approachable than doing this, not to mention that this process should run everytime we want to shrink the memory.

Even, in retrospect, if WebAssembly supports swapping or switching its current memory with a new one, or, supports cloning an instance so it shouldn't be reinstantiated (preferably synchronous, but async works fine) this would also solve this issue.

@titzer
Copy link

titzer commented Aug 3, 2021

We did discuss shrinking memory in the initial design of Wasm memories. This is a really tricky area in the internal implementation inside of engines. In particular, shrinking a memory is hard to do without introducing many potential security vulnerabilities. Basically, revoking access to some memory at the end of an array buffer is problematic, as there can be inconsistent views of the size of memory. E.g. just consider some code deep in Chrome or FireFox that loops over the memory from beginning to end. (Legacy) code that caches the length between array iterations would then break if the memory is shrunk while it is iterating (either synchronously, in a deeply-nested call, or asynchronously, in another web worker, with a shared memory). Note that said code is subtly wrong if a memory grows, but it is not a vulnerability in the sense such code would just terminate early. This is the reasoning why growing is much safer.

For this reason we settled on the current design where memories cannot be shrunk. We reasoned that applications that did want to shrink memory should be prepared to do the more complicated thing that you outline here.

We've discussed other alternatives to actually shrinking the memory, e.g. memadvise, a call that simultaneously zeroes whole pages of memory and serves as a hint that they can be unmapped, so at least the physical memory (but not virtual memory) can be released by the OS.

@cmuratori
Copy link

We've discussed other alternatives to actually shrinking the memory, e.g. memadvise, a call that simultaneously zeroes whole pages of memory and serves as a hint that they can be unmapped, so at least the physical memory (but not virtual memory) can be released by the OS.

Do these discussions exist transcribed anywhere, for those of us who'd like to come up to speed?

- Casey

@Jamesernator
Copy link

Jamesernator commented Jun 14, 2022

For this reason we settled on the current design where memories cannot be shrunk. We reasoned that applications that did want to shrink memory should be prepared to do the more complicated thing that you outline here.

It seems unfortunate that this has to be done outside of the WASM, rather than having some internal say memoryref with some instructions to swap a memory (particularly with multi memory for partitioning bits off that can be swapped independently).

Such a design would be rather nice for host integration in that things like JS could pass in a memory as an argument to WASM functions, let wasm do some heavy processing or whatever on the memory and just let go of the reference when it's done.

e.g. As a simple bikeshed, something like:

(module
    ;; Declare a memory that is swappable
    ;; Such memories can't have fixed memory locations given well they
    ;; can be swapped, by requiring such a declaration non-swappable memories
    ;; should should not suffer any penalty
    (memory $data swappable)
    
    (func $processData (export "processData") (param $inMem memoryref)
        ;; Swap the data in for processing
        (memory.swap $data (local.get $inMem))
        
        ;; ...process the data here
        
        ;; Once done with the data swap the data for some empty data again
        (memory.swap $data (memory.new 0))
        ;; Once this function is over, the memoryref for the param is no longer held
        ;; in WASM, once released in the host it can be freed safely
    )
)       

Ultimately this would be safer for hosts because they can just release the memory when they release the reference.

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

5 participants