grain-lang / grain

The Grain compiler toolchain and CLI. Home of the modern web staple. 🌾
https://grain-lang.org/
GNU Lesser General Public License v3.0
3.29k stars 115 forks source link

Error running componentized wasip1 module #2153

Open lann opened 2 months ago

lann commented 2 months ago
$ grain --version
0.6.6

$ cat hello.gr
module Main
print("Hello, World")

$ grain compile hello.gr --release

$ wget https://github.com/bytecodealliance/wasmtime/releases/download/v24.0.0/wasi_snapshot_preview1.command.wasm
[...]
$ wasm-tools component new hello.gr.wasm --adapt wasi_snapshot_preview1.command.wasm -o hello.component.wasm

$ wasmtime --version
wasmtime-cli 24.0.0
$ wasmtime run hello.gr.wasm
Hello, World
$ wasmtime run hello.component.wasm
GrainExceptionError: failed to run main module `hello.component.wasm`

Caused by:
    0: failed to invoke `run` function
    1: error while executing at wasm backtrace:
           0:  0xc18 - <unknown>!<wasm function 5>
           1: 0x1c24 - <unknown>!<wasm function 19>
           2: 0x859f - <unknown>!<wasm function 82>
           3: 0x85d4 - <unknown>!<wasm function 83>
           4: 0x8c46 - wit-component:adapter:wasi_snapshot_preview1!wasi:cli/run@0.2.0#run
    2: wasm trap: wasm `unreachable` instruction executed
lann commented 2 months ago

@ospencer it looks like I could use some of those pointers... :sweat_smile:

lann commented 2 months ago

Unfortunately due to #2152 I can't run with --debug, but manually poking around at the names section it looks like:

<wasm function 5> -> _ZN136_$LT$wasi_snapshot_preview1..bindings..wasi..io..streams..InputStream$u20$as$u20$wasi_snapshot_preview1..bindings.._rt..WasmResource$GT$4drop4drop17h2cb53bfcb8abc006E
<wasm function 19> -> wasi:cli/run@0.2.0#run

(82 and 83 don't have names)

Edit: Wrong symbols here; ignore this

lann commented 2 months ago

Ah this is two separate errors smushed together:

GrainExceptionError: failed to run main module `hello.component.wasm`

GrainException (from the Grain runtime) + Error: failed to run ... (from Wasmtime)

alexcrichton commented 2 months ago

I was helping @lann debug this a bit and I'm definitely new here to Grain so take this with a grain of salt. From the component/adapter side of things, however, one major thing that the adapter will do which Grain may not be expecting is that it will use memory.grow to allocate a wasm page for itself to place all its intermediate data within. This happens before Grain is invoked because at the component level the adapter is the entrypoint and then that delegates to Grain itself.

It looks like Grain's allocator will use memory.size to determine the initial size of the heap which, if this is true, ends up being incompatible with the adapter. This can be solved by either (a) exporting a cabi_realloc function from the core Grain module (which the adapter will call to allocate memory and then that'd initialize everything correctly) or (b) updating the malloc implementation to have a different heuristic for determining the initial size of the heap. With LLVM and wasm-ld the (b) solution looks like using the __heap_end symbol as the end of the heap. This solution though is wasm-ld specific and may not be easily applicable to Grain, however.

ospencer commented 2 months ago

@lann I've got this repository here which has a couple of examples of Grain components: a basic command component, a wasi:http/proxy one, and a wasi:http/imports one. https://github.com/ospencer/grain-components

Each of the subfolders has a basic README with instructions on how to compile the Grain modules and then the right wasm-tools invocation.

@alexcrichton is exactly right—the compiler doesn't export cabi_realloc itself, but if bindings are generated through wit-bindgen it will. There's an experimental grain branch on this fork that you can use to generate bindings from wit: https://github.com/grain-lang/wit-bindgen/tree/grain

Finishing up the bindgen/documenting it all/baking it into the toolchain has been on the TODO list for awhile 😅

But this should all work!

ospencer commented 2 months ago

Ah this is two separate errors smushed together:

GrainExceptionError: failed to run main module `hello.component.wasm`

GrainException (from the Grain runtime) + Error: failed to run ... (from Wasmtime)

This'll happen because Grain's initialization code runs as a part of _start. --use-start-section will instead move it to a start section to guarantee it's called at the right time. IIRC Rust and some other languages will just make sure any initialization code has been run whenever an export is called. We've discussed this but haven't implemented anything just yet.

ospencer commented 2 months ago

If you run into other issues, feel free to ping me here or on Discord/Slack!