What's the current status of the Reactor vs Command distinction in Preview 2? My vague understanding for Commands was that a Command instance would have its "main" function (and no other exports) called exactly once. Is that the expectation for wasi:cli/run
? (and if so it seems like that should be documented in either that interface or the wasi:cli/command
world)
Related to this question, is wasi:http using the reactor mode? If so, could we add an exported setup()
function to wasi:http/proxy that register callbacks at the initialization phase?
:point_up: This is actually (part of) the context for my question: how should reactor initialization be done prior to the implementation of start defs.
re: https://github.com/golang/go/issues/65199#issuecomment-1905205827
there isn't an export for reactor-style wasm programs
What I wrote there is actually wrong; there currently is no initialization mechanism for user code; we can't use the component-model start function, and there is discussion of WASI adding an export for this purpose.
"no initialization mechanism for user code"
Is there an initialization mechanism for the runtime or non-user code?
There is a component-model start function, that runs on instantiation. Code that doesn't need to call imports can use it. The thing I had forgotten was, that start function is not currently permitted to call imports, which means it wouldn't be friendly to give it to users and say "here, write your init code!" if it's going to trap as soon as they call an import.
But, I just talked about this with Luke yesterday, and he's now thinking we can remove the restriction on calling imports. Assuming we do do this, then what I wrote in that issue would become correct again, and we could use the component-model start function for any purpose.
Got it, so the component-model start happens too early in the lifecycle to be used by a runtime which is likely to call imports.
What are the considerations if that constraint is relaxed?
The start function runs automatically on instantiation, so if it can call imports, it means that component instantiation can evoke I/O. The thinking was, there may be situations where users want to instantiate a component, but without letting it do any I/O until a later step.
In Go’s case, the runtime picks up a few things from the local filesystem (assuming it’s readable), and user init
code can do I/O
Assuming that wasi:filesystem
and wasi:cli
and its dependents would be initialized prior to a user component?
In many cases, "the filesystem" is likely to be virtualized, so the resulting component wouldn't actually do that I/O. But yes, not everything will work that way.
Maybe a better question is: are components initialized in order of dependence?
Also yes, component dependencies are acyclic, and initialized topologically.
Somewhat related to Go and the Component Model: we’re currently using wasm-tools component embed
and wasm-tools component new
to create a component from a Wasm module generated by the TinyGo compiler.
Is there a way to differentiate which realloc
gets called a component export or import?
I don't think so; the canonical ABI just has one realloc
.
I’ve spoken with Luke about this, how cabi_realloc
might interact with the Go GC, given that it allocates byte slabs, which might not be the correct GC shape for say something like a string
which contains a pointer.
the canonical abi has one realloc per import or export function - you can assign a different realloc function to each
we have some "cheat codes" to do this for us for the preview 1 adapter in wasm-tools component new
but no generalized support fori t
Luke commented about a cheat code for making allocations cheaper for I/O, e.g. hijacking the next call to realloc
before calling read
on a stream, to enable BYO buffer.
so, if specializing per import/export fn is enough, that may be possible without spec changes, and we just have to encode it somehow in the module and teach component new
to do that
oh, hmm, thats more/different than i was thinking
but it may also be possible
thats some bookkeeping youd need to do inside your module
vs exposing many distinct reallocs and assigning them to different import/export lift/lowers is something your module would expose and component new
would do the wiring for
Going back to the Component Model start for a sec, can wasm-tools
wire up a module _initialize
to the Component Model start section?
in theory yes
but ill admit i dont fully understand the implications of doing so at the moment, some things in the above conversation were new to me so id need to make sure i really understood what was going on first
(maybe cabi_initialize
is a better name than _initialize
but thats bikeshedding)
Going back to the Component Model start for a sec, can wasm-tools wire up a module _initialize to the Component Model start section?
Yes, that's something wasm-encoder
can do if one were encoding a component manually, but it's not implemented in wit-component
when converting a core module to a component.
Additionally, I'll point out that component start sections aren't implemented in Wasmtime as they require fully supporting value import and exports to implement, which are also currently not implemented.
can we make a restriction on the spec to eliminate the value imports/exports for now?
i don't see why not, something like limiting it to arg*
of 0 length and r
of zero (no result)?
so easily calling something like _initialize
could probably just make that limitation in wasmtime and have it non-conforming on start section implementation like it is now (until values are implemented)
We should probably put this limitation in the spec.
yes we should bubble it up to the spec level with the preview2 emoji as a qualifier
Thanks. I think that will help ease the implementation of the go:wasmexport
proposal in Go, and simplify our work implementing the Component Model and Preview 2
Ideally the Go (and TinyGo) toolchains will be able to emit a component directly, once the spec is formalized.
I filed https://github.com/WebAssembly/component-model/pull/297 to remove the restriction on imports, and to add the no-args-no-returns restriction.
Possibly interesting tidbit: componentize-py
relies on wasm-tools component link
to create a component using shared-everything linking. Part of that process involves synthesizing an "init" module which has a start
function that calls any __wasm_call_ctors
or _initialize
functions found in the input modules. Some of those functions call host imports, and that succeeds because of the order of instantiation inside the component. So calling imports from a (module-level) start
function is possible in certain cases, and componentize-py
has been relying on that for a while.
that's implemented with a component start section?
No, it's a module start section inside the last module to be instantiated inside the component.
ah, gotcha!
Here's the code that ensures the desired instantiation order, FWIW: https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wit-component/src/encoding.rs#L604-L630
Can that code call component imports?
Yes
oh! do we even need a component start section if, for the case without argument or return values, module start functions do the trick?
Well you need to split your code over at least two modules in order for the above trick to work (or have wasm-tools compoonent link
do that for you, based on an input shared library).
It's easy enough for componentize-py
since it controls the whole toolchain, but I'm not sure how generalizable it is to other languages and toolchains.
Interesting. I wasn't aware we could do that. So yeah, I'd still like to have component start functions, so the core-wasm toolchain story can just be to export a function with a designated name, and wit-component can put it in the component-model start section.
While investigating this, I noticed that while you can call imports from the core-wasm start function, the function table isn't always initialized at that time. I was able to call write(1, ...)
and it worked, but printf
didn't work because it does a call_indirect internally.
Or, wait, the table is initialized, but the adapter's table isn't initialized yet.
Question regarding the Go wasmexport
proposal and how it might interact with cabi_realloc
:
cabi_realloc
?wasmexport
proposal presumes that the Go scheduler will be activated and run while the exported function runs, and pause other goroutines when the called func completes. My guess is that might not be desirable in a cabi_realloc
call.Is a guest allowed to make host calls inside of a call to cabi_realloc?
Short answer: no.
Long answer: You'll want to take a look at CanonicalABI.md which outlines all of this. Specifically what you'll be interested in here is the may_leave
flag. This is notably set to False
while lower_values
is called, meaning that when values are being lowered into a component (which is when cabi_realloc
would be called) the flag is false which means that an import would trap. More-or-less cabi_realloc
is expected to be a "stay inside the module" kind of thing.
My guess is that might not be desirable in a cabi_realloc call.
If activating the schedule has high overhead, then yes you probably don't want to do that. If there's low overhead though it may not be an issue. It's worth pointing out that there are no threads in components at this time, and it'll be awhile before there are. That means that in a single-threaded world where you're calling malloc
there's not much use for the scheduler, especially if imports can't be called
Thanks!
I suspect if the Go runtime is adapted for this, then anytime it’s not allowed to call imports, it could block the calling goroutine until it can, and go to another goroutine
Last updated: Dec 23 2024 at 13:07 UTC