bytecodealliance / wasmtime-dotnet

.NET embedding of Wasmtime https://bytecodealliance.github.io/wasmtime-dotnet/
Apache License 2.0
409 stars 52 forks source link

WASM that runs in wasmtime will not run in the library #278

Open williammortl opened 10 months ago

williammortl commented 10 months ago

Hello, I have a Rust hello world app:

fn main() {
    println!("Hello, world!");
}

I compile it:

cargo build --target wasm32-wasi

and can run it in wasmtime wasmtime run hello-world.wasm without issue. However, when I run it in my C# app:

// See https://aka.ms/new-console-template for more information
using Wasmtime;

var config = new Config();
config.WithWasmThreads(true);
using var engine = new Engine(config);

using var module = Module.FromFile(engine, "hello-world.wasm");

using var linker = new Linker(engine);
using var store = new Store(engine);

var instance = linker.Instantiate(store, module);
var run = instance.GetAction("run")!;
run();

and I get this error:

Wasmtime.WasmtimeException: 'unknown import: `wasi_snapshot_preview1::fd_write` has not been defined'

any thoughts?

martindevans commented 10 months ago

It looks like you need to enable WASI for this code to work.

williammortl commented 10 months ago

@martindevans thank you so much - I closed this when I realized what a bonehead I was... that said, since you were kind enough to respond, I wonder if you could answer a followup...

I have this multi-threaded C code:

#include <pthread.h>
#include <stdio.h>
#include <string.h>

#define NUM_THREADS 10

void *thread_entry_point(void *ctx) {
  int id = (int) ctx;
  printf(" in thread %d\n", id);
  return 0;
}

extern void launch_threads() {
  pthread_t threads[10];
  for (int i = 0; i < NUM_THREADS; i++) {
    int ret = pthread_create(&threads[i], NULL, &thread_entry_point, (void *) i);
    if (ret) {
      printf("failed to spawn thread: %s", strerror(ret));
    }
  }
  for (int i = 0; i < NUM_THREADS; i++) {
    pthread_join(threads[i], NULL);
  }
}

int main(int argc, char **argv) {
  launch_threads();
  return 0;
}

I compile it thusly:

$WASI_SDK_DIR/bin/clang --sysroot $WASI_SDK_DIR/share/wasi-sysroot --target=wasm32-wasi-threads -pthread -Wl,--import-memory,--export-memory,--max-memory=67108864 src/threads.c -o bin/threads.wasm

I can then run it without issue:

wasmtime --wasm threads --wasi threads bin/threads.wasm

However, even after (I think) enabling threads in this C# code, it fails:

// See https://aka.ms/new-console-template for more information
using Wasmtime;

var wasi = new WasiConfiguration()
    .WithInheritedStandardInput()
    .WithInheritedStandardOutput()
    .WithInheritedStandardError();
var config = new Config()
    .WithWasmThreads(true)
    .WithBulkMemory(true)
    .WithMultiMemory(true);
using var engine = new Engine(config);
using var store = new Store(engine);
store.SetWasiConfiguration(wasi);
using var linker = new Linker(engine);
linker.DefineWasi();

using var module = Module.FromFile(engine, "threads.wasm");

var instance = linker.Instantiate(store, module);
var run = instance.GetFunction("_start").Invoke();

on the linker.Instantiate line, I get error:

Wasmtime.WasmtimeException: 'unknown import: "env::memory" has not been defined'

Any ideas or tips you could give me?

martindevans commented 10 months ago

I really don't know a lot about WASI threads, the following is from some very quick research I just did right now and could definitely be wrong!

It looks like a WASI threads program imports a "shared memory" called env::memory (ref). According to the error you're getting this has not been defined, so instantiating fails.

Wasmtime has a SharedMemory struct, so I assume that's what you'd need to add to the Linker make your program work.

However looking through the Wasmtime C-API (which is what wasmtime-dotnet uses to interact with wasmtime) I don't see a mention of the word "shared" anywhere in memory.h or wasi.h. It's possible this can't be done through the C-API.

martindevans commented 10 months ago

To follow up to that, there's this comment in Config.h:

Note that threads are largely unimplemented in Wasmtime at this time.

peaceshi commented 8 months ago

I have a similar issue. An unhandled exception occurred when I ran it from C#.

// zig build-exe main.zig -target wasm32-wasi -OReleaseSmall
const std = @import("std");

pub fn main() void {
    std.debug.print("wasm: Hello, world!\n", .{});
}
Console.WriteLine("Hello, World!");
var resourceStream = await Assembly.GetExecutingAssembly().ReadResourceAsync("main.wasm");
var wasiConfig = new WasiConfiguration()
    .WithInheritedStandardInput()
    .WithInheritedStandardOutput()
    .WithInheritedStandardError()
    .WithInheritedArgs()
    .WithInheritedEnvironment();
using var config = new Config()
    .WithDebugInfo(true)
    .WithCraneliftDebugVerifier(true)
    .WithOptimizationLevel(OptimizationLevel.SpeedAndSize)
    .WithWasmThreads(true)
    .WithBulkMemory(true)
    .WithMultiMemory(true);

using var engine = new Engine(config);
using var linker = new Linker(engine);
using var store = new Store(engine);
using var module = Wasmtime.Module.FromStream(engine, "main.wasm", resourceStream);

store.SetWasiConfiguration(wasiConfig);
linker.DefineWasi();
linker.DefineModule(store,module);
var instance = linker.Instantiate(store, module);
instance.GetAction("_start")?.Invoke(); // line 44 here

Output:

Hello, World!
wasm: Hello, world!
Unhandled exception. Wasmtime.WasmtimeException: error while executing at wasm backtrace:                               
    0:   0xa9 - <unknown>!_start                                                                                        

Caused by:                                                                                                              
    Exited with i32 exit status 0                                                                                       
   at Wasmtime.Function.Invoke(Span`1 argumentsAndResults, StoreContext storeContext)                                   
   at Wasmtime.Function.InvokeWithoutReturn(Span`1 arguments, StoreContext storeContext)                                
   at Wasmtime.Function.<>c__DisplayClass171_0.<WrapAction>b__0()                                                       
   at WasmtimeApp.Program.Main(String[] args) in Program.cs:line 44
   at WasmtimeApp.Program.<Main>(String[] args)                                                                         

Exit code -532,462,766.

The wat is:

(module
  (type $t0 (func (param i32)))
  (type $t1 (func (param i32 i32 i32 i32) (result i32)))
  (type $t2 (func))
  (import "wasi_snapshot_preview1" "proc_exit" (func $wasi_snapshot_preview1.proc_exit (type $t0)))
  (import "wasi_snapshot_preview1" "fd_write" (func $wasi_snapshot_preview1.fd_write (type $t1)))
  (func $_start (export "_start") (type $t2)
    (call $f3)
    (call $wasi_snapshot_preview1.proc_exit
      (i32.const 0))
    (unreachable))
  (func $f3 (type $t2)
    (local $l0 i32) (local $l1 i32)
    (global.set $g0
      (local.tee $l0
        (i32.sub
          (global.get $g0)
          (i32.const 16))))
    (local.set $l1
      (i32.const 0))
    (block $B0
      (br_if $B0
        (i32.load8_u offset=1048597
          (i32.const 0)))
      (i32.store8 offset=1048597
        (i32.const 0)
        (i32.const 1)))
    (block $B1
      (loop $L2
        (br_if $B1
          (i32.eq
            (local.get $l1)
            (i32.const 20)))
        (i32.store offset=8
          (local.get $l0)
          (i32.sub
            (i32.const 20)
            (local.get $l1)))
        (i32.store offset=4
          (local.get $l0)
          (i32.add
            (local.get $l1)
            (i32.const 1048576)))
        (br_if $B1
          (i32.and
            (call $wasi_snapshot_preview1.fd_write
              (i32.const 2)
              (i32.add
                (local.get $l0)
                (i32.const 4))
              (i32.const 1)
              (i32.add
                (local.get $l0)
                (i32.const 12)))
            (i32.const 65535)))
        (local.set $l1
          (i32.add
            (i32.load offset=12
              (local.get $l0))
            (local.get $l1)))
        (br $L2)))
    (i32.store8 offset=1048597
      (i32.const 0)
      (i32.const 0))
    (global.set $g0
      (i32.add
        (local.get $l0)
        (i32.const 16))))
  (memory $memory (export "memory") 17)
  (global $g0 (mut i32) (i32.const 1048576))
  (data $d0 (i32.const 1048576) "wasm: Hello, world!\0a\00"))
peterhuene commented 8 months ago

Hi @peaceshi. When running a WASI program with the .NET bindings, you'll want to catch WasmtimeException where you invoke the start function and check if ExitCode on the caught exception is 0, which indicates a successful exit.

ExitCode will be null when the exception is not from calling proc_exit. It will also be non-zero in the case of an unsuccessful exit via proc_exit.

It's a little unfortunate that Zig is calling proc_exit(0) after the user main as it can simply return from _start; proc_exit must be implemented via a trap that gets translated to a .NET exception as execution of the wasm guest must immediately abort.