Repository navigation
Add new walkthrough-style explainer - #745
lukewagner wants to merge 15 commits into
Conversation
|
This is fantastic -- I did have one ask though, could the new walkthrough style explainer be multiple files rather than one massive one? Also I do wonder if renaming Explainer.md is a good idea given all the links out there to it... |
|
@vados-cosmonic Thanks! Personally, I think single-page documents are convenient, because you can ctrl-F skip around the page, see your progress as you go down the page, and see the automatic ToC. Also, the CM repo already uses this single-page-explainer convention. If page-load times were bad, that might be a reason to break it up, but it seems to load quite fast. As for renaming old |
tlively
left a comment
There was a problem hiding this comment.
Thanks for the nice explainer. These comments are mostly about ways to improve the exposition, but a few design questions slipped in there, too. I have plenty more design questions that I'll reserve for a better forum :)
| following type which, due to running everything during a module `start` | ||
| function, has no exports: |
There was a problem hiding this comment.
Is running everything in the start function a common pattern? If it's not common, it would be helpful to point that out.
There was a problem hiding this comment.
Although occasionally we have to work around temporary toolchain/CM limitations (including in the Web Preview, due to lack of a CM-level callback type), it is already and increasingly normal. In particular, the CM provides a solution to the core-memory-not-visible-to-imports issue (multiple modules) and thus start functions can serve as ESM top-level scripts (literally when using component ESM-integration).
There was a problem hiding this comment.
And toolchains know how to emit the multiple modules necessary to work around the cyclic dependency problem?
There was a problem hiding this comment.
Yep: wasm-tools component new / wasm-component-ld do this automatically already (using tiny helper modules and call_indirect stubs as necessary). It could be optimized, but it works.
| resource types could be type-imported by modules and then the untyped `i32` | ||
| indices could be replaced by references to the imported type. |
There was a problem hiding this comment.
Couldn't they already be replaced by externref or anyref, even without type imports? Surely those are no worse than i32 table indices.
There was a problem hiding this comment.
That does seem like a much more appropriate type for resources.
Also, has anyone thought of the following: if they are externref, can we get rid of explicit drops for resources? I expect instead the engine's management of the externref could do it on its own (through ref counting or more elaborate GC). If yes, then that could simplify a lot of things, and solve (one of) the biggest difficulties in getting a WasmGC language to target CM (with or without #525). For example, do we even need the own/borrow distinction then? It seems like they are there only for subtle decisions about dropping.
(Of course, we would still need the destructor side of things, i.e., the drop notification on the resource exporting side. But resource users would not need to worry about the whole thing.)
If this hasn't been fully explored before, should I open an issue with a fleshed-out proposal?
There was a problem hiding this comment.
Great point; I forgot to mention externref as an intermediate step; added!.
Based on our experience, if you have a standard linear-memory language, i32 is still rather simpler and more efficient than externref: because support for manually passing around externrefs is still rather limited (and requires source-language support), linear memory bindings are going to immediately table.{get,set}ing the externref anyways to get an i32 that will flow into the wild core wasm, but now there's this extra bit of overhead of (un)boxing up the original host value into an externref and then storing the externref in a table, and also now the GC might be involved.
But yeah, externref is totally being discussed in the context of GC languages in #525. If anyone wants to discuss further, do feel free to add to this gdoc I just created.
rossberg
left a comment
There was a problem hiding this comment.
This is great. Super helpful to be able to understand it without all the WIT idiosyncrasies.
How about including the donut wrapping example? You refer to it several times, and it's almost the only piece missing to make this doc self-contained.
Some suggestions and questions below. Most of the questions are probably outside the scope of this doc, but popped up during my read-through.
| (func (export "greeting") (param $namePtr i32) (param $nameLen i32) (result i32) | ||
| (local $ptr i32) (local $len i32) (local $retp i32) | ||
| (local.set $len (i32.add (local.get $nameLen) (i32.const 6 (; = strlen("hello ") ;)))) | ||
| (local.set $ptr (call $libc-malloc (local.get $len))) |
There was a problem hiding this comment.
If the implementation wanted to avoid memory corruption and do correct error handling here, how would it go about that with the given signature? Would it trap on 0?
There was a problem hiding this comment.
Yep, trap or, if you "know what you're doing" (i.e., you're a programmer who knows API contract, not bindgen), return some valid value that doesn't require allocation like an empty string.
| `borrow` handle. This optimization applies only when passing a `(borrow $R)` | ||
| handle to the component instance that defined `$R` and it skips the overhead of | ||
| adding a handle, calling `resource.rep`, and then calling `resource.drop` to |
There was a problem hiding this comment.
I'm not sure I understand how this composes. What if a function optimised this way wants to transitively lend the borrow further to a function from outside the component?
There was a problem hiding this comment.
If the component defined the resource type itself (such that this optimization applies), it can't use the resource type in imports so it can't pass the received borrow handle to an import, and borrowed handles can't be returned, so I think there is noone that the borrowed handle can be given to.
| mapping the hierarchical name into the language's existing support for packages, | ||
| modules or namespaces. To do this, in addition to the kebab-case **plain names** | ||
| shown above, component names can also be **interface names** which are a triplet | ||
| of: namespace `:` package name `/` package item name. For example: |
There was a problem hiding this comment.
Some package systems are hierarchical, so shouldn't nested paths be allowed?
There was a problem hiding this comment.
Indeed, and some possible use cases for this (on the producer side) have even been discussed and so the text format grammar is already generalized to allow repeated namespace: and package/ names. However, this hasn't been implemented yet (limited resources and all) and so it's still an emoji-gated feature. I added a short note to mention this possibility here.
| resolve this tension, interface names can alternatively be put in an `implements` | ||
| attribute of a plain-named import or export. For example: |
There was a problem hiding this comment.
I have to admit that I'm really confused by how the linking logic is expressed. Some is encoded in the import name, some outside of it with ad-hoc clauses like implements and external-id, some things appear to be expressible in multiple ways. In particular, I didn't get the intended difference between implements and external-id. What does it mean to have both? And what do they default to when absent?
There was a problem hiding this comment.
This commit adds a rationale for what goes in a name vs. in an attribute. This new commit shows both implements and external-id attributes used together and explains the role of each part. It is rather nuanced though and so more discussion in the CM SG is already planned.
be47205 to
bab89bc
Compare
Co-authored-by: Andreas Rossberg <rossberg@mpi-sws.org>
This commit adds a new
Explainer.mdthat attempts to provide a better introductory experience to the CM for folks already thinking at the WAT level by walking through the CM's (pre-0.3) features, starting with a "hello, world" WAT component and then progressively introducing CM features through new examples. The target audience here is CG folks already familiar with WAT and evaluating this as a new spec proposal and perhaps thinking about runtime/toolchain implementations... not general audiences (for that, component-model.bytecodealliance.org is still linked to as the better starting point).@eqrion pointed out that a good explainer should also start with a nice crisp updated-for-2026 Problem Statement, but that's something I think we can iterate on separately (perhaps as part of the first one or two CM SG meetings) so for now there's just a short placeholder section at the beginning with a "TODO" and link to
design/high-level/(which have historically sorta served this purpose).There are also JS API links left as "TODO" that are intended to be resolved by the new Markdown files added in #686, which I'd like to focus on getting merged next, before this PR (at which point I'll rebase and update this PR to link to the new docs); I'm mostly just posting this now to parallelize feedback. Also, in the last CG meeting, @tlively asked if there was any good CM SG "pre-reading" for anyone so motivated and I think perhaps this PR might serve that purpose.