Open orsinium opened 2 months ago
@deadprogram asked me for a smaller code to reproduce and without Rust.
main.go
:
package main
var data []byte
//go:export boot
func Boot() {
fileSize := 1
data = make([]byte, fileSize)
data[0] = 3
f(data)
}
//go:noinline
func f([]byte) {}
target.json
:
{
"llvm-target": "wasm32-unknown-unknown",
"cpu": "generic",
"features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory",
"build-tags": ["tinygo.wasm", "wasm_unknown"],
"goos": "linux",
"goarch": "arm",
"linker": "wasm-ld",
"rtlib": "compiler-rt",
"scheduler": "none",
"gc": "leaking",
"libc": "wasmbuiltins",
"cflags": ["-mno-bulk-memory", "-mnontrapping-fptoint", "-msign-ext"],
"ldflags": [
"--allow-undefined",
"--no-demangle",
"--initial-memory=65536",
"--max-memory=65536",
"--stack-first",
"--no-entry",
"-zstack-size=14752"
],
"extra-files": ["src/runtime/asm_tinygowasm.S"]
}
Running with wasmer:
wasmer run ./tinygo-gc-debug.wasm --entrypoint boot
Traceback:
error: RuntimeError: unreachable
at runtime.alloc (<module>[2]:0xe0)
at boot (<module>[3]:0xfa)
@orsinium can you please also try removing "-mno-bulk-memory"
Running with wasmer:
wasmer run ./tinygo-gc-debug.wasm --entrypoint boot
Don't specify a different entrypoint. This likely causes the crash you're seeing. Just let _start
be called (it initializes important data structures like the heap).
can you please also try removing
"-mno-bulk-memory"
That's unlikely to have an effect, the only thing it does is change whether LLVM is allowed to use bulk memory instructions (it doesn't affect memory layout).
can you please also try removing "-mno-bulk-memory"
Doesn't help.
Just let _start be called (it initializes important data structures like the heap).
There is no _start
section in the generated wasm file:
❯ wasm-objdump -x --section=export ./tinygo-gc-debug.wasm
tinygo-gc-debug.wasm: file format wasm 0x1
Section Details:
Export[3]:
- memory[0] -> "memory"
- func[1] <_initialize> -> "_initialize"
- func[3] <boot> -> "boot"
Probably, because there is "--no-entry"
specified in the target file. If I try removing it:
wasm-ld: error: entry symbol not defined (pass --no-entry to suppress): _start
failed to run tool: wasm-ld
error: failed to link /tmp/tinygo180852312/main: exit status 1
It looks like you've modified some internals of TinyGo and are reporting issues as a result of that.
If you want us to help, I suggest you use either one of the built-in targets (like -target=wasi
) or describe exactly the modifications you made and the platform you're targetting.
The TinyGo compiler I use is not modified. I installed the artifact from this job: https://github.com/tinygo-org/tinygo/actions/runs/8469118660
I use a custom target.json
which is attached to the issue and in the comment above. The wasm-unknown
target expects the memory to be imported and the wasi
target cannot be used with the environment that I target.
$ tinygo build -target=wasm-unknown .
$ wasmer run ./tinygo-gc-debug.wasm --entrypoint boot
error: Unable to instantiate the WebAssembly module
╰─▶ 1: Error while importing "env"."memory": unknown import. Expected Memory(MemoryType { minimum: 2 pages, maximum: None, shared: false })
Ok, in that case you need to make sure that the first thing that's called is _initialize
. This exported function needs to be called before anything else to initialize the heap and various other important data structures.
What happens
When compiling to WASM (both
wasi
andwasm-unknown
targets) with GC (I triedleaking
,conservative
, andprecise
, same story each time), if I limit the available memory to a single page (64 kB), the code fails with "out of memory", even though I request only 1 byte allocation.What I expect
I expect that 64 kB of memory should be enough to fit not only the stack and the GC metadata but also some of the application memory.
Memory layout
Here is the hexdump of the memory after the allocation with the upper memory limit disabled (in other words, when I let TinyGo runtime to allocate the second memory page):
There is some data up to 14752, which is the stack size in this example (I also tried decreasing the stack size, but it doesn't help), then only a few bytes are used at the beginning of the heap, and then there is one bit set at the very end of the second page, which I expect is the allocator metadata. That's it. So, apparently, the first page is not fully occupied, unless it's reserved for something but not used (and so all bits are zero).
What's curious, if you put some data into the allocated slice, you will see it at the address 0x00039d0 (14800) which is just a bit after the stack. That means, the runtime doesn't actually need the second page for the allocation but allocates it regardless. Explicitly setting the capacity for
make
doesn't help.Traceback
It leads to the following line of TinyGo runtime:
To reproduce
Here is a self-contained demo, with
target.json
and wasmtime+Rust-powered runtime:tinygo-gc-debug.zip
If you run
task
, it should fail with the traceback above. If you opentarget.json
, remove"--max-memory=65536",
from it, and run again, everything works but runtime allocates a second page of memory, which it shouldn't do.