问题

我已经启动并运行了wasmtime个,从Rust主机调用TinyGo WASM/WASI模块.一切都很好,直到我try 从Go Wasi模块返回一个字符串,这似乎是每个人都在努力解决的问题.我理解在特定位置访问WASM模块的内存并读取特定长度的概念;我不理解的是如何使用offset而不是指针来实现这一点.

我认为,对S自己的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偏移量.
  2. 假设这是正确的,我如何获得特定变量的偏移量?我看到了很多使用随机值的示例(比如使用510),但在我自己的示例中,任何大于0的值都是段错误.我想我可能误解了offset是什么.
  3. 共享WASM内存是否需要由主机分配?我的假设是,WASM模块本身会自然地扩展它自己的内存(就像它在本地运行时一样).如果我必须在主机上分配内存,如果是WASM模块创建了使用内存的变量,我如何确定要分配多少内存?

推荐答案

Regarding your questions

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

How to read a string from a TinyGo WASM module

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

A simple example:

  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"

Go相关问答推荐

SEARCH On Conflict Clause不考虑乐观锁定版本

Go Net/http路由

为什么我不能使用Docker从本地访问我的Gin应用程序?

GORM没有从 struct 创建完整的表,如何修复?

Golang中的泛型 struct /接口列表

";无效的复制因子;融合Kafka Go客户端

在 GoLang 中对自定义 struct 体数组进行排序

生成一个 CSV/Excel,在 Golang 中该列的下拉选项中指定值

使用Go和Operator SDK通过API调用设置Kubernetes Pods的安装步骤

将 firestoreinteger_value转换为整数

如何以干净的方式在中间件中注入 repo 或服务?

golang:解组动态 YAML 注释

上传图片失败,出现错误dial tcp: lookup api.cloudinary.com: no such host

也许在 golang 中包(字符串和字符串类型不匹配)

将 big.Int 转换为 [2]int64,反之亦然和二进制补码

Golang并发写入多个文件

如何排除溢出矩阵的坐标

为什么 go.mod 中的所有依赖都是间接的?

Go:如何创建一个可以提供配置文件中描述的 url 的服务器

如何迭代在泛型函数中传递的片的并集?