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 and wasm-unknown: allocator always allocates 2 pages (2x64k) #4223

Open
orsinium opened this issue Apr 11, 2024 · 5 comments
Open

wasm and wasm-unknown: allocator always allocates 2 pages (2x64k) #4223

orsinium opened this issue Apr 11, 2024 · 5 comments
Labels
wasm WebAssembly

Comments

@orsinium
Copy link

What happens

When compiling to WASM (both wasi and wasm-unknown targets) with GC (I tried leaking, conservative, and precise, 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):

0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
0003980 0000 0000 0005 0000 39f0 0000 39f0 0000
0003990 39f0 0000 39f0 0000 39f0 0000 0000 0000
00039a0 7073 6972 6574 0000 0000 0002 f903 0001
00039b0 0001 0000 1bf1 0000 0001 0000 0000 0000
00039c0 0001 0000 0000 0000 0000 0000 0000 0000
00039d0 0000 0000 0000 0000 0001 0000 0000 0000
00039e0 39f0 0000 0000 0000 0001 0000 0000 0000
00039f0 0000 0000 0000 0000 0000 0000 0000 0000
*
001f900 0000 0100 0000 0000 0000 0000 0000 0000
001f910 0000 0000 0000 0000 0000 0000 0000 0000
*
0020000

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

Error: error while executing at wasm backtrace:
    0:  0x115 - runtime.abort
                    at /usr/local/lib/tinygo/src/runtime/runtime_tinygowasm_unknown.go:29:6              - runtime.runtimePanicAt
                    at /usr/local/lib/tinygo/src/runtime/panic.go:87:7              - runtime.runtimePanic
                    at /usr/local/lib/tinygo/src/runtime/panic.go:72:16              - runtime.alloc
                    at /usr/local/lib/tinygo/src/runtime/gc_leaking.go:44:15
    1:  0x12f - main.LoadFile
                    at /home/gram/Documents/tinygo-gc-debug/main.go:21:13              - boot
                    at /home/gram/Documents/tinygo-gc-debug/main.go:11:17

Caused by:
    wasm trap: wasm `unreachable` instruction executed

It leads to the following line of TinyGo runtime:

// Unfortunately the heap could not be increased. This
// happens on baremetal systems for example (where all
// available RAM has already been dedicated to the heap).
runtimePanicAt(returnAddress(0), "out of memory")

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 open target.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.

@orsinium orsinium added the wasm WebAssembly label Apr 11, 2024
@orsinium orsinium changed the title wasm and wasm-unknown: the first page (64k) is not used for allocations wasm and wasm-unknown: allocator always allocates 2 pages (2x64k) Apr 11, 2024
@orsinium
Copy link
Author

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

@deadprogram
Copy link
Member

@orsinium can you please also try removing "-mno-bulk-memory"

@aykevl
Copy link
Member

aykevl commented Apr 27, 2024

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).

@orsinium
Copy link
Author

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

@aykevl
Copy link
Member

aykevl commented May 13, 2024

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.

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

No branches or pull requests

3 participants