rust wasmtime中的“offset”是什么Memory.read?

kq0g1dla  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(111)

问题

我已经启动并运行了wasmtime,从Rust主机调用TinyGo WASM/WASI模块。一切都很好,直到我尝试从Go WASI模块返回一个字符串,这似乎是每个人都在努力的事情。我理解在特定位置访问WASM模块的内存并阅读特定长度的概念;我不明白的是如何用offset来代替指针。
我在想,对wasmtime自己的example from their docs的澄清可能会为我指明正确的方向:

use wasmtime::{Memory, Store, MemoryAccessError};

fn safe_examples(mem: Memory, store: &mut Store<()>) -> Result<(), MemoryAccessError> {
    let offset = 5;
    mem.write(&mut *store, offset, b"hello")?;
    let mut buffer = [0u8; 5];
    mem.read(&store, offset, &mut buffer)?;
    assert_eq!(b"hello", &buffer);

    assert_eq!(&mem.data(&store)[offset..offset + 5], b"hello");
    mem.data_mut(&mut *store)[offset..offset + 5].copy_from_slice(b"bye!!");

    Ok(())
}

字符串

常见问题

1.什么是offset?我的印象是它不是一个指针地址,而是从WASM模块的内存开始的usize偏移量。
1.假设这是正确的,我如何得到一个特定变量的偏移量?我看到了很多例子,其中一个随机值(比如510),但在我自己的例子中,任何大于0 segfaults。我想我可能误解了offset是什么。
1.共享的WASM内存需要由主机分配吗?我的假设是WASM模块本身会自然地扩展自己的内存(就像它在本机运行时一样)。如果我必须在主机上分配内存,如果是WASM模块创建了使用内存的变量,我如何确定要分配多少内存?

ldfqzlk8

ldfqzlk81#

关于您的问题

1.是的,offset是从WASM内存开始的偏移量。它就像WASM内存中的指针。试图在主机Rust应用程序中访问offset作为正常指针可能会导致segfault。
1.宿主Rust应用程序的变量与WASM内存分离。它们不会自动成为WASM内存内容的一部分。这就是WASM的全部技巧。所有内容都必须显式复制到WASM内存中,或者从WASM内存中读取并写入宿主应用程序的变量。
1.如果您需要WASM示例内部的内存,您必须手动分配它,例如使用导出的malloc()函数(稍后详细介绍)。对于每次分配,您需要知道需要多少字节。

如何从TinyGo WASM模块中读取字符串

  1. TinyGo在跨越WASM边界[0,1]时将字符串编码为(ptr, len)元组。
    1.由于返回非平凡类型是一个相当新的特性[2],TinyGo使用了一种解决方法(可能是为了向后兼容):它不是返回(ptr, len)元组,而是要求 * 你 * 传递一个指向空闲内存段/缓冲区的指针,在那里它可以存储(ptr, len)元组。因为ptrleni32类型,你需要传递一个8字节的缓冲区。
    1.从哪里获取缓冲区?首先需要分配它,这必须发生在WASM内存中,所以需要调用模块的导出malloc函数。
    1.现在您可以调用返回字符串的函数,同时将缓冲区作为参数传递。
    1.然后必须从WASM内存中读取(ptr, len)元组。
    1.最后,从WASM内存中读取[ptr..ptr+len],并将字节转换为Rust String。

一个简单的例子:

1.创建一个基本的TinyGo WASM模块,导出一个返回字符串的函数:

package main

//export ReturnString
func ReturnString() string {
    return "hello from TinyGo/WASM"
}

func main() {}

字符串
使用TinyGo将其编译为WASM:tinygo build -o return_string.wasm -target wasm ./return_string.go

  1. Rust Code:
use wasmtime::*;
use wasmtime_wasi::sync::WasiCtxBuilder;
use std::mem;

/// Go's string representation for export.
/// 
/// According to <https://tinygo.org/docs/concepts/compiler-internals/datatypes/#string> and
/// <https://github.com/tinygo-org/tinygo/blob/731532cd2b6353b60b443343b51296ec0fafae09/src/runtime/string.go#L10-L13>
#[derive(Debug)]
#[repr(C)]
struct GoStringParameters {
    ptr: i32,
    len: i32,
}

fn main() {
    // Create wasmtime runtime with WASI support, according to <https://docs.wasmtime.dev/examples-rust-wasi.html#wasirs>
    let engine = Engine::default();
    let module = Module::from_file(&engine, "../return_string.wasm").expect("Create module");
    let mut linker = Linker::new(&engine);
    let wasi = WasiCtxBuilder::new()
        .inherit_stdio()
        .inherit_args().expect("WASI: inherit args")
        .build();
    let mut store = Store::new(&engine, wasi);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s).expect("Add WASI to linker");
    let instance = linker.instantiate(&mut store, &module).expect("Create instance");

    // malloc a GoStringParameters in WASM memory
    let go_str_addr = {
        let malloc = instance.get_func(&mut store, "malloc").expect("Couldn't get malloc function");
        let mut result = [wasmtime::Val::I32(0)];
        malloc.call(&mut store, &[wasmtime::Val::I32(mem::size_of::<GoStringParameters>() as i32)], &mut result).expect("malloc GoStringParameters");
        result[0].unwrap_i32()
    };

    // Call ReturnString() and pass a pointer where it should store the GoStringParameters
    let wasm_return_string_function = instance.get_func(&mut store, "ReturnString").expect("Couldn't get function");
    wasm_return_string_function.call(&mut store, &[wasmtime::Val::I32(go_str_addr)], &mut []).expect("Call ReturnString");

    // Read the GoStringParameters from WASM memory
    let mut buf = [0u8; mem::size_of::<GoStringParameters>()];
    let mem = instance.get_memory(&mut store, "memory").unwrap();
    mem.read(&mut store, go_str_addr as usize, &mut buf).expect("Get WASM memory");
    // SAFETY: This hack (mem::transmute) only works on little endian machines, because WASM memory is always in little endian
    let go_str_parameters: GoStringParameters = unsafe { mem::transmute(buf) };
    dbg!(&go_str_parameters);
    
    // Read the actual bytes of the string from WASM memory
    let mut str_bytes = vec![0u8; go_str_parameters.len as usize];
    mem.read(&mut store, go_str_parameters.ptr as usize, &mut str_bytes).expect("Read string bytes");
    let rust_str = String::from_utf8(str_bytes).unwrap();
    dbg!(rust_str);

   // TODO: Call exported free() function on the GoStringParameters address
}


输出量:

$ cargo run -q --release
[src/main.rs:36] &go_str_parameters = GoStringParameters {
    ptr: 65736,
    len: 22,
}
[src/main.rs:42] rust_str = "hello from TinyGo/WASM"

相关问题