Stream: wasmtime

Topic: access to guest memory with component model


view this post on Zulip Roman Volosatovs (Mar 10 2023 at 20:54):

In default wasmtime 6.0.1 I can use Linker::func_wrap to define a host function taking a Caller as a parameter, on which the host function can call get_export https://docs.rs/wasmtime/latest/wasmtime/struct.Caller.html#method.get_export and consecutively get a Memory handle via https://docs.rs/wasmtime/latest/wasmtime/enum.Extern.html#method.into_memory

Component LinkerInstance::func_wrap https://docs.rs/wasmtime/latest/wasmtime/component/struct.LinkerInstance.html#method.func_wrap , however, expects the closure to take https://docs.rs/wasmtime/latest/wasmtime/struct.StoreContextMut.html as the parameter, which does not provide access to any of the exports

Moreover, the ExportInstance itself does not seem to provide means to get anything other than a function, module or an instance https://docs.rs/wasmtime/latest/wasmtime/component/struct.ExportInstance.html

Is there a way for a host function to access the guest's memory in the component-model and if so, how?

view this post on Zulip Alex Crichton (Mar 10 2023 at 21:07):

There is no means by which the underlying linear memory can be accessed with the component model because the abstraction layer of components encapsulates these implementation details of the core wasm module.

Could you expand a bit more on what you need the original linear memory for?

view this post on Zulip Roman Volosatovs (Mar 10 2023 at 21:21):

I'd like to support a "syscall-like" interface, where a guest is passing a pointer and a length to the host via the host function and the host writes to it. That works fine in default wasmtime using Memory::write https://docs.rs/wasmtime/latest/wasmtime/struct.Memory.html#method.write

Essentially this is for backwards-compatiblity with existing Wasm modules while we transition to component model fully

For reference, here's how it works in "default" https://github.com/wasmCloud/wasmCloud/blob/a2ccbb2e1786d2251951f953bdc7f0cca1d8e9fa/src/actor.rs#L88-L483 and I'd like to be able to expose the same ABI to component

Project homepage. wasmCloud allows for simple, secure, distributed application development using WebAssembly actors and capability providers. - wasmCloud/actor.rs at a2ccbb2e1786d2251951f953bdc7f0c...

view this post on Zulip Alex Crichton (Mar 10 2023 at 21:25):

Ok that makes sense, and for that functionality you'll have to model it differently in the component model. I'd recommend looking into preview2's API design along with the preview1-to-preview2 adapter in the preview2-prototyping repository

view this post on Zulip Roman Volosatovs (Mar 10 2023 at 21:45):

Right, so looking at https://github.com/bytecodealliance/preview2-prototyping it seems that what it does is it takes a WebAssembly module using preview1 ABI and turns that into a component - what I want to do is essentially reversed, where I want to take an existing WebAssembly component, embed a "translation layer" to the legacy ABI and run that on a host, which would be able to parse the resulting WebAssembly, but still interact with it through the legacy ABI

Does this make sense?

Polyfill adapter for preview1-using wasm modules to call preview2 functions. - GitHub - bytecodealliance/preview2-prototyping: Polyfill adapter for preview1-using wasm modules to call preview2 func...

view this post on Zulip Alex Crichton (Mar 10 2023 at 21:46):

That makes sense, yes, but currently that's not possible. The abstration level and layer of the component model is such that you can't acquire an underlying linear memory from the host's point of view. From the host's point of view it must communicate with component model types, e.g. things like list<u8> and such

view this post on Zulip Alex Crichton (Mar 10 2023 at 21:47):

The rough analogy is that if you had something like an HTTP RPC protocol or something like that you can't make an RPC for "please fill in this buffer" because you can't get written to by some remote system, for example.

view this post on Zulip Joel Dice (Mar 10 2023 at 21:51):

In other words, component interfaces should always be virtualizable, i.e. implementable by another component with no special powers to inspect another component's memory.
Of course, you could do it ptrace-style where the component exports a function that takes an offset and a length and returns a list<u8> of the memory at that location (and another one that writes a list<u8> to a given offset) :)

view this post on Zulip Roman Volosatovs (Mar 10 2023 at 23:12):

I see, thank you so much for your answers!
Just a simple WIT like that actually suffices:

interface host {
    host-call: func(binding: string, namespace: string, operation: string, payload: list<u8>) -> result<list<u8>, string>
}

default world actor {
    import host: self.host
    export guest-call: func(operation: string, payload: list<u8>) -> result<list<u8>, string>
}

So we'll go the "componentization" route instead :rocket:


Last updated: Dec 23 2024 at 13:07 UTC