https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#canon-resourcenew what happens if resource.new
is called on a resource rep a second time?
For example, let’s say you have a resource number
has a static func max(a: number, b: number) -> number
that returns one of its arguments.
The component that implements max
will export a lifted function that accepts a rep (pointer) to a
and b
and returns a handle. At least this is what wit-bindgen c
generates.
What’s the best (or right) way for an implementation to return one of its arguments? e.g. get the preexisting handle for a given rep?
Calling resource.new
with the same rep is valid, and it makes a new own handle. In the case you're describing the a/b arguments are passed as handles, not as a rep, so you can return one of them back out to the caller. If wit-bindgen c
generates pointers there then that's a bug to fix in the bindings generator.
__attribute__((__export_name__("example:resources/simple#[static]number.merge")))
int32_t __wasm_export_exports_example_resources_simple_static_number_merge(int32_t arg, int32_t arg0) {
exports_example_resources_simple_own_number_t ret = exports_example_resources_simple_static_number_merge(((exports_example_resources_simple_number_t*) arg), ((exports_example_resources_simple_number_t*) arg0));
return (ret).__handle;
}
From WIT: merge: static func(a: borrow<number>, b: borrow<number>) -> number;
Is there any instance where an exported function or method would take a rep (pointer) as an argument, instead of a handle?
It makes sense to me that the exports API is consistent with the imports API, e.g. that self
or resource values are always handles, and that the component can call resource.rep to get the concrete representation of the resource.
ah yes if the signature uses borrow<T>
arguments then you get a pointer instead of a handle, so that's expected. When you receive a borrow<T>
you can't return it, so to return a resource you'd have to return a newly created one
There's a special case for borrows to own
resources where you get the rep, not the handle, because there's not actually anything you can do with the handle other than get the rep
Dang ok.
Guess I’ll special case the mapping of lifter borrows to Go types
Lifted*
I’m trying to wrap my head around the case where you have a component that exports a function with a borrow<t>
argument that might be a resource from another component.
e.g. Component A exports a pollable
resource, and Component B also exports a pollable
resource.
It’s straightforward to have a Component C or interface C that exports a function that accepts a borrow<pollable>
defined in either component A or component B, since the arguments will be handles.
But I can’t see how Component A could have a exported function that accepts a borrowed resource from Component B as an argument, since the argument will be represented as a rep
and not a handle.
Or is it the intention that one and only one component can export (and therefore implement) a given resource
type?
I think the answer to your question is that C isn't possible in your scenario, which is why this isn't a problem.
The longer form answer is that each component is sort of an "island" in terms of type information, this means that if C imports A&B it doesn't actually have A&B and it needs to actually define what A&B are. In such a situation C declares the type of A and the type of B, and when doing so it'll declare that each of them exports as pollable
and C will understand that the two are different. This means that it's a type error to pass B's pollable
to a function in A since that wouldn't type-check. (or would be caught at runtime by indexing tables today)
Put another way there's no global pollable
resource, everyone has their own view of what pollable
is, so anyone can implement that and it's up to whomever is stitching everything together to wire it all up right
Can a component both import and export the same interface, which includes a resource type?
yes
when doing so, however, if you're exporting an interface you're exporting a new definition of a resource, so the import and the export will have different types
Right.
As far as I can tell, this only matters for resource types, since other types are value types or primitives, and can be taken from an export call and passed into an import call.
correct, the way I think about it resources are nominal types and everything else is structural
OK, thanks. I think my working mental model was that resources were duck-typed and could be freely passed between import and export boundaries, as long as they had the requisite methods.
e.g. the provenance of a resource was irrelevant to functions that operated on them, maybe aside from methods, which would need access to the underlying rep
.
I think I found a bug in the C generator for exported resources.
__attribute__((__export_name__("example:resources/simple#[static]number.merge")))
int32_t __wasm_export_exports_example_resources_simple_static_number_merge(int32_t arg, int32_t arg0) {
exports_example_resources_simple_own_number_t ret = exports_example_resources_simple_static_number_merge((exports_example_resources_simple_own_number_t) { arg }, (exports_example_resources_simple_own_number_t) { arg0 });
return (ret).__handle;
}
__attribute__((__export_name__("example:resources/simple#[static]number.choose")))
int32_t __wasm_export_exports_example_resources_simple_static_number_choose(int32_t arg, int32_t arg0) {
exports_example_resources_simple_borrow_number_t ret = exports_example_resources_simple_static_number_choose(((exports_example_resources_simple_number_t*) arg), ((exports_example_resources_simple_number_t*) arg0));
return (ret).__handle;
}
The WIT for these two static functions:
merge: static func(a: number, b: number) -> number;
choose: static func(a: borrow<number>, b: borrow<number>) -> borrow<number>;
exports_example_resources_simple_borrow_number_t
is defined as such:
typedef exports_example_resources_simple_number_t* exports_example_resources_simple_borrow_number_t;
It’s a pointer, not a struct, so it can’t have a __handle
.
One thing there is that there's less validation in wit-parser
than there is for components in general. For example in the component model you can't return a borrow, but wit-parser doesn't disallow that
Aha. So C generator generates invalid code.
I noticed this when I tried to generate Rust bindings, and it blew up
yeah it'd be best for you to get a WIT-level error there saying that you can't return a type with a borrow
so that way each code generator wouldn't have to handle this case
Seems reasonable.
one way you can test this today is:
$ wasm-tools component wit --wasm ./wit | wasm-tools validate
if that doesn't print anything then it's valid
Should the WIT spec specify that, or enforce it?
I think so yeah, it's not too useful to end developers to let things through parsing and validation that are always errors later on
Last updated: Dec 23 2024 at 12:05 UTC