Heya, I'm trying to figure out how the whole component model works, including the canonical ABI (with the goal of compiling my own language to it). I've noticed a few things:
From https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md:
As an optimization, flags are represented as packed bit-vectors. Like variant discriminants, flags use the smallest integer that fits all the bits, falling back to sequences of i32s when there are more than 32 flags.
def alignment_flags(labels):
n = len(labels)
assert(0 < n <= 32)
if n <= 8: return 1
if n <= 16: return 2
return 4
First, that assert kinda confuses me. The whole case of "more than 32 flags" is not handled... anywhere, as far as I can tell. Additionally, why not use 64-bit integers too? They're primitives too. I would also say the same thing about Variant discriminators only using up to 32 bits, but honestly 4 billion variants seems like a pretty reasonable limit.
From https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md:
record-fields ::= record-field
| record-field ',' record-fields?
More generally, it seems like zero-sized types (and impossible types like variants with no variants) are impossible. I guess they're expected to be stripped by whatever generator that makes the WIT data?
I'll probably add further questions to this thread later, but that's it for now.
On zero-sized types you can see the rationale here: https://github.com/WebAssembly/component-model/pull/218
Interesting, thank you!
For flags we recently limited flags to 32 and haven't gotten to go back and update the abi representation
The plan is to increase the limit to 64 in the future and use i64 for the representation
Some minor extra changes are needed in the spec though to fully handle this
Ah, fair enough!
What are "refines"? They're mentioned when storing/loading variants, but not specified anywhere from what I can tell?
It looks like the feature was effectively "disabled" in https://github.com/WebAssembly/component-model/pull/255
Fair enough! Makes my life easier lol
https://github.com/WebAssembly/component-model/issues/414
Haha, I was thinking about opening an issue. Thanks!
I'm not sure I understand components at all, honestly. Assuming a non-component-aware host, can it call functions in a component at all (assuming we manually build the serialization?). To me it seems like a WASM component doesn't even use the same format as a WASM core module; I would have expected an extension to the core module using a custom section that gives the format of the module.
I'm trying to build a component from user code, so currently my thought on how to approach that is to use wasm-encode
to build up a Module, the byte-ify it, but it's decently low-level and I feel like y'all have probably made a better library for this kind of situation, no?
My current understanding is that a WASM component is a wrapper around a WASM core module, and defines a mapping from core module functions to component functions. When a host wants to call a function from the component, it looks at the signature, lowers the parameters into local memory using realloc
(if it can't be easily passed as several arguments), converting its internal representation to the canonical ABI along the way, runs the core function, then gets the return value from local memory, again using the canonical ABI. Is... any of that correct?
And does this mean that any WASM component that uses types that need a linear memory needs to implement a full allocator with realloc
support etc?
I think you're on the right track for a lot of the concepts. The part that may help is that components aren't necessarily a wrapper on a single core wasm module but are generally expected to be composed of multiple core wasm modules and wasm components. This high-level guide painted the picture better for me: https://wasm-components.fermyon.app/encoding.html
I see. Is there a performance loss when joining multiple core wasm modules into one, vs building an equivalent joined core wasm module? Or is it nearly free?
Also, thanks for that high-level guide, that does help.
That depends on what you mean by "equivalent joined core wasm module"; one answer is that there is no equivalent: components can be written in different languages with e.g. mutually incompatible memory layouts and still composed together.
Fair, I meant in the context of creating a component with full control of the whole pipeline. Is it fine enough to build several modules and link them together, or should I instead creating one big module and export what it needs exported?
If I'm understanding your question: there is going to be a little more overhead in calling between two components than a function call within a module but apart from that I think its more a question of how you want to organize and connect your components/modules together.
Yeah, that was my question. I'm guessing the runtime really treats them as two separate modules and chains the calls together?
A non-component-aware runtime would have to do that, yes. I believe that is how jco runs components in the browser, for example. A component-aware runtime like Wasmtime can be a little smarter but Wasm's sandboxing requirements still typically require some memory copies (and other bookkeeping) that wouldn't be necessary in a "native" function call.
Yes, with shared-nothing linking each subcomponent exists in its own sandbox, with the host mediating communication between them.
With the component model, you can use any combination of static linking (everything goes into a single module), shared-everything linking (multiple modules linked together and sharing memory and table(s)), and shared-nothing linking (multiple modules, each with its own memory and table(s)). Each option has its pros and cons, and they can be combined in complementary ways. See also https://github.com/WebAssembly/component-model/blob/main/design/mvp/Linking.md.
I see. Thank you very much! Final question for now: what (rust) crates should I use to generate a core module + a WIT interface? There's walrus
+wit-component
or wasm-encoder
as far as I know (wasm-encoder
appears to be lower-level), but maybe I'm missing some or some aren't maintained very much anymore?
wasm-encoder
seems like a good choice to me, and can be used in either of two ways to produce a component:
wit-component
, followed by wit-component
to wrap that module in a component.wit-component
would otherwise do, such as creating "stub" modules to break dependency cycles which would otherwise prevent linear instantiation.I haven't used walrus
myself. I've heard it's cool, though, and takes care of the annoying details of managing indexes, etc.
Hey also @Sekoia you might want to take a look at the Component Model book -- there are a lot of benefits/extras to components, and that might be a good place to start.
If you prefer a WAT-first approach you can check out some of the tests in the wasmtime
crate:
You can kind of get a feel for how components wrap and enhance/extend/enhance modules.
Obviously as well with all the other links everyone has provided
Last updated: Dec 23 2024 at 13:07 UTC