CG-07-01.md
August 6, 2025 · View on GitHub

Agenda for the July 01 video call of WebAssembly's Community Group
- Where: Virtual meeting
- When: July 01, 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
- Presentation: Introducing the Lime family of featuresets (Dan Gohman, 5 minutes)
- Discussion: JS interop design (continued) (Thomas Lively, 55 minutes)
- Closure
Agenda items for future meetings
None
Meeting Notes
Attendees
- Dan Gohman
- Manos Koukoutos
- Thomas Lively
- Derek Schuff
- Conrad Watt
- Sébastien Doeraene
- Paolo Severini
- Emanuel Ziegler
- Nick Fitzgerald
- Yuri Iozzelli
- Chris Fallin
- Erik Rose
- Bailey Hayes
- lukasdoe
- Robin Freyler
- Luke Wagner
- pmp-p
- Jakob Kummerow
- Francis McCabe
- Oscar Spencer
- Yury Delendik
- Heejin Ahn
- Ryan Hunt
- Thomas Trenner
- Patrick Dubroy
- Matthias Liedtke
- Ben Titzer
- Zalim Bashorov
- Daniel Lehmann
- Richard Winterton
- Brendan Dahl
- Andrew Brown
- Ricky Vetter
- Johnnie Birch
- David Degazio
- Sean Jensen-Grey
Proposals and discussions
Presentation: Introducing the Lime family of featuresets (Dan Gohman, 5 minutes)
DG: We have developers who want to target a stable set of features. Wasm 2.0 has reftypes and SIMD, and folks who don’t want to implement all of that are targeting a smaller subset.
People like academics and embedded developers are staying with Wasm 1.0. Wasm 1.0 and 2.0 etc. are points in time on the standards process, not feature sets for specific use cases.
It’s just a list of features, not a standard with a spec (or a vote), but it’s a stable set that is documented in tool-conventions
MVP + early simple proposals + Bulk-memory-opts (memory.fill + memory.copy only) and call-indirect-overlong again pick features based on use cases rather than standards process. For engines that target MVP it’s much easier to add than adding full support for reference types and mutable tables.
CW: if you have memory.fill, you must have passive segments?
DG: memory.init uses passive segments, we don’t have that.
call-indirect-overlong is a tweak included in the reference types proposal that we could have included earlier with more foresight. It allows full LEBs in the binary encoding of tables.
CW: do you have any info on which use cases in industry were asking for this?
DG: academia (for research focused on one set of features) and embedded users who ship devices with a subset and want to support that long term.
Questions: Should lime include more features, e.g. EH, SIMD, tail-calls, etc?
Lime1 is now stable, but we could also make a Lime2.
What about completely different subsets? We could make more different subsets. We can let succes be determined by adoption rather than standardization?
Should it become a standard? Let’s wait and see if it becomes popular, and if so, maybe we can define a profile for it.
CW: a lot of widely used tools will need to know about it. How will we handle disagreements about e.g. what should be in lime2
DG: not sure, might be an indication that we should bring it up for discussion in the CG, there are several options, but probably bring it in
CW: makes sense, I think if there starts to be disagreement we should discuss and vote on it in the CG, i think this makes sense as more lightweight if there's no controversy
CW: should we consider a lime profile?
DG: I think we should think about it, but first define the subset, and I think the best way to do that is based on adoption rather than deciding up front.
BT: This is mostly a subset of features and not a subset of behaviors, correct? I.e. disallowing a certain behavior from an instruction that has nondeterminsm?
DG: yeah we didn’t do that. The only test we have so far is just validating instructions, on the assumption that you are already a conforming implementation. We have talked about subsetting nondetermnistic behavior, but aren’t doing that now.
CW: Does binaryen need to know about lime to avoid introducing bad instructions?
TL: currently binaryen just gets a list of features in use from LLVM, and doesn’t add to it when it optimizes.
Discussion: JS interop design (continued) (Thomas Lively, 55 minutes)
Continuing same slides from last time, now we talk about the 2nd problem, populating the prototypes with methods. We want to have some methods in wasm, but you might want to have other prototypes in JS, so it needs to be configurable.
We expect an order of magnitude more methods than prototypes. So problem 2 is much more important for performance since there’s so much more work to be done. We also talked about these experimental results. Nothing has changed there, no new experiments yet.
We have some idea of how the prototypes will be associated with the wasmGC objects, the imperative approach is, if you run the JS to set up all the prototypes, it’s very slow. So the nest 2 experiments were, how can we do it better. We’ll talk more in detail about that, but even a small detail e.g. a name vs an index can have a big perf/size change. And then the direct approach, punch through the abstractions, gets pretty close to the baseline in size and perf.
Straw poll results: 1A was th eonly option with nobody against, and no other options had more in favor. Not binding, but it’s looking pretty good on consensus.
So now PRoblem 2: we have prototypes, they’ve been associated with descriptors, now we need to install the methods on the prototypes. (4-Tuples slide)
We think the array version of 2A could be made pretty fast.
CW: wrt the general idea of importing. If you knew ahead of time what it was and could optimize, could it be made fast?
TL: if it’s just one function call like this, the unoptimized call overhead doesn’t matter. If you do it one at a time, and your start function has 10k calls, we don’t think that will be very optimizable
RH: is there a variant between these 2 where you create the arrays and iterate over them in wasm and call each time. No code size issue, you just do it all in a loop
TL: yeah much better for code size, we’d still worry a lot about performance, the loop wouldn’t be tiered up, youd have unoptimized calls. Haven’t measured, but are concerned about that.
RH: i get the concern about it not being tiered up yet, but when I think about how I”d implement a VM function to set a bunch of properties, it’s not too different from what I’d do in wasm code.
JK (chat): Conrad: we certainly would expect the function to be a standardized compile-time builtin. But as a general point, it doesn't usually make sense to optimize the start function, because it runs only once, and optimization is slower than unoptimized execution.
BT (chat): I suppose the start function is subject to OSR tierup if it has a hot loop, though.
TL: yeah, the imperative experiment with strightl line JS code was really slow. Maybe the wasm start function would be better, but im guessing not a lot better, i bet it would be a noticeable difference.
CW: is there an intent to experiment with the combined function?
TL: that’s one thing I’m hoping to get today is feedback toward what we should experiment with. So no concrete plans for an experiment yet but we’d like to know what would be most useful.
LW (chat): Also, if these are JS built-ins, they can be inlined, avoiding call import overh
JK (chat): Ben: if you have OSR at all... we still don't. Luke: if it's a single "configure everything" call, call overhead doesn't matter :)
LW (chat): Jakob: I’m thinking in the context of Ryan’s suggestion where the loop is in wasm.
TL: Option 2B. This is what’s documented in the overview today We had data for that, it works well, much faster than straight line JS. we got feedback that we don’t like it when custom sections do things. The way to think of it modularly is that the custom section declaratively defines a wrapper module around the core module that provides imports in the form of newly created prototype objects. The core module instantiates with those imports, and it takes the exports and uses them to populate the prototypes. So it adds behavior before and after the instantiation of the core module but doesn’t change instantiation itself. So to make it faster you can use indices instead of names. It saves a bunch of code siae and makes it noticeably faster. One downside is that in the JS api there’s currently no way to get a stable list of exports in index order. In JS today you iterate through the exports but due to JS the order in which you iterate is not necessarily the order they were added because it puts number-ish things first. So that’s annoying. Maybe we want to add a JS API to get an ordered list, it doesn’t seem bad.
RH: i recently learned that the embedder interface defines exports and imports with indexes. Had assumed based on the JS API that it was just names. In practice to tools preserve import and export order?
TL: first, there’s also teh precedent in the C API also uses indexed exports. And lots of engines use that. For tools at least binaryen does preserve the order of exports in general. Currently there’s a known bug in the text parser where we don’t do that right. But I would fix that. So I dion’[ot think this would be a big burder on the ecosystem.
CW: so the exported :”foo” function, is that meant to take an externref and put it on the prototype?
TL: you have a prototype thats imported, and it was created either because the custom section said to create it, or its an existing object. So its importe as the prototype, and there’s an exported fn “:foo” and you install it on the prototype as a method that can be called. So its receiver is the first argument. So when i do myWasmStruct.Foo(), its called
CW: so the idea is taht the engine merges them all together
TL: if so that would be observable. So the engine would do it before instantiation, then a regular instantiation, then populate the prototypes afterward. So its still faster.
CW: is that something we could epxlain in pure wasm? So you have to call the start function, and you do get to merge the installation after you have the exports… the point is that the installation happens after you finish running the start function and return to the host
ZB (chat): is "index" is index in export list? Can we use index of a function inside wasm?
JK (chat): Zalim: yes, it's an export index. Technically we could use the internal function index, but spec-wise that's likely a non-starter: from the outside of the module, we can't refer to functions by their internal index. (That would be the more intrusive design that folks aren't happy with.)
LW: would wasm-ld need to understand this section when it statically links object files that have their own fragments?
TL: wasm-ld has no idea what a wasm-gc struct is. So nothing works yet. But if we did that, wasm-ld would already be doing things like synthsizing type sections from scratch. And today it creates name sections from scratch based on merging. So this wouldn’t be new.
LW: true, but thi sis now a custom section specific to JS. so that’s new
TL: yeah, a little coounterfactual since wasm-ld doesn’t know anything about wasm-gc. So.. consider wasm-merge. It’s a linker-like tool that handles wasm gc . it would need to know how to merge these. It would be a standard section (Even if not the core spec), and like any custom section, a tool would need to know about it. So yeah tools would need to knwo about it, i just don’t think it’s a problem
CW: in case of a malformed custom section, would it just keep executing but in a slow way?
TL: my preference would be for a malformed custom section to be an error in the JS API, instantiation would fail (early and loudly would be best IMO). you could just ignore the custom section but you wound’t get the behavior you want at runtime. This would only be in the JS API, not the core spec.
TL: option 2D. Exporting all of those things just to install them on prototypes is unnecessarily expensive. So we could say that if the prototype “consumes” the export, we could leave them off the exports object. Again it’s a bit like a wrapper module.
Option 2E: change the core spec to allow duplicate export names. If we refer to them by index the names can be empty, that saves code size. Or use the export name to give the JS property name that should be used to install in the prototype. Just moves the information but saves code size by moving the name from the custom section to the export section.
Option 2F: if we don’t want custom sections to be magical, move to core wasm. So, put the info in the export section instead. Or, we could have an “embedder section” that only exist on the JS embedder, but it’s not a custom section anymore.
CW: is the difference between this that you’re giving up on trying to make this like an embedder module?
TL: yes, your’e still exporting things, but now exporting with all the information you need, rather than trying to specify that info separately
RH: on the 1st 2 options: (2A) the later options, when you use the modular thing you have to actually go into the export section and you are reducing that overhead? A nice thing about doing the loop in wasm, you don’t need to export the functions that you install, you could just use an elem section and don’t need to worry about the duplicate export. It escapes via function references, but it’s not in the export section so you don’t have that overhead
TL: right, instead of exporting functions, you pass references to them to this API. so you need a declarative segment or otherwise take references, but dont’ need to export explicitly.
JK (chat): Ryan: yes, fully agreed. the "configure all" variant of Option 2A is pretty elegant and efficient :)
TL: thoughts on the options?
CW: I think you could argue me all th way to 2D, but it seems like the “configure all” version of the API solves the problems an dis a smaller footprint on the languages. I hope that experiments show that it’s fast enough, but if 2D were way faster I could probably live with it
SD: another benefiit of the imported API is that if the source wanted access to the methods from its start function, it could have it. It’s not possible with the oither solutions?
TL: yes, true. Then you can call the API anywhere. But remember that for the modular approach,s these are just optimizations over exporting things and doing it in JS
Is everyone here happy with the “configure all” API and if that’s fast enough, go with that?
RH: I’d still be interested in comparing against a loop in wasm with all the calls? Would be a good justification for why we did it this way. But if it proves really slow, the I”m open to configure-all.
CW: “configure-all” is still modular in the sense that you can have multiple calls to it, operating on different objects, so you can compose that way?
TL: yes, if you merge modules each with a configure-all call, it should still work?
CW: so we can do the loop experiment by calling configure-all in a loop?
TL: yeah as long as we aren’t materializing extra arrays, etc certainly nothing stops you from using it to configure one thing at a time.
BT (chat): Is 2E somewhat orthogonal to this problem? AFAICT it’d be generally useful, but IIUC the blocker is the JS exports object?
JK( chat): Ryan: we might in fact want both options: "configure-all" as the main workhorse, "one-method" as escape hatch for the few use cases that might need more flexibility.
RH (chat): Yeah, I could see that happening as long as configure-all pays it’s weight for the extra implementation burden.
JK (chat): Ben: 2D and 2E are efficiency improvements for 2B/2C mostly
JK: one interesting property of 2A, we could tweak it to subsume task 1 . it could consume descriptors instead of prototypes, so it create and installs the prototypes both at once. We want the prototype to be immutable after creation, so if there’s a way to install that imperatively, we’d need runtime checks. But it would make it unnecessary to have any special solution to task 1 and you could use 2A or both.
TL: yes you would need runtime checks though, so you can’t observe the prototype changing. Also there are constructors. They need to appear on the export object and have some properties too. So there’s no way around adding constructors to the exports objects. And this configuration API probably would not let you do that. So we’d need something else to configure exported constructors.
LW: if initial experiments still show overhead with 2A, there is still design space to explore, e.g. the overhead of the string constants, building the array with them, etc. we could have configure-all take a comma-separated list of names. Put all the info there. And there are otehr variants possible.
TL: yeah the idea there was just “what if we just took all the bytes form the custom section and passed them to the API?:” so there are other options there too.
CW: for the “all” version of 2A, are we thinking that’s like a compile-time import like string buildings, or just a regular import?
TL: i think just a irregular imports, we only expect it to be called once.
RH: I heard there was hope you could elide array allocations? In that case you’d want it to be a builtin.
TL: yeah in that cae if you want it to be recognized as doing that, it could be worth it. Either way I assume there’d be like a “webassembly.configurePrototypes” that could exist.
JK: I think you’d want ti to be a builtin if you want to let engines generate code before seeing the imports. If engines are fine only ocmpling after seeing the imports, a regular one should be fine
RH: if there’ smor ethan configure-all, e.g. set property, those are more likely to want the highest performance. So you’d want somewhere to put them all, e.g. a collection of builtins.
TL: would also be like SD”s proposal for more JS builtins. They would be like any other JS property setters.
CW: time check, do we want a straw poll?
TL: not sure we need a straw poll, the imported API seems to have a lot of interest, we could prototype that as a next step.
CW: can we maybe straw poll that, to see if any surprises?
TL: sure. Poll on whether you would support using the imported API?
| For | 17 |
| Neutral | 3 |
| Against | 0 |
TL: seems like a clear winner if we are happy with the performance.