Stream: general

Topic: static functions on exported resource types


view this post on Zulip Randy Reddig (Mar 10 2024 at 17:19):

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?

view this post on Zulip Alex Crichton (Mar 10 2024 at 20:45):

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.

view this post on Zulip Randy Reddig (Mar 10 2024 at 21:08):

__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;
}

view this post on Zulip Randy Reddig (Mar 10 2024 at 21:08):

From WIT: merge: static func(a: borrow<number>, b: borrow<number>) -> number;

view this post on Zulip Randy Reddig (Mar 10 2024 at 21:10):

Is there any instance where an exported function or method would take a rep (pointer) as an argument, instead of a handle?

view this post on Zulip Randy Reddig (Mar 10 2024 at 21:16):

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.

view this post on Zulip Alex Crichton (Mar 11 2024 at 14:08):

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

view this post on Zulip Alex Crichton (Mar 11 2024 at 14:09):

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

view this post on Zulip Randy Reddig (Mar 11 2024 at 14:10):

Dang ok.

view this post on Zulip Randy Reddig (Mar 11 2024 at 14:13):

Guess I’ll special case the mapping of lifter borrows to Go types

view this post on Zulip Randy Reddig (Mar 11 2024 at 14:13):

Lifted*

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:06):

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.

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:07):

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.

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:09):

Or is it the intention that one and only one component can export (and therefore implement) a given resource type?

view this post on Zulip Alex Crichton (Mar 11 2024 at 22:11):

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)

view this post on Zulip Alex Crichton (Mar 11 2024 at 22:11):

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

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:12):

Can a component both import and export the same interface, which includes a resource type?

view this post on Zulip Alex Crichton (Mar 11 2024 at 22:14):

yes

view this post on Zulip Alex Crichton (Mar 11 2024 at 22:15):

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

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:15):

Right.

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:18):

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.

view this post on Zulip Alex Crichton (Mar 11 2024 at 22:18):

correct, the way I think about it resources are nominal types and everything else is structural

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:21):

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.

view this post on Zulip Randy Reddig (Mar 11 2024 at 22:22):

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.

view this post on Zulip Randy Reddig (Mar 15 2024 at 21:09):

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;
}

view this post on Zulip Randy Reddig (Mar 15 2024 at 21:10):

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>;

view this post on Zulip Randy Reddig (Mar 15 2024 at 21:11):

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.

view this post on Zulip Alex Crichton (Mar 15 2024 at 21:14):

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

view this post on Zulip Randy Reddig (Mar 15 2024 at 21:14):

Aha. So C generator generates invalid code.

view this post on Zulip Randy Reddig (Mar 15 2024 at 21:14):

I noticed this when I tried to generate Rust bindings, and it blew up

view this post on Zulip Alex Crichton (Mar 15 2024 at 21:15):

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

view this post on Zulip Alex Crichton (Mar 15 2024 at 21:15):

so that way each code generator wouldn't have to handle this case

view this post on Zulip Randy Reddig (Mar 15 2024 at 21:15):

Seems reasonable.

view this post on Zulip Alex Crichton (Mar 15 2024 at 21:15):

one way you can test this today is:

$ wasm-tools component wit --wasm ./wit | wasm-tools validate

view this post on Zulip Alex Crichton (Mar 15 2024 at 21:16):

if that doesn't print anything then it's valid

view this post on Zulip Randy Reddig (Mar 15 2024 at 21:16):

Should the WIT spec specify that, or enforce it?

view this post on Zulip Alex Crichton (Mar 15 2024 at 21:41):

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