Stream: git-wasmtime

Topic: wasmtime / issue #8562 Add static type information to exp...


view this post on Zulip Wasmtime GitHub notifications bot (May 06 2024 at 21:12):

kaivol opened issue #8562:

Problem

I use the following .wit to describe a WASM component which provides a long living _operator_ that is initialized once and then invoked multiple times to perform some operation:

world example {
    export exports: interface {
        resource operator {
            init: static func(...) -> operator;
            run: func(...) -> ...;
        }
    }
}

Using this component from a wasmtime host is somewhat unergonomic, because calling call_init
returns a ResourceAny instance, which doesn't carry any (static) type information.
Calling methods on this resource is thus very verbose (see below), and there is a risk of mixing up resources of different types.

let resource = component.exports().operator().call_init(&mut store, ...)
component.exports().operator().call_run(&mut store, resource, ...)

Feature

Ideally, the rust-representation of the resource returned from a WASM component would contain sufficient type information to allow its (and only its) methods to be called with standard method syntax.

Benefit

Better ergonomics when working with resources (reduced error potential, less verbose syntax).

Implementation

It feels like this should not be too complicated, but maybe I'm missing something and what I'm suggesting isn't actually possible.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 00:53):

alexcrichton commented on issue #8562:

Thanks for the report! This is currently an intentional design decision but not because we think it's a good idea, moreso we couldn't think of a better idea at the time.

One tricky part here is that Rust's type system cannot statically rule out all errors here. For example if the exported resource had type T then that type T is only valid for a single instantiation of a component. Each instantiation should get a unique type T, but that can't be reflected in Rust's type system.

Not to say that prevents improving on the current situation of course. I think it'd be great if the Resource<T> approach could be fit in for more, albeit not ironclad, safety here.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 00:53):

alexcrichton added the wasm-proposal:component-model label to Issue #8562.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 00:53):

alexcrichton added the wasmtime:api label to Issue #8562.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 11:13):

FrankReh commented on issue #8562:

Each instantiation should get a unique type T, but that can't be reflected in Rust's type system.

I only ask because this seems a learning opportunity for people new to this technology.

Why should that be the case? If call_init is called twice, aren't the two operator's the same type? Separate instances of the same type?

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 11:21):

kaivol commented on issue #8562:

@alexcrichton Thanks for the reply, it's always interesting to hear about the rationale / reason behind such design decisions!
Feel free to close this if you consider the issue to be resolved (at least for the time being).

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 11:45):

kaivol commented on issue #8562:

Each instantiation should get a unique type T, but that can't be reflected in Rust's type system.

I only ask because this seems a learning opportunity for people new to this technology.

Why should that be the case? If call_init is called twice, aren't the two operator's the same type? Separate instances of the same type?

As I understand it, they are (only) of the same type in the context of the component's .wit defintion, but if they originate from different instantiations of the component they are nevertheless generally not compatible (although that should not be a problem if it is only about calling instance methods on them (?)).
When alex says _Each instantiation_ he's talking about instantiations of the component, not instantiations of the exported resource (correct me if I am wrong).

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 12:14):

FrankReh commented on issue #8562:

Thank you. You are right that I didn't take the use of instantiation correctly. I still have questions about why this part of the API doesn't use the Rust type system more directly, but I won't hijack this thread further.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 14:43):

alexcrichton commented on issue #8562:

Yeah @kaivol is correct. If you have a resource A which exports a resource R, then if you instantiate the component twice producing A1 and A2 the R resource has a different type between A1/A2. Effectively the resource type has a bit of runtime state which indicates which component it came from. This is the part that I don't know how to reflect into the Rust type system statically.

I'm hesitant to close this though because it would be possible to create a type representing R which ensures that you're using "some R" with "some A". There'd still be a runtime check that the two match but that's probably a better situation than today where there's no guard rails whatsoever at compile time.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 14:55):

FrankReh commented on issue #8562:

the R resource has a different type between A1/A2

Is it really a different type, or is it up to the implementation to keep track of which A component the R is tied to?

As an example, the host could keep a weak pointer to the component instance the R came from, and either the methods the host is trying to call use that inner weak pointer as the target, or it's used for a runtime comparison with the target.

(Just wanting to make clear what causes my confusion about the status quo. I feel very out of my depth asking this kind of question but all of the builders of this technology are being so helpful.)

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 14:57):

FrankReh edited a comment on issue #8562:

the R resource has a different type between A1/A2

Is it really a different type, or is it up to the implementation to keep track of which A component the R is tied to?

As an example, the host could keep a weak pointer to the component instance the R came from, and either the methods the host is trying to call use that inner weak pointer as the target, or it's used for a runtime comparison with the target.

(Just wanting to make clear what causes my confusion about the status quo. I feel very out of my depth asking this kind of question but all of the builders of this technology are being so helpful.)

I agree that doesn't solve the problem statically. The compiler won't tell you if you match a component instance with the wrong resource.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 15:09):

FrankReh edited a comment on issue #8562:

the R resource has a different type between A1/A2

Is it really a different type, or is it up to the implementation to keep track of which A component the R is tied to?

As an example, the host could keep a weak pointer to the component instance the R came from, and either the methods the host is trying to call use that inner weak pointer as the target, or it's used for a runtime comparison with the target.

(Just wanting to make clear what causes my confusion about the status quo. I feel very out of my depth asking this kind of question but all of the builders of this technology are being so helpful.)

I agree that doesn't solve the problem statically. The compiler won't tell you if you match a component instance with the wrong resource.


There are crates like generativity and async-local, which maybe try to solve the runtime instantiation vs compile time compatibility type problem but I'll admit I've never fully understood how to use them in anything but the simplest examples plus they don't support all architectures of interest plus the latter is riddled with unsafe calls so I'm in no way wanting to sound like an advocate but it does seem some projects are using them successfully.

view this post on Zulip Wasmtime GitHub notifications bot (May 07 2024 at 18:15):

alexcrichton commented on issue #8562:

Oh no you're right, the implementation keeps track of which types come from which instance and mistakes are detected dynamically. I was mostly reflecting on how I don't think there's a way in Rust to remove this dynamic check and reflect everything statically. I haven't looked into those crates though, myself, so I'll try to take a look!


Last updated: Dec 23 2024 at 12:05 UTC