You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For me a type should contain only types or some descriptors, but it seems any mutable objects can be added a to (non-mutable?) type.
The most confusing part is how instances are connected? E.g. the (func (param "X" (list (list s32)))) is the import and export of two components, and the import side wants to call this function. What should happen? How this is represented in core module side? How the data is transferred?
For me a type should contain only types or some descriptors, but it seems any mutable objects can be added a to (non-mutable?) type.
Ah, I'm guessing you're commenting on the alias declarator (alias export $I "f" (func)), particularly on the func. Yes agreed that is confusing and indeed Binary.md contains a bullet:
Validation of instancedecl (currently) only allows the type and instance sorts in alias declarators.
@alexcrichton this second snippet in the OP currently validates in wasm-tools; is that a bug or am I missing something here?
In any case, while that alias declarator binds a new funcidx, I don't think there's anything you can subsequently do with that funcidx (to make it show up in an import/export declarator), so I think it's effectively "dead code" atm, unless, again, I'm confused about something. So great question here and thanks for asking!
What should happen? How this is represented in core module side? How the data is transferred?
For this, let me point you to the Canonical Definitions section of the explainer. Specifically, the exporting component will canon lift a core function into a component-typed function and the importing component will canon lower the imported component-typed function to produce a new core function that can be imported and called by core wasm.
Just in case it's helpful, I started writing a little more detail on this one before Luke responded above. Here we are:
Defining a concrete component, which we can refer to as $C later and in its body if necessary
Creating an entry in the type index space of that component, which contains s32
Creating an entry in the instance index space which contains an instance which exports a type, the exported type being equal to s32.
We have not imported or exported anything from the overall component yet, so if you were to instantiate the component $C, it would create this instance, but nothing particularly interesting would happen. The component's type is (component), or, as written in the formalism $\{\} \to \{\}$, because it has no imports or exports.
You could add something like (export "i" (instance 0)) to the example component. Then, C would export the instance it had just defined, and you would get something like (component (export "i" (instance (export "inline" (type (eq s32)))))) (or, after elaboration, in the formal syntax, something like, $\exists (\tau : (\mathrm{eq}\ s32)). \{\} \to \{ \mathrm{i} : \{ \mathrm{inline} : \tau \} \}$.
I agree with you a little bit that the syntax here is a little confusing! As Luke mentioned, I don't think that this has meaning with func as that sort. The reason why we can generally have instances (which are also stateful) in these alias declarators is that it gives us a surface syntax consistent with the rest of the syntax to get at the exported types inside of those instances (which are per-instance, due to generativity), like ensuring that a type that a component exports is the same as one that it imports. Concretely:
The type here elaborates to something like $\forall (\tau : (\mathrm{sub\ resource})). \{ \mathrm{x} : \{ \mathrm{y} : \{ \mathrm{t} : \tau \} \} \} \to \{ \mathrm{z} : \{ \mathrm{u} : \tau \} \}$. I.e. "This component imports an instance, which itself exports an instance which exports a resource type. This component than exports an instance which exports the same resource type."
You may find this (rather out of date) Menhir parser for the text format from an old draft of the reference interpreter to be a useful reference. There will be a newer reference interpreter sometime soon.
Creating an entry in the instance index space which contains an instance which exports a type, the exported type being equal to s32.
So the point of these inlined instances is exporting something form their owner component without creating another (helper) component, isn't it? These instances don't have a real source component, only contains a list of (mutable) exports.
How can I declare version suffixes? The explainer says: versionsuffix ::= (versionsuffix "<semversuffix>") but I got a parse error for this:
(import "res" (type (sub resource))) How do you know that the (rep ...) of the resource is i32 or i64? If I understand correctly, borrow and own needs this information.
The canonical explainer says this: case OwnType() : return lift_own(cx, load_int(cx, ptr, 4), t)
The size can be 8 for i64 rep, or am I misunderstand something?
(import "type" (type (eq 0))) What is imported here exactly? Some kind of object? What is the purpose if this declaration?
When you pass data with lifted/lowered functions, do you need to optimize shared buffer readings? I.e. if you pass 5 u8 lists to a lifted function, which refer to the same, 1 MByte data, do you allocate 5 * 1MB data on the lowered side, or do you need to recognize that they are the same lists and copy only 1 MB and set start pointers accordingly? This can be rather complex for intersected regions, i.e. the 1 MB data starts from offset 0, 5, 10, ... so you only need to copy 1MB + 4*5 byte data.
The load_int(cx, ptr, 4) passed to lift_own() is loading the i32index of the owned handle in the current component instance's handles table. The rep (which is indeed either i32 or i64) is stored in the handle element, and thus the index can always be i32. The only resource operations that need to know whether rep is i32 or i64 are resource.new $rt and resource.rep $rt, and validation rules specify that these may only be used in the same component instance that defines $rt (thus keeping rep an encapsulated implementation detail).
(import "foo" (type (eq 0))) says that the foo import argument must be a type that is equal to whatever type is at typeidx0 (which itself can be an import). They're not super useful, but do help preserve the original WIT document structure. On exports, eq constraints are actually useful, since they allow a single type be exposed to clients under multiple names (e.g., in multiple exported WIT interfaces) while preserving the fact that all those distinct names refer to the same type and are thus type-compatible.
No, there is no deduplication on the lowering side: the aliased data on the lifting side will be copied each time.
Thank you. It seems I totally misunderstood how resources work. I also saw an example for export aliasing a resource type from an instance with the eq definition. Probably it is not possible to do that without eq.
It seems to me components only use the original WebAssembly types: i32/i64/f32/f64, all of its type definitions can be translated to them. The newer types such as v128, (ref [null]), externref, etc. are not used. Is this correct? Probably there were no other options when components were first created. GC support (WebAsembly 3.0) is good for passing any data between instances. Is there any plan to use them?
You're right that the reason that only the original four core wasm value types are used in the Canonical ABI is because that's all there was (stable) when we first released Preview 2. Including v128 would require adding an analogous component-level type (including to WIT), which hypothetically we could do, but there's not a strong motivation yet. But GC and reference types do have motivating use cases, with a number of folks keenly interested (viz., those with languages targeting wasm-gc). There's a pre-proposal in #525 and hopefully someone will have the resources to start working on it before long.
Thank you. My next question is about transmitting data between modules. I am trying to figure this about, but the text in canonicalABI is confusing for me.
Lets assume a function with type (type (func (param "P" (list (list s8))))) is present. We should use it for transmitting data from module A to B. Module A has memory MA, and module B has memory MB, the size of these memories is 32 bit.
If I understand correctly the outer list is represented by a length and a start values, represented by two i32 arguments for a core function. The start value points to a memory address, which contains similar pairs. These pairs represent the s8 arrays. Everything is stored in the MA memory.
When I transmit the data, shall I copy the whole argument into a hidden buffer (i.e. computing the linear size for the whole thing and serialize all data into it)? Or can I directly copy it into MB? In MB, shall I allocate memory with the allocator function in the canonical options for each array, or compute the size of the whole thing, and allocate memory with a single alloc, and then serialize? The start values must be updated to contain offsets in MB (so they are not just copied).
Good question! While the CABI Python code looks like it might require an intermediate copy into a hidden buffer, that's more of a spec mechanism (to decouple component-to-component and component-to-host) and by-design you can do a direct copy from, in your example, MA to MB. E.g., Wasmtime's impl, when compiling a component-to-component call, the compiler generates a wasm trampoline (sitting between the caller and callee) that has access to both MA and MB, and this lets the trampoline look at the outer list's length in MA, then call realloc in MB for the elements of the outer list, then look at the outer list's first element's length in MA, the call realloc in MB, then copy the elements into MB, then write the pointer/length into the outer list's first element in MB, then repeat for each element, finally passing the pointer/length of the outer list into MB's core wasm entry point.
I am sorry, I still have more questions. This time about resources. Resources has 32 bit handles. I compiled a "hello world" with clang (wasi 0.2), and it seems that it expects that a signed number for handle is >= 0, and it seems the value 0 is considered as an error in some cases. Are handle values limited to [1 .. INT32_MAX]?
Resources has h.num_lends and h.borrow_scope.num_borrows in the canonical abi. Are borrowed resources tracked in different ways? If a resource is recursively borrowed, how can h.num_lends track it? It seems resources are copied when borrowed, and they don't track their original owner.
I tried to implement resources as objects, which are passed around. The handle array contains pointers. The lowest two bits are always 0, so one bit is used to represent borrowed resources. This way passing around them is cheap. Is this implementation correct, or there are cases when it is incorrect?
Is it possible to pass a resource as both borrow and own to another component? In this case an instance has both a borrowed and owned reference to the same resource.
Almost: handle values are limited to [1 .. 228-1]. For reference, the relevant description here is the table state section.
Resources has h.num_lends and h.borrow_scope.num_borrows in the canonical abi. Are borrowed resources tracked in different ways? If a resource is recursively borrowed, how can h.num_lends track it? It seems resources are copied when borrowed, and they don't track their original owner.
When a handle (owned or borrowed) is lent (by passing it as an argument for a borrow, in lift_borrow), the lent handle's num_lends is incremented and it is not decremented until the call returns. The incrementing of num_lends, even on borrowed handles, ends up transitively keeping the original owned handle alive (which will trap if resource.droping when num_lends > 0.
I tried to implement resources as objects, which are passed around. The handle array contains pointers. The lowest two bits are always 0, so one bit is used to represent borrowed resources. This way passing around them is cheap. Is this implementation correct, or there are cases when it is incorrect?
Technically, based on the above, it would be the highest 4 bits that would always be zero. But yes, you could put the resource rep value and resource type into an object and store a pointer to that object in each handle array element, destroying the object when the owned handle is successfully resource.droped. (To be clear, the own, borrow_scope and lend_count fields of ResourceHandle have to be stored in the handle table element, not the object, but a bit can stolen from the pointer to represent own, as you said. Also, until you implement full P3 concurrency, borrow_scope can be removed since it'll always be equal to the current task.)
Is it possible to pass a resource as both borrow and own to another component? In this case an instance has both a borrowed and owned reference to the same resource.
No: if you pass the own first, it'll remove the owned handle from the source table, so you can't then pass a borrow; if you pass the borrow first, it'll increment the lend_count on the own handle and then when you try to pass the owned handle (before the call you passed the borrow to returns), lift_own will trap because lend_count != 0. You can however pass two borrow handles to the same resource to the same component instance, and this will produce two distinct borrowed handles in that component instance (with distinct i32 indices).
Based on wasm-tools the last part is not entirely true. It yields an error only, if the outer alias crosses the border of an actual component. An example:
Is it valid, if an imported instance with this type provides the same resource 3 times, or they must be different resources?
What about instances, if I import wasi:cli/exit@0.2.0, wasi:cli/exit@0.2.6wasi:cli/exit@0.2.11 and wasi:cli/exit@0.3.0, is it valid to return with the same instance?
This is valid. I did my code based on the text, but what wasm-tools` does is more reasonable. Which one is correct?
Ah, great catch; wasm-tools is doing the right thing and that description you cited needs to be nuanced to say that the outer alias can't access resources when it crosses a component boundary. I'll file a PR to fix in a bit.
Is it valid, if an imported instance with this type provides the same resource 3 times, or they must be different resources?
When validating, those are 3 distinct resource types unequal to each other (since, without knowing the actual instantiation arguments, they could be 3 distinct resource types).
What about instances, if I import wasi:cli/exit@0.2.0, wasi:cli/exit@0.2.6 wasi:cli/exit@0.2.11 and wasi:cli/exit@0.3.0, is it valid to return with the same instance?
I'm not exactly sure what you mean by "return with the same instance", but if you have a component that has 4 imports of 4 instances of the exit interface, which contains 2 functions: from the importing component's perspective, there are 8 functions (that can be alias exported into 8 distinct function indices). When instantiating that component, you may supply the same 1 instance with 2 functions for all 4 instance imports, and then all 8 calls will go to those 2 functions, unbeknownst to the caller. I don't know if that answers your question; happy to discuss more.
When validating, those are 3 distinct resource types unequal to each other (since, without knowing the actual instantiation arguments, they could be 3 distinct resource types).
I am sorry if my question was not clear. I know that from validation perspective, they are 3 different resources. My question is: can an instance object, that is provided by a global registry for a given name, export the same resource using three different names? The user of the instance does not need to know this fact, it will handle them as three different resources. My question is: do the importing process needs to check that the resources are different, or not (I suspect the latter).
My second question is similar, just for instances. Can a global registry provide the same instance for different names, or they must be different instances. If the former is true, different versions of wasi can be supported by the same instance object (as long as the functionality is the same). If it is not allowed, then I need to replicated the same object many times.
can an instance object, that is provided by a global registry for a given name, export the same resource using three different names?
Ah, gotcha, thanks. Yes, the same resource type may be supplied for 3 distinct imported resource types and so there is no requirement of inequality, only equality (when required by eq). E.g., you could do this in a component via:
My second question is similar, just for instances. Can a global registry provide the same instance for different names, or they must be different instances. If the former is true, different versions of wasi can be supported by the same instance object (as long as the functionality is the same).
Yes, as long as the types match (which for instances, allows ignoring unused exports, which is useful for your WASI use case), a single instance (or func or any other definition) can be supplied for multiple different names. E.g., you could do this in a component via:
My next questions is about parameter copying during lift/lower
Does the realloc(originalPtr, originalSize, alignment, newSize) function support memory free operation? E.g. when newsize is set to zero?
When memory is allocated for parameters during lift/lower, do we need to free it with realloc when the called function returns (assuming realloc supports free)? I saw somewhere that borrowed handles needs to be freed.
Do we need to copy arguments if the canonical arguments of both lift and lower uses the same WebAssembly memory? (I suspect yes)
Do we need to validate utf strings during parameter copy, even if the source and target uses the same character format (e.g. utf8)
If we encounter invalid utf strings, shall we trap or handle them in some way? E.g. convert each invalid character to a pre-defined character?
What shall we do when invalid utf strings are converted to other format (if no rules for handling invalid utf)?
Does the realloc(originalPtr, originalSize, alignment, newSize) function support memory free operation?
The component model itself won't ever invoke this as an attempt to free memory. The canonical ABI strictly says where and when this function is called, and it's never done with the purpose of deallocating memory. Most of the time it's just an allocation function, but for strings w.r.t. reencoding there's the possibility of needing to resize an existing allocation.
Languages can pick their own desired semantics for when originalSize > 0 and newSize == 0 -- the component model doesn't specify what should happen in that situation and also won't ever exercise that situation.
When memory is allocated for parameters during lift/lower, do we need to free it with realloc when the called function returns (assuming realloc supports free)?
Ownership of allocations is passed to the wasm itself, which is responsible for the deallocation, yes. What that means is up to the guest itself and the component model does not attempt to clean up allocations. This is all the responsibility of the guest to avoid leaking, for example.
Do we need to copy arguments if the canonical arguments of both lift and lower uses the same WebAssembly memory?
Sort of. I believe that this case is an unconditional trap today because it's a textbook case of reentrance. Reentrance I think is disallowed currently (@lukewagner can correct me if I'm wrong), so this isn't a situation that actually comes up in practice because this generates a trap.
Do we need to validate utf strings during parameter copy, even if the source and target uses the same character format
Do we need to copy arguments if the canonical arguments of both lift and lower uses the same WebAssembly memory?
Sort of. I believe that this case is an unconditional trap today because it's a textbook case of reentrance. Reentrance I think is disallowed currently (@lukewagner can correct me if I'm wrong), so this isn't a situation that actually comes up in practice because this generates a trap.
If a single component instance holds both the readable and writable ends of a stream or future, the lift associated with {stream,future}.write and the lower associated with {stream,future}.read can both use the same memory. To avoid some rather-subtle semantic interleaving questions, we've temporarily disallowed this case by trapping.
If a component re-exports a child, and a parent does donut wrapping, a parent can technically lower its own (re-exported) lifted function, leading to lift/lower using the same memory. Again, we temporarily trap in this case.
So at the moment, there should never be any same-instance/same-memory lift+lower (so you don't have to worry about overlapping reads/writes). (In the future Lazy Lowering should eliminate the subtle interleaving questions, so that "just do memmove()" is the correct semantics.)
(About to be on vacation for 2 weeks; sorry in advance for slow replies.)
Activity
lukewagner commentedon Mar 11, 2026
The type of that instance in
$Cwould be:Ah, I'm guessing you're commenting on the alias declarator
(alias export $I "f" (func)), particularly on thefunc. Yes agreed that is confusing and indeedBinary.mdcontains a bullet:@alexcrichton this second snippet in the OP currently validates in
wasm-tools; is that a bug or am I missing something here?In any case, while that
aliasdeclarator binds a newfuncidx, I don't think there's anything you can subsequently do with thatfuncidx(to make it show up in animport/exportdeclarator), so I think it's effectively "dead code" atm, unless, again, I'm confused about something. So great question here and thanks for asking!For this, let me point you to the Canonical Definitions section of the explainer. Specifically, the exporting component will
canon lifta core function into a component-typed function and the importing component willcanon lowerthe imported component-typed function to produce a new core function that can be imported and called by core wasm.alexcrichton commentedon Mar 11, 2026
That looks like a bug yeah, good catch @zherczeg! I'll try to dig in and see what's going on today.
Disallow non-type/instance aliases in component/instance types
syntactically commentedon Mar 11, 2026
Just in case it's helpful, I started writing a little more detail on this one before Luke responded above. Here we are:
s32s32.We have not imported or exported anything from the overall component yet, so if you were to instantiate the component $C, it would create this instance, but nothing particularly interesting would happen. The component's type is$\{\} \to \{\}$ , because it has no imports or exports.
(component), or, as written in the formalismYou could add something like$\exists (\tau : (\mathrm{eq}\ s32)). \{\} \to \{ \mathrm{i} : \{ \mathrm{inline} : \tau \} \}$ .
(export "i" (instance 0))to the example component. Then, C would export the instance it had just defined, and you would get something like(component (export "i" (instance (export "inline" (type (eq s32))))))(or, after elaboration, in the formal syntax, something like,I agree with you a little bit that the syntax here is a little confusing! As Luke mentioned, I don't think that this has meaning with
funcas that sort. The reason why we can generally have instances (which are also stateful) in these alias declarators is that it gives us a surface syntax consistent with the rest of the syntax to get at the exported types inside of those instances (which are per-instance, due to generativity), like ensuring that a type that a component exports is the same as one that it imports. Concretely:The type here elaborates to something like$\forall (\tau : (\mathrm{sub\ resource})). \{ \mathrm{x} : \{ \mathrm{y} : \{ \mathrm{t} : \tau \} \} \} \to \{ \mathrm{z} : \{ \mathrm{u} : \tau \} \}$ . I.e. "This component imports an instance, which itself exports an instance which exports a resource type. This component than exports an instance which exports the same resource type."
You may find this (rather out of date) Menhir parser for the text format from an old draft of the reference interpreter to be a useful reference. There will be a newer reference interpreter sometime soon.
zherczeg commentedon Mar 11, 2026
Thank you for the explanations!
So the point of these inlined instances is exporting something form their owner component without creating another (helper) component, isn't it? These instances don't have a real source component, only contains a list of (mutable) exports.
How can I declare version suffixes? The explainer says:
versionsuffix ::= (versionsuffix "<semversuffix>")but I got a parse error for this:alexcrichton commentedon Mar 11, 2026
Correct, yeah.
I beileve that's not implemented in wasm-tools just yet, which would explain the parse error (assuming that's what you're using)
Disallow non-type/instance aliases in component/instance types (#2462)
zherczeg commentedon May 2, 2026
I have a few more questions.
(import "res" (type (sub resource)))How do you know that the(rep ...)of the resource is i32 or i64? If I understand correctly,borrowandownneeds this information.The canonical explainer says this:
case OwnType() : return lift_own(cx, load_int(cx, ptr, 4), t)The size can be 8 for i64 rep, or am I misunderstand something?
(import "type" (type (eq 0)))What is imported here exactly? Some kind of object? What is the purpose if this declaration?When you pass data with lifted/lowered functions, do you need to optimize shared buffer readings? I.e. if you pass 5 u8 lists to a lifted function, which refer to the same, 1 MByte data, do you allocate 5 * 1MB data on the lowered side, or do you need to recognize that they are the same lists and copy only 1 MB and set start pointers accordingly? This can be rather complex for intersected regions, i.e. the 1 MB data starts from offset 0, 5, 10, ... so you only need to copy 1MB + 4*5 byte data.
lukewagner commentedon May 4, 2026
Hi! Great questions. Answers in order:
load_int(cx, ptr, 4)passed tolift_own()is loading thei32index of the owned handle in the current component instance'shandlestable. Therep(which is indeed eitheri32ori64) is stored in the handle element, and thus the index can always bei32. The only resource operations that need to know whetherrepisi32ori64areresource.new $rtandresource.rep $rt, and validation rules specify that these may only be used in the same component instance that defines$rt(thus keepingrepan encapsulated implementation detail).(import "foo" (type (eq 0)))says that thefooimport argument must be a type that is equal to whatever type is attypeidx0(which itself can be an import). They're not super useful, but do help preserve the original WIT document structure. On exports,eqconstraints are actually useful, since they allow a single type be exposed to clients under multiple names (e.g., in multiple exported WIT interfaces) while preserving the fact that all those distinct names refer to the same type and are thus type-compatible.zherczeg commentedon May 5, 2026
Thank you. It seems I totally misunderstood how resources work. I also saw an example for export aliasing a resource type from an instance with the
eqdefinition. Probably it is not possible to do that withouteq.It seems to me components only use the original WebAssembly types: i32/i64/f32/f64, all of its type definitions can be translated to them. The newer types such as v128, (ref [null]), externref, etc. are not used. Is this correct? Probably there were no other options when components were first created. GC support (WebAsembly 3.0) is good for passing any data between instances. Is there any plan to use them?
lukewagner commentedon May 5, 2026
You're right that the reason that only the original four core wasm value types are used in the Canonical ABI is because that's all there was (stable) when we first released Preview 2. Including
v128would require adding an analogous component-level type (including to WIT), which hypothetically we could do, but there's not a strong motivation yet. But GC and reference types do have motivating use cases, with a number of folks keenly interested (viz., those with languages targeting wasm-gc). There's a pre-proposal in #525 and hopefully someone will have the resources to start working on it before long.zherczeg commentedon May 7, 2026
Thank you. My next question is about transmitting data between modules. I am trying to figure this about, but the text in canonicalABI is confusing for me.
Lets assume a function with type
(type (func (param "P" (list (list s8)))))is present. We should use it for transmitting data from module A to B. Module A has memory MA, and module B has memory MB, the size of these memories is 32 bit.If I understand correctly the outer list is represented by a length and a start values, represented by two i32 arguments for a core function. The start value points to a memory address, which contains similar pairs. These pairs represent the s8 arrays. Everything is stored in the MA memory.
When I transmit the data, shall I copy the whole argument into a hidden buffer (i.e. computing the linear size for the whole thing and serialize all data into it)? Or can I directly copy it into MB? In MB, shall I allocate memory with the allocator function in the canonical options for each array, or compute the size of the whole thing, and allocate memory with a single alloc, and then serialize? The start values must be updated to contain offsets in MB (so they are not just copied).
Or something else happens?
lukewagner commentedon May 7, 2026
Good question! While the CABI Python code looks like it might require an intermediate copy into a hidden buffer, that's more of a spec mechanism (to decouple component-to-component and component-to-host) and by-design you can do a direct copy from, in your example, MA to MB. E.g., Wasmtime's impl, when compiling a component-to-component call, the compiler generates a wasm trampoline (sitting between the caller and callee) that has access to both MA and MB, and this lets the trampoline look at the outer list's length in MA, then call
reallocin MB for the elements of the outer list, then look at the outer list's first element's length in MA, the callreallocin MB, then copy the elements into MB, then write the pointer/length into the outer list's first element in MB, then repeat for each element, finally passing the pointer/length of the outer list into MB's core wasm entry point.zherczeg commentedon May 17, 2026
I am sorry, I still have more questions. This time about resources. Resources has 32 bit handles. I compiled a "hello world" with clang (wasi 0.2), and it seems that it expects that a signed number for handle is >= 0, and it seems the value 0 is considered as an error in some cases. Are handle values limited to
[1 .. INT32_MAX]?Resources has
h.num_lendsandh.borrow_scope.num_borrowsin the canonical abi. Are borrowed resources tracked in different ways? If a resource is recursively borrowed, how canh.num_lendstrack it? It seems resources are copied when borrowed, and they don't track their original owner.I tried to implement resources as objects, which are passed around. The handle array contains pointers. The lowest two bits are always 0, so one bit is used to represent borrowed resources. This way passing around them is cheap. Is this implementation correct, or there are cases when it is incorrect?
Is it possible to pass a resource as both borrow and own to another component? In this case an instance has both a borrowed and owned reference to the same resource.
lukewagner commentedon May 17, 2026
Good questions:
Almost: handle values are limited to [1 .. 228-1]. For reference, the relevant description here is the table state section.
When a handle (
owned orborrowed) is lent (by passing it as an argument for aborrow, inlift_borrow), the lent handle'snum_lendsis incremented and it is not decremented until the call returns. The incrementing ofnum_lends, even on borrowed handles, ends up transitively keeping the originalowned handle alive (which will trap ifresource.droping whennum_lends > 0.Technically, based on the above, it would be the highest 4 bits that would always be zero. But yes, you could put the resource
repvalue and resource type into an object and store a pointer to that object in each handle array element, destroying the object when theowned handle is successfullyresource.droped. (To be clear, theown,borrow_scopeandlend_countfields ofResourceHandlehave to be stored in the handle table element, not the object, but a bit can stolen from the pointer to representown, as you said. Also, until you implement full P3 concurrency,borrow_scopecan be removed since it'll always be equal to the current task.)No: if you pass the
ownfirst, it'll remove theowned handle from the source table, so you can't then pass aborrow; if you pass theborrowfirst, it'll increment thelend_counton theownhandle and then when you try to pass theowned handle (before the call you passed theborrowto returns),lift_ownwill trap becauselend_count != 0. You can however pass twoborrowhandles to the same resource to the same component instance, and this will produce two distinct borrowed handles in that component instance (with distincti32indices).zherczeg commentedon May 21, 2026
Thank you! Few more questions:
https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md says:
For outer aliases, validation restricts the sort to one of type, module or component and additionally requires that the outer-aliased type is not a resource type (which is generative).
Based on
wasm-toolsthe last part is not entirely true. It yields an error only, if the outer alias crosses the border of an actual component. An example:If I continue this with:
I get an error.
However:
This is valid. I did my code based on the text, but what wasm-tools` does is more reasonable. Which one is correct?
My next question is about multiple sub-resources:
Is it valid, if an imported instance with this type provides the same resource 3 times, or they must be different resources?
What about instances, if I import
wasi:cli/exit@0.2.0,wasi:cli/exit@0.2.6wasi:cli/exit@0.2.11andwasi:cli/exit@0.3.0, is it valid to return with the same instance?lukewagner commentedon May 21, 2026
Ah, great catch;
wasm-toolsis doing the right thing and that description you cited needs to be nuanced to say that the outer alias can't access resources when it crosses a component boundary. I'll file a PR to fix in a bit.When validating, those are 3 distinct resource types unequal to each other (since, without knowing the actual instantiation arguments, they could be 3 distinct resource types).
I'm not exactly sure what you mean by "return with the same instance", but if you have a component that has 4 imports of 4
instances of theexitinterface, which contains 2 functions: from the importing component's perspective, there are 8 functions (that can bealias exported into 8 distinct function indices). When instantiating that component, you may supply the same 1 instance with 2 functions for all 4 instance imports, and then all 8 calls will go to those 2 functions, unbeknownst to the caller. I don't know if that answers your question; happy to discuss more.zherczeg commentedon May 22, 2026
I am sorry if my question was not clear. I know that from validation perspective, they are 3 different resources. My question is: can an instance object, that is provided by a global registry for a given name, export the same resource using three different names? The user of the instance does not need to know this fact, it will handle them as three different resources. My question is: do the importing process needs to check that the resources are different, or not (I suspect the latter).
My second question is similar, just for instances. Can a global registry provide the same instance for different names, or they must be different instances. If the former is true, different versions of wasi can be supported by the same instance object (as long as the functionality is the same). If it is not allowed, then I need to replicated the same object many times.
lukewagner commentedon May 22, 2026
Ah, gotcha, thanks. Yes, the same resource type may be supplied for 3 distinct imported resource types and so there is no requirement of inequality, only equality (when required by
eq). E.g., you could do this in a component via:Yes, as long as the types match (which for instances, allows ignoring unused exports, which is useful for your WASI use case), a single
instance(orfuncor any other definition) can be supplied for multiple different names. E.g., you could do this in a component via:lukewagner commentedon May 22, 2026
(Filed #657 to fix outer alias text.)
zherczeg commentedon May 29, 2026
My next questions is about parameter copying during lift/lower
Does the
realloc(originalPtr, originalSize, alignment, newSize)function support memory free operation? E.g. whennewsizeis set to zero?When memory is allocated for parameters during lift/lower, do we need to free it with realloc when the called function returns (assuming realloc supports free)? I saw somewhere that borrowed handles needs to be freed.
Do we need to copy arguments if the canonical arguments of both lift and lower uses the same WebAssembly memory? (I suspect yes)
Do we need to validate utf strings during parameter copy, even if the source and target uses the same character format (e.g. utf8)
If we encounter invalid utf strings, shall we trap or handle them in some way? E.g. convert each invalid character to a pre-defined character?
What shall we do when invalid utf strings are converted to other format (if no rules for handling invalid utf)?
Thank you for the help.
alexcrichton commentedon May 29, 2026
The component model itself won't ever invoke this as an attempt to free memory. The canonical ABI strictly says where and when this function is called, and it's never done with the purpose of deallocating memory. Most of the time it's just an allocation function, but for strings w.r.t. reencoding there's the possibility of needing to resize an existing allocation.
Languages can pick their own desired semantics for when
originalSize > 0andnewSize == 0-- the component model doesn't specify what should happen in that situation and also won't ever exercise that situation.Ownership of allocations is passed to the wasm itself, which is responsible for the deallocation, yes. What that means is up to the guest itself and the component model does not attempt to clean up allocations. This is all the responsibility of the guest to avoid leaking, for example.
Sort of. I believe that this case is an unconditional trap today because it's a textbook case of reentrance. Reentrance I think is disallowed currently (@lukewagner can correct me if I'm wrong), so this isn't a situation that actually comes up in practice because this generates a trap.
Yes
Trap
No need to worry about this, invalid strings trap prior to needing to convert, so this case doesn't come up
lukewagner commentedon May 29, 2026
Mostly this is disallowed by design due to the Component Model reentrance invariant (#2), as Alex said. There are technically two remaining corner cases:
{stream,future}.writeand the lower associated with{stream,future}.readcan both use the same memory. To avoid some rather-subtle semantic interleaving questions, we've temporarily disallowed this case by trapping.So at the moment, there should never be any same-instance/same-memory lift+lower (so you don't have to worry about overlapping reads/writes). (In the future Lazy Lowering should eliminate the subtle interleaving questions, so that "just do
memmove()" is the correct semantics.)(About to be on vacation for 2 weeks; sorry in advance for slow replies.)