Learning WASM #10

Managedな値をホストとの共有メモリに書き出してみる。 とは言っても、バイト列に変換して、共有メモリにコピーするだけ。

WASM側

using System.Runtime.InteropServices;
using System.Text;

namespace classlib;

public static class Class1
{
    [UnmanagedCallersOnly(EntryPoint = "Read")]
    public static IntPtr Read() 
    {
        var str = ManagedString();
        return WriteToMemory(str);
    }
    private static IntPtr WriteToMemory(string str) {
        var prefix = sizeof(int);
        var bytes = Encoding.ASCII.GetBytes(str);
        var ptr = Marshal.AllocHGlobal(prefix + bytes.Length);

        Marshal.WriteInt32(ptr, bytes.Length);
        for (var i = 0; i < bytes.Length; i++) {
            Marshal.WriteByte(ptr + prefix + i, bytes[i]);
        }
        
        return ptr;
    }
    private static string ManagedString() {
        return "This is managed string value";
    }
}

最初の4バイトはペイロードの長さをintで入れる。C#のエンディアンって何だっけかな。 まあどっちもC#だから問題なし。

ホスト側

using System.Text;
using Wasmtime;

var wasm = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "lib.wasm");
Console.WriteLine($"Load wasm file: {wasm}");
using var engine = new Engine();
using var module = Module.FromFile(engine, wasm);
using var linker = new Linker(engine);
using var store = new Store(engine);

linker.DefineWasi();
store.SetWasiConfiguration(new WasiConfiguration());

var instance = linker.Instantiate(store, module);

var init = instance.GetAction("_initialize");
if (init is null)
{
    Console.WriteLine("error: MyAdd export is missing");
    return;
}

init();

var read = instance.GetFunction<int>("Read");
if (read is null)
{
    Console.WriteLine("error: Read export is missing");
    return;
}

var ptr = read();
var mem = instance.GetMemory("memory")!;
var len = BitConverter.ToInt32(mem.GetSpan(ptr, sizeof(int)));
var str = Encoding.ASCII.GetString(mem!.GetSpan(ptr + sizeof(int), len));

Console.WriteLine(str);

実行結果

This is managed string value

コピーしてるのでそこがボトルネック。バイト列とるときに共有メモリに置きたい。

そもそも、ゲスト側のメモリ自体がmemoryなわけだからbytesのポインタさえ取れればホストでも取れそうだ。unsafeでコンパイルしないとだめだけど。