Description
Problem
WebAssembly.Memory
instances are getting a toResizableBuffer()
method per #1292.
Resizable ArrayBuffers have a maxByteLength
property. For 32bit memories, the maximum spec byte size of a WebAssembly.Memory
is 65536 pages, or 2^32 bytes. For 64bit memories, the the maximum size can easily be > 65535 pages.
The problem is any max sizes > 65535 pages are unrepresentable in 32bits as bytes. V8 and SpiderMonkey use size_t
to represent lengths for ArrayBuffers, and on 32bits, size_t
is only 32bits.
For 32bit memories, the only unrepresentable value is the spec maximum of 65536 pages. For 64bit memories the problem is worse, as the whole point is to have larger memories.
Other interesting properties you might care about:
WebAssembly.Memory
does not require a maximum for unshared memories;ArrayBuffer()
always require a maximumWebAssembly.Memory
's maximum is not inspectable by user code;ArrayBuffer
s' maximum is inspectable
Possible solutions
Rejected proposals
1a. Clamp maxByteLength
to a single, arch-independent value
If mem.toResizableBuffer()
is called, clamp the returned ArrayBuffer
's maxByteLength
to some single, arch-independent value, like 65535 * 65536.
Pros
- Architecture independent
Cons
- Significantly reduces utility for 64bit memories on 64bit architectures
- Mismatch with JS API where the maximum passed to the
WebAssembly.Memory
constructor differs from themaxByteLength
property.
1b. Clamp maxByteLength
on 32bit architectures only
If mem.toResizableBuffer()
is called, clamp the returned ArrayBuffer
's maxByteLength
to 65535 * 65536 only on 32bit architectures.
Pros
- Minimal change
Cons
- Architecture dependent: usage of 64bit memories need to be aware if it's running on 32bit or 64bit machines. FWIW this awareness is already required for pure JS usage of resizable buffers.
- Mismatch with JS API where the maximum passed to the
WebAssembly.Memory
constructor differs from themaxByteLength
property.
2a. Throw in toResizableBuffer()
if maxByteLength
exceeds a single, arch-independent limit
If mem.toResizableBuffer()
is called, and mem
's maximum byte size exceeds some single, arch-independent value, like 65535 * 65536, then throw a RangeError.
Pros
- Sidesteps the question of matching the JS API
Cons
- Significantly reduces utility for 64bit memories on 64bit architectures
2b. Throw in toResizableBuffer()
if maxByteLength
exceeds 2^32 on 32bit architectures only
If mem.toResizableBuffer()
is called on 32bit architectures, and mem
's maximum byte size exceeds 65535 * 65536, then throw a RangeError.
Pros
- Sidesteps the question of matching the JS API
Cons
- Architecture dependent
3. Change spec to throw early when constructing WebAssembly.Memory
s with unsatisfable maximum sizes
If a maximum is passed to WebAssembly.Memory
that is always unsatisfiable, e.g. > 65536 pages on 32bit architectures, throw a RangeError. For backwards compatibility with existing code, 65536 still needs to be accepted.
Cons
- Architecture dependent
- Against the spirit of the Wasm design of max size (?)
4. Change implementations to accommodate size values > 2^32 - 1
Pros
- Transparent
Cons
- Complexity in runtime implementations, as lengths flow into many arithmetic operations, with possible knock-on effects (e.g. on 32bit architectures, optimizing tiers can no longer assume byte lengths from TypedArrays and ArrayBuffers are always 32bits)
5. Have the resizable buffer report an engine-determined maxByteLength
(suggested by @lukewagner)
If mem.toResizableBuffer()
is called, the returned ArrayBuffer
can have an implementation-defined maxByteLength
that is smaller than the requested max size passed to the WebAssembly.Memory
constructor. This aligns more closely with Wasm memories' concept of max as a hint that the engine can decrease as it probes.
Pros
- Trivial implementation
Cons
- Nondeterminism
- Mismatch with pure JS usage of RABs. But there is an argument to be made that the interpretation of maximum is just different between pure JS uses of RABs and Wasm memories. Pure JS uses of RABs are varied, while the "have the engine probe for an actual maximum" heuristic makes sense if you're allocating a Wasm program's heap memory and you want as much as you can get.
- Doesn't compose as nicely with the decision made in [js-api] Throw in toResizableBuffer if memory has no max #1871. I think if the CG chooses this option, we should reverse the decision in [js-api] Throw in toResizableBuffer if memory has no max #1871.
6. Have maxByteLength
be Infinity
for Wasm memories
(suggested by @eqrion)
If mem.toResizableBuffer()
is called, the returned ArrayBuffer
will have Infinity
as the value for maxByteLength
. Also reverse the decision in #1871.
Open question on what mem.type()
would return.
Pros
- Sidesteps the representation issues
- Deterministic
- Captures the difference in interpretation of "max" between Wasm memories and JS uses of resizable ABs
Cons
- In some engines (V8), resizable ABs still will have the implementation constraint of needing to always grow in place.
Infinity
may give the wrong intuition.
7. "Lie" in the maxByteLength getter
(after discussion with @eqrion)
See #1895 (comment) for details.
Pros
- No representation issue for the actual max
- Deterministic