Repository navigation
Linking in Instantiation stage #547
Description
Activity
Hi, thanks for asking and excited to hear you're working on the WasmEdge implementation!
Can we assume that all the imports of the core:module $Main will be reserved (found) in this component imports?
A nested module named
$Mis only instantiated by a subsequent(core instance $name (instantiate $M (with ...)*))definition (if there is none, no instances are created;$Mis dead code). The imports of$Mare 100% determined by what is passed in the...inside the(with ...)expressions. Here, the semantics is just named-parameter-passing (just like theimportObjectpassed toWebAssembly.instantiate. Moreover, there can be 0..N(core instance ...)definitions for$M, creating 0..N instances of$M, each with potentially different arguments supplied for$M's imports. So you basically want to think of$Mas a big outer function nesting all the real wasm(func ...)s and the imports of$Mare the parameters of this outer function.On a side note, I've started writing more
.wasttests (now that I can actually execute them) in thetestsdirectory. Most of the tests are for async (in thetests/asyncsubdirectory), but I've been meaning to fill in more for pre-async stuff for linking and resource types. E.g., here's a multi-instance test for resource types. Feel free to ask me for any specific.wasts (here or on zulip) if they'd help you.Reacted by YiYing HeHi, thanks for asking and excited to hear you're working on the WasmEdge implementation!
Can we assume that all the imports of the core:module $Main will be reserved (found) in this component imports?
A nested module named
$Mis only instantiated by a subsequent(core instance $name (instantiate $M (with ...)*))definition (if there is none, no instances are created;$Mis dead code). The imports of$Mare 100% determined by what is passed in the...inside the(with ...)expressions. Here, the semantics is just named-parameter-passing (just like theimportObjectpassed toWebAssembly.instantiate. Moreover, there can be 0..N(core instance ...)definitions for$M, creating 0..N instances of$M, each with potentially different arguments supplied for$M's imports. So you basically want to think of$Mas a big outer function nesting all the real wasm(func ...)s and the imports of$Mare the parameters of this outer function.On a side note, I've started writing more
.wasttests (now that I can actually execute them) in thetestsdirectory. Most of the tests are for async (in thetests/asyncsubdirectory), but I've been meaning to fill in more for pre-async stuff for linking and resource types. E.g., here's a multi-instance test for resource types. Feel free to ask me for any specific.wasts (here or on zulip) if they'd help you.Thanks for the reply. The multi-instance test you mentioned is useful, I'll read it then.
The link of the example I took is incorrect, and I've fixed it.As you mentioned, I think I can treat the
import,component, andmodulesection of a component as the descriptions, which describe the declaration of the module or sub-component, and theinstanceandcore:instancesections supply the needed import arguments to instantiate them.
That is, after passing the validation, take the example above:;; app.wat (component (import "libc" (core module $Libc ...)) (import "libzip" (core module $Libzip ...)) (import "libimg" (core module $Libimg ...)) (import "zipper" (component $Zipper ...)) (import "imgmgk" (component $Imgmgk ...)) (core module $Main (import "libc" "memory" (memory 1)) (import "libc" "malloc" (func (param i32) (result i32))) (import "zipper" "zip" (func (param i32 i32) (result i32 i32))) (import "imgmgk" "transform" (func (param i32 i32) (result i32 i32))) ... (func (export "run") (param i32 i32) (result i32 i32) ... ) ) (instance $zipper (instantiate (component $Zipper) (with "libc" (module $Libc)) (with "libzip" (module $Libzip)) )) (instance $imgmgk (instantiate (component $Imgmgk) (with "libc" (module $Libc)) (with "libzip" (module $Libzip)) (with "libimg" (module $Libimg)) )) (core instance $libc (instantiate (module $Libc))) (core func $zip (canon lower (func $zipper "zip") (memory (core memory $libc "memory")) (realloc (func $libc "realloc")) )) (core func $transform (canon lower (func $imgmgk "transform") (memory (core memory $libc "memory")) (realloc (func $libc "realloc")) )) (core instance $main (instantiate (module $Main) (with "libc" (instance $libc)) (with "zipper" (instance (export "zip" (func $zipper "zip")))) (with "imgmgk" (instance (export "transform" (func $imgmgk "transform")))) )) (func $run (param string) (result string) (canon lift (core func $main "run") (memory (core memory $libc "memory")) (realloc (func $libc "realloc")) )) (export "run" (func $run)) )When instantiating the
$Mainor the component$Zipper(the sub-components or sub-modules), their needed imports are expected to be supplied by the(with ...)expressions. Is it correct?Another interesting question.
What would the code be like if a component have to import the outside modules, such aswasi?
For example, in core WASM, the module would like:(module (import "wasi" "fd_open" (func (param ...) (result i32))) ... )And the runtime should help to link with the modules when instantiate it.
In component model proposal, such as the above example, the component seems like described all the needed sub-components into a top component and link as internal.
Would you help to take a simply example of a component import the external modules?
Thanks!When instantiating the $Main or the component $Zipper (the sub-components or sub-modules), their needed imports are expected to be supplied by the (with ...) expressions. Is it correct?
That's right
What would the code be like if a component have to import the outside modules, such as wasi?
WASI imports appear in a component as
imports withinstancetype. For example, based on this WIT if your component importednow, you'd get:(component (import "wasi:clocks/monotonic-clock@0.3.0" (instance (export "now" (func (result u64))) ;; (i'm ignoring the 'type instant = u64' here) )) ... )
Because we're importing an
instance(as opposed to acomponentorcore module), you don't have toinstantiateit -- it's already instantiated (by the host or by a parent component -- we don't know) and so this import goes straight into theinstanceindex space (with which you can use analiasdefinition to project out thenowfunction into thefuncindex space, thencanon lowerit into thecore funcindex space, then reference that from awithwheninstantiateing the main core module of the component).Reacted by YiYing HeWhen instantiating the $Main or the component $Zipper (the sub-components or sub-modules), their needed imports are expected to be supplied by the (with ...) expressions. Is it correct?
That's right
What would the code be like if a component have to import the outside modules, such as wasi?
WASI imports appear in a component as
imports withinstancetype. For example, based on this WIT if your component importednow, you'd get:(component
(import "wasi:clocks/monotonic-clock@0.3.0" (instance
(export "now" (func (result u64))) ;; (i'm ignoring the 'type instant = u64' here)
))
...
)
Because we're importing aninstance(as opposed to acomponentorcore module), you don't have toinstantiateit -- it's already instantiated (by the host or by a parent component -- we don't know) and so this import goes straight into theinstanceindex space (with which you can use analiasdefinition to project out thenowfunction into thefuncindex space, thencanon lowerit into thecore funcindex space, then reference that from awithwheninstantiateing the main core module of the component).It's an useful information. Thanks for the explaination!
It's more clear to understand this with the concept of index spaces when instantiating or validating (mapping to the context in WASM instantiation).
I realized that this proposal is still in early stage, but I think independent pages or sections for briefly validation and instantiation rules will help a lot.I realized that this proposal is still in early stage, but I think independent pages or sections for briefly validation and instantiation rules will help a lot.
You're right. The goal is to fully formally define this and #101 contains (in Latex you can render to a pdf) a pretty good sketch of how validation works if you're able to parse the formal language it's using. We'll resume working on this, but switch to SpecTec now that Core WebAssembly spec has switched to SpecTec as well.
Reacted by YiYing HeI realized that this proposal is still in early stage, but I think independent pages or sections for briefly validation and instantiation rules will help a lot.
You're right. The goal is to fully formally define this and #101 contains (in Latex you can render to a pdf) a pretty good sketch of how validation works if you're able to parse the formal language it's using. We'll resume working on this, but switch to SpecTec now that Core WebAssembly spec has switched to SpecTec as well.
Sorry for another basic question.
Would you help to suggest a tool to convert the text format of component model (for example, this component in the wast file) into binary format? I found that the common tools are mainly used to compose the components into binary files.
To implement the instantiation phase, it's more convenient to have more test data to confirm the correctness of instantiation steps. Aspecially the binary format order of sections may not be the same as the text format. Thanks!No problem, happy to help. I'd suggest wasm-tools. Given a
component.wat(containing a single top-level(component ...)), you can parse+validate+encode it viawasm-tools parse component.wat -o component.wasm.Reacted by YiYing HeNo problem, happy to help. I'd suggest wasm-tools. Given a
component.wat(containing a single top-level(component ...)), you can parse+validate+encode it viawasm-tools parse component.wat -o component.wasm.Thanks and it help a lot with the test in the test folder.
I used thewasm-toolsto translate the example component text format into binary format, but have a question about value section.
Take the exported function in the top component here:(func (export "run") (alias export $d "run"))The binary sequence is as following:
// alias section, size = 8, vector size = 1 0x06, 0x08, 0x01, // alias sort = sort:func, aliastarget = (export, index = 1, name = "run") 0x01, 0x00, 0x01, 0x03, 0x72, 0x75, 0x6e, // value section, size = 9, vector size = 1 0x0b, 0x09, 0x01, // value[0]: "run" 0x00, 0x03, 0x72, 0x75, 0x6e, 0x01, 0x00, 0x00According to the value binary format:
value ::= t:<valtype> len:<core:u32> v:<val(t)> => (value t v) (where len = ||v||)Let's focus on the
valuesequence:0x00, 0x03, 0x72, 0x75, 0x6e, 0x01, 0x00, 0x00
Would you help to explain how to decode the sequence into thevaluedefinition?According to the value explainer:
Value definitions (in the value index space) are like immutable global definitions in Core WebAssembly except that validation requires them to be consumed exactly once at instantiation-time.Would you help to explain more detaily about the consuming the value definitions at instantiation time?
Thanks!Let's focus on the
valuesequence:0x00, 0x03, 0x72, 0x75, 0x6e, 0x01, 0x00, 0x00Actually, I think section
0x0b(type 11) is an export section, rather than a value section---this corresponds to the(export "run")portion of the wat statement you showed. So0x0b 0x09says that this is a 9-byte export section. The contents of that section is avec(<export>), so the0x01means that there is only one export. The export rule starts with anexportname', and the0x00tells us that it is the first case of an exportname (i.e. one without a version suffix). The rest of that rule tells us that the next (SLEB-encoded) u32 is the length of the name, in this case 3, and we then have the raw bytes of the export name (0x72 0x75 0x6e). With that out of the way, going back to theexportrule, we should expect to see asortidxnext, and indeed0x01 0x00is the sortidx for the function at index 0 in the function index space (which happens to be the function we justalias export'd into our index space. Finally, we should look for an optional ascribed type in the form of an<externdesc>?, which the final0x00tells us is not in fact present.Would you help to explain more detaily about the consuming the value definitions at instantiation time?
A value can be consumed by feeding it into another component as an instantiation argument, exporting it, or passing it to a function in a start definition (which may in turn produce some values of its own). Any given component must do this exactly once to any value that it gets (either by importing one or by running a start definition that produces one).
Reacted by YiYing He and Luke WagnerLet's focus on the
valuesequence:0x00, 0x03, 0x72, 0x75, 0x6e, 0x01, 0x00, 0x00Actually, I think section
0x0b(type 11) is an export section, rather than a value section---this corresponds to the(export "run")portion of the wat statement you showed. So0x0b 0x09says that this is a 9-byte export section. The contents of that section is avec(<export>), so the0x01means that there is only one export. The export rule starts with anexportname', and the0x00tells us that it is the first case of an exportname (i.e. one without a version suffix). The rest of that rule tells us that the next (SLEB-encoded) u32 is the length of the name, in this case 3, and we then have the raw bytes of the export name (0x72 0x75 0x6e). With that out of the way, going back to theexportrule, we should expect to see asortidxnext, and indeed0x01 0x00is the sortidx for the function at index 0 in the function index space (which happens to be the function we justalias export'd into our index space. Finally, we should look for an optional ascribed type in the form of an<externdesc>?, which the final0x00tells us is not in fact present.Would you help to explain more detaily about the consuming the value definitions at instantiation time?
A value can be consumed by feeding it into another component as an instantiation argument, exporting it, or passing it to a function in a start definition (which may in turn produce some values of its own). Any given component must do this exactly once to any value that it gets (either by importing one or by running a start definition that produces one).
Sorry, my mistake. You are right, it's export section.
Thanks for the explaination!Thanks @syntactically! And just to add: the emojis (like 🪙 for values) next to productions in
Binary.mdandExplainer.md(for the binary and text formats, resp.) mean that this feature is not yet implemented or officially released inwasm-tools/wasmtime. In the specific case of 🔀 (Preview 3 / async), the feature is implemented, but requires a feature flag to enable.Reacted by YiYing HeHi @lukewagner , questions about instance section in instantiation phase.
According to the definition:
core:instance ::= (instance <id>? <core:instancexpr>) core:instanceexpr ::= (instantiate <core:moduleidx> <core:instantiatearg>*) | <core:inlineexport>* core:instantiatearg ::= (with <core:name> (instance <core:instanceidx>)) | (with <core:name> (instance <core:inlineexport>*)) core:inlineexport ::= (export <core:name> <core:sortidx>) instance ::= (instance <id>? <instanceexpr>) instanceexpr ::= (instantiate <componentidx> <instantiatearg>*) | <inlineexport>* instantiatearg ::= (with <name> <sortidx>) | (with <name> (instance <inlineexport>*)) inlineexport ::= (export "<exportname>" <sortidx>)As mentioned in doc, the
instantiatearghas 2 forms: first is the name withsortidx, another is the name with inline exports.
In the test, we can find the inline export case ofcore:instance:(core instance $dm (instantiate $DM (with "" (instance (export "R1.resource.drop" (func $R1.resource.drop)) (export "R2.resource.drop" (func $R2.resource.drop)) (export "make-R1" (func $make-R1')) (export "make-R2" (func $make-R2')) (export "get-rep-R1" (func $get-rep-R1')) (export "get-rep-R2" (func $get-rep-R2')) (export "num-live" (func $num-live')) ))))For the
core:instancecase, it's simple that construct the inline exports into a core module instance, and supply the core module named as""to the imports of$DMwhen instantiation.
We know that when instantiate a core module, the spec only allows importingfunc,memory,table, andglobal(andtaginexception-handlingproposal) instances.
But thecore:sortadditionally defined thetype,module, andinstancecases.
Can we assume that thecore:inlineexportfor constructing the core module instance for the importing use only contained thefunc,memory,table, andglobalcases?On the other hand is the
instancecase. The test only covered thewith "" (instance )expression.
Since thesortidxandcore:sortidxhave several cases, are they mapping onto the index space of the context?
That is, for the component level import/export and instantiation, thewithexpression accepts the types not only theinstance, right?As my knowledge, for the
core:instancecase, the inline exports will be constructed into a core module instance, and supplied as imports with the module name inwithexpression.
But for theinstancecase, the inline exports will be constructed into a component instance, thewithexpression can also accepts other entries in the index spaces.Please help to point out my misunderstanding or give some simple examples. Thanks!
Great questions:
Can we assume that the core:inlineexport for constructing the core module instance for the importing use only contained the func, memory, table, and global cases?
Yes
That is, for the component level import/export and instantiation, the with expression accepts the types not only the instance, right?
Yes. Here's an example of passing component-level
funcs and here's an example of passing component-levelinstances.Reacted by YiYing HeThanks for help with the link phase explaination.
I think this may be the last question of implementing the linking part.
After completing this, I will change to canonical ABI part and will create another issue if I meet troubles.
I will close this issue soon after the question being answered.I found in the tests, the invocation of exported component functions are
funcs, not thecore funcs. (For example here)
Thefuncs are all declared bycanon lift. With current definitions, there are no other canonical ABI defines the componentfunc, right?
For the export section of the top layer of component, is it possible to export thecore funcdirectly and invoke that function?
That is, such as(assert_return (invoke "run") (i32.const 0))in thewastfile, which the"run"is an exportedcore funcof a component?Another is about the
startsection.
Thestartdirectly defined thefuncidx. Does it mean that the function of the start section can only accepts thefunc, but notcore func?Thanks!
That's great news; good job! Happy to discuss other questions in other issues in the future.
With current definitions, there are no other canonical ABI defines the component func, right?
Depending on how you define "defines" (;-) the functions in the component-level
funcindex space can come fromcanon lift,import,exportoralias. If you repeatedly trace thesefuncindices back to their "originating" definition, the two originating kinds of definitions arecanon liftand host-defined functions.For the export section of the top layer of component, is it possible to export the core func directly and invoke that function? That is, such as (assert_return (invoke "run") (i32.const 0)) in the wast file, which the "run" is an exported core func of a component?
No,
core funcs cannot be exported becausecore functypes are not included incomponentype.The
startdirectly defined the funcidx. Does it mean that the function of the start section can only accepts the func, but not core func?Correct
Reacted by YiYing HeDepending on how you define "defines" (;-) the functions in the component-level
funcindex space can come fromcanon lift,import,exportoralias. If you repeatedly trace thesefuncindices back to their "originating" definition, the two originating kinds of definitions arecanon liftand host-defined functions.Thanks, that's what I wanted to check.
No,
core funcs cannot be exported becausecore functypes are not included incomponentype.It seems much easier that the component-level function exporting will not be mixed with the core WASM level.
For runtime, we should consider the situation because users may instantiate not only components, but traditional WASMs.
So it means that according to the export binary definition (export ::= en:<exportname'> si:<sortidx> ed?:<externdesc>?), thesortidxis valid iff it's notcore:sort, right?So it means that according to the export binary definition (export ::= en:<exportname'> si: ed?:?), the sortidx is valid iff it's not core:sort, right?
Almost; the one
core:sortallowed in imports/exports is acore:module(bullet 3 in Binary.md#import-and-export-definitions). Becausecore:modules are stateless/immutable, it doesn't break shared-nothing to import and export them from components and this feature is useful for factoring out common core wasm code that would otherwise be duplicated between different components (e.g).Reacted by YiYing HeAlmost; the one
core:sortallowed in imports/exports is acore:module(bullet 3 in Binary.md#import-and-export-definitions). Becausecore:modules are stateless/immutable, it doesn't break shared-nothing to import and export them from components and this feature is useful for factoring out common core wasm code that would otherwise be duplicated between different components (e.g).Oops, for quickly implementing in runtime, maybe I didn't noticed the detail in documents.
Thanks for pointing it out!Reacted by Luke WagnerThanks for your help.
I'll create anothor issue for canonical ABI if I have questions.
Hi, here's from WasmEdge, and I would want to implement the component-model on this runtime.
Hence there's no spec to clearly introduce the steps of instantiation such as WASM core, I may have to ask some basic questions about linking for a better design of our runtime data structure.
Maybe the questions are simple, or are described in the proposal docs, thanks for you all to answer.
The main question is about the import searching when linking.
Take the linking example in this repo:
When instantiating this component, the
core:module $Mainwill be instantiated also.As above, this component imports
"libc","libzip", and"libimg"modules.Assume that the validation is passed.
Can we assume that all the imports of the
core:module $Mainwill be reserved (found) in this component imports?Another example for the nested component.
For the nested component
$B, should this component also import"libc"for thecore:moduleto import?If the import of
"libc"is not necessary in$B, the import searching of thecore:module $Mainin component$Bshould access the imports of component$A?Thank you for answering.