CG-04-22.md
May 8, 2025 · View on GitHub

Agenda for the April 22 video call of WebAssembly's Community Group
- Where: Virtual meeting
- When: April 22, 16:00-17:00 UTC (9am-10am PDT, 18:00-19:00 CEST)
- Location: link on W3C calendar or Google Calendar invitation
Registration
No registration is required for VC meetings. The meeting is open to CG members only.
Agenda items
- Opening
- Proposals and discussions
- Require maximum for Memory resizable buffer integration (PR) and max size question (Shu-yu Guo, 15 minutes)
- Closure
Agenda items for future meetings
None
Meeting Notes
Attendees
- Thomas Lively
- Sean Jensen-Grey
- Derek Schuff
- Adam Bratschi-Kaye
- Nick Fitzgerald
- Ricky Vetter
- Erik Rose
- Paolo Severini
- Yuri Iozzelli
- Oscar Spencer
- Shravan Narayan
- Conrad Watt
- Yan Chen
- Ben Visness
- Ryan Hunt
- Yury Delendik
- David Degazio
- Colin Murphy
- Alex Crichton
- Shu-yu Guo
- Deepti Gandluri
- Manos Koukoutos
- Chris Woods
- Jakob Kummerow
- Brendan Dahl
- Luke Wagner
- Heejin Ahn
- Emanuel Ziegler
- Zalim Bashorov
- Michael Ficarra
- Andrew Brown
- Julien Pages
- Sam Clegg
- JJ
- Johnnie Birch
- Richard Winterton
- Keith Winstein
Proposals and discussions
Require maximum for Memory resizable buffer integration (PR) and max size question
SYG presenting slides
BV: When you call toResizableBuffer, does that affect what the .buffer property returns?
SYG: Yes, on subsequent calls. The first time you call it it changes the underlying buffer to a resizable one, detaching the old one. So every time you call .buffer after that, you get the new one. .buffer always returns the current/most recent.
CW: When we discussed the version for shared memories, does it alias?
SYG: It allows aliasing, and in fact SABs cannot be detached. So toResizableBuffer on a shared buffer still replaces the current cached buffer, but it doesn’t detach, so you have an aliased growable SAB. The old buffer still has a fixed length, so you can’t see the whole memory.
BV: Why do we have these methods that change the mode of the buffer property rather than configuring it in the constructor?
SYG: When you create a memory, you say , then give me a resizable buffer? I don’t think we discussed that?
CW: I think we did discuss it. The issue is that if you have a WebAssembly memory declared inside a module, then you don't have an opportunity to declare resizability for JS.
SYG presenting Q1
SYG: The goal of JS requiring a max is to always allow a non-copying implementation, where you reserve virtual memory but don’t commit until it’s used. That requires a max size. JS arraybuffers have that requirement; Wasm doesn’t, it would be nice if it did. In V8 we try to grow in place and use this reserve+commit strategy if possible.
Should mem.toResizableBuffer() throw if no maximum was passed when creating mem?
I want to incentivize passing max sizes and want the JS side to be less surprising.
DS: In Emscripten today, the benefit of having resizable buffers is in the shared memory case. Without threads, buffers only need to be refreshed on growth. When you use shared memories, there is already a maximum size. So I don't know that this would incentivize developers.
SC: There are issues when developers copy the views and they become stale, so I think this would be helpful even for non-shared memories.
CW: The ergonomics of refreshing views seem bad, so I think using resizable buffers would be useful.
RH: I think that requiring a maximum is reasonable here. Not super strong but it aligns the APIs, and it’s also how we implement it.
SYG: Does something this small warrant a vote?
CW: We can do unanimous consent, but if there’s an objection, we can do a full vote
TL: Agreed. Are there any objections, should we discuss more?
TL: Looks like unanimous consent.
SYG presenting Q2
I don’t think it’s good to make it one byte smaller, there are places where we want it to be a multiple of page sizes.
RH: We ran into this as well. It becomes even funnier with Memory64. Even if you're representing the byte size as a double, you can go beyond the max safe integer. Even representing this as a 64-bit integer doesn't solve the problem.
The only thought I had, is we do have an implementation limit that specifies that the max that a JS embedder can create is 16G. It’s a little odd to have it be different from what the type of the memory is, but it’s something that implementations can agree on.
CW: Can you explain that part for 64-bit memories again?
RH: The clamping on max byte length? Basically if you have a memory64 and page sizes of whatever max, the JS API specifies that the memory can never actually grow above 16G. So we could change how we report the max byte length on RAB, so that we never report more than 16G
CW: Would we have forward-compat concerns if we wanted to relax this in the future and allow larger 64-bit memories in the future?
RH: Good question, not sure, i would hope that code would continue working if the max byte length would just increase. I do hope eventually we could get to where we could increase it.
CW: If we did that for 64-bit memories, would we want to have a limit of 65535 pages similarly for 32-bit memories?
SJG (chat): Limit to 65534 pages?
RH: I guess this doesn’t solve it for 32-bit memories because our implementation limit there is 65536. We have a workaround for the size_t problem but it doesn’t help generally.
CW: It seems like Shu’s 2nd solution fits pretty well, would that work for firefox?
RH: I don’t think we have an implementation issue with that, I was just trying to find a more principled way of describing why we’d do this. And it’s just that we have a spec-defined limit on how big it can get. But if we are just clamping because of an integer representation issue, it seems odd.
BV: We might also run into this with custom page sizes, where even the core ability to represent the size could be impacted by this (e.g. if you have one-byte pages).
TL: Were you saying that even if you represented it with 64 bits in the implementation, the custom page sizes would still cause a problem?
BV: My understanding is that the instruction returns the size in pages rather than bytes which means that what memory.size returns always fits ina 64 bit integer. But with custom page sizes = 1, we hit the same problematic overflow case.
TL: A similar problem, although maybe we could punt it to the custom page sizes proposal.
BV: Yeah it could be fixed by the limits we impose, either in the core spec or the JS API or whatever.
BV (chat): I realize I may be muddying the concerns a bit, and for that I apologize, but we've been tripped up on the interaction between core spec limits, JS API spec limits, and implementation limits many times before, and I want to make sure they're all considered (especially in light of other proposals)
CW: That would be my expectation. We are already allowing 4GB 32-bit memories, so there isn't a great spec limit to use here.
YI (chat): Isn't the engine going to fail to create a 4GB memory in practice anyway on a 32 bit system?
JK (chat): Yuri: yes, absolutely
SYG (chat): It's going to fail to actually grow to such a size, but it might not fail when creating an initial memory with such a max size
BV (chat): Yes, the max size of memory is a concern, as is the result type of instructions like memory.size regardless of architecture - even if it all fits inside a size_t in the implementation
DG: Is there actually a problem with throwing? This is only when we try to change it to resizable? Are people objecting to growing at all? I don’t see a principled way to do it. If we just restrict the problem to 32 bits, there could be an implementation where growth is limited. Ie. could we just throw in this specific case.
SJG (chat): +1 on Deepti's proposal
CW: I guess if a producer just happens to create a module of the large size, they’d see throws when it loads here.
DG: that’s a trivial fix on the application size, to switch to a representable maximum
SYG: I guess you’d have to provide a max, so who would hit the error: users who are providing the spec-ed max. Are there a lot of users doing this? No existing users will hit this yet because you’d only throw when you try to convert to a resizable buffer.
CW: maybe we could just do what DG suggested then, throw in that case.
TL: Unless you’re using shared memories, there’s no max supplied by default. So we’ve said that users need to supply a max from Q1. So what’s the cost of just using a 64 bit size here? This seems less disruptive?
SYG: It’s super disruptive in the implementation because this gets passed around to a lot of places in the code and there’s lots of arithmetic. Updating all of that plus the extra memory cost didn’t seem worth it.
TL: So you're proposing this limitation only on 32-bit platforms?
SYG: Yes.
DD: A note on that; isn’t the whole problem that creating a RAB view to one of these is also a resource exhaustion problem, i.e. creating a view to this on a small platform, i.e. virtual address space? If we assume that RABs need to be continuous and have the size in advance, it seems you’d need some platform-specific failure mode anyway, so throwing on conversion seems reasonable.
SYG: I think it is the performance expectation that JS resizable buffers work like mmap and the design encourages that strategy. It's also spec compliant to copy on grow, but that's not desirable.
LW: Just wanted to check: is the problem only with the max byte length field?
SYG: The actual byte length field, it’s not really possible to actually grow that much on 32-bit
LW: And the max is speced to be exactly what you passed to the JS AB constructor?
SYG: Right the inspectable property will be what you passed to the constructor.
LW: So if internally you pass a huge length and it does a best-effort reservation. So the internal-max can be less than what you passed. Is there a principled argument that we should allow the probed max length to be less than what you originally set to better reflect the internal max?
CW: I think the kneejerk reaction would be that it would make it non-deterministic, you’d probe the max length and you’d just get whatever the engine was able to allocate.
LW: Which you could find out anyways by trying to grow. It's more cheaply observable if it's in the property.
BV: Even then there’s no guarantee that you can grow to the internal limit, depending on other things.
LW: Sure, it would still be a hint. I just want to relax the restriction that the reported max and set max have to be equal.
SYG: it’s possible to relax it for WebAssembly.Memory because they are created for wasm. For JS arraybuffers we’d need a normative change in TC39. for Wasm.Memory, because it’s so cheaply probably, i’m not sure leaks additional bits about the implementation.
CW: I don’t want it to be less deterministic than JS, if there are 2 options, I’d rather have the more deterministic one.
LW: We’re saying a wasm module with a max l length bigger than it will throw on 32-bit architectures
CW: My understanding of the throw change would be platform-independently throwing if creating 65536 page 32-bit memories.
SYG: That's not what I originally thought, but we could make it architecture independent; I hadn't really thought about that, but we could make it more deterministic to have it throw for 65536 pages regardless of architecture.
LW: Is it the case that if you have a memory64… is that implemented on 32-bit architectures?
All: Yes.
LW: So is the max byte length field for memory64 on a 32-bit architecture: is there the same problem?
SYG: I would imagine so, yes. It should be a worse problem.
CW: I think the question is: on a 32-bit architecture, do we try to implement the max size of memory in a 32-bit size_t?
RH: It’s complicated. Generally we try to keep them in units of pages to try to avoid the overflow issues. We generally only convert to byte lengths for the current memory size and after validation. So we’ll OOM before we grow beyond what we can represent. For max, we have the concept of the source max length (in pages) and the implementation-defined “clamped” max length. Then we validate that before converting. For JIT code and bounds checks, I forget what we do, but we have some sort of wide field there.
CW: So the relevant extension of the current problem to Memory64: Is there a plan in JS to create 64-bit resizable buffers?
SYG: Full 64-bit? Not that I know of?
CW: So you could imagine a version of this problem transferring to 64 bits, with a combination of features?
SYG: I don't think it's been well-considered. There is no proposal on the JS side for buffer sizes other than doubles / numbers.
CW: Do i understand right that if you have 64 bit Wasm memory, that you won’t have a toResizableBuffer method?
SYG: I wasn’t sure about the interaction with memory64; is it part of the standard?
RH: It’s stage 4 and we ship it.
SYG: How do you go over max safe integer?
RH: We just clamp it, and figured we’d run into it. I guess that’s now.
LW: … Is it defined to throw? (TODO: what?)
SYG: You can throw non-deterministically. I think the implementations can. I think the spec allows it for a copying implementation under the hood?
CW: We could naturally extend the 32-bit throwing solution that Deepti proposed to 64-bit memories. First we need to answer the question of whether 64-bit memories should have a toResizableBuffer method. If "no", then there's no problem. If so, we could say that if you call it on a memory whose size is too large you could get the same throw you’d get on a 32-bit system.
LW: For 64-bit its normal to attempt to set the length large, higher than 4G, so that throwing might make toResizableBuffer not very useful on memory64
CW: What do you expect to happen if you try to make a resizable buffer of a 64-bit memory.
LW: It won’t be able to grow that large, so it’s really just what we say happens when you try to read the max.
SBC: Is this only for 32-bit architectures?
CW: My idea was to even do this on 64 bit architectures.
SBC: I would certainly hope we could get a bigger resizable buffer than 4G on 64-bit arches.
CW: You definitely won’t be able to get one bigger than 4G with the current JS semantics.
SYG: The limitations around size_t are only on 32 bit machines.
CW: But you don’t have more than 4G RABs on 654 bit machines either?
SYG: The max limit is bigger on 64 bit machines.
TL: Maybe it would make sense to get this in writing on an issue. There’s a lot of different solutions and things floating around. It might make sense to get this organized and written down.
SYG: yeah Im not sure we can come to a decision now, there are a lot of ideas. I wil write my understanding of the options that were floated, i.e. throwing, clamping, different ways to expose that.
CW: The big missing piece that we haven't considered is the interaction with memory64 with resizable buffers.
SYG: so I'll consider question 1 answered and get that speced, and we’ll work on question 2 in the meantime