Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
williammortl opened this issue Oct 15, 2023 · 6 comments
Open

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

williammortl opened this issue Oct 15, 2023 · 6 comments

Comments

@williammortl
Copy link

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
Copy link
Contributor

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

@williammortl
Copy link
Author

williammortl commented Oct 17, 2023

@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?

@williammortl williammortl reopened this Oct 17, 2023
@martindevans
Copy link
Contributor

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
Copy link
Contributor

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

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

@peaceshi
Copy link

peaceshi commented Jan 5, 2024

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
Copy link
Member

peterhuene commented Jan 5, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants