Skip to content

fix(parser): do not fuse local.get/local.tee across a branch target - #56

Open
stevefan1999-personal wants to merge 1 commit into
explodingcamera:nextfrom
stevefan1999-personal:fix/memory-oob-trap
Open

fix(parser): do not fuse local.get/local.tee across a branch target#56
stevefan1999-personal wants to merge 1 commit into
explodingcamera:nextfrom
stevefan1999-personal:fix/memory-oob-trap

Conversation

@stevefan1999-personal

Copy link
Copy Markdown

I was trying to compile TCC to WASI and run it using tinywasm thanks to the exception implementation (sigh, which is because of the use of setjmp/longjmp for compiler rewind, that's the whole reason, WASI syscall is really not that hard to say the least), but it went into a mysterious bug that keeps trapping on the same address.

And yes, that means I did have a WASIp1 implementaion and I actually implemented it with a VFS (nix the network socket just yet), so I can theoretically have a cross-platform JIT compiler where I can have safe reentrancy, and it is not until recently the TCC mob branches have their TLS implementations, but it is all too late and they still rely on CRT. It is also theoretically possible.

So my plan is to compile libtcc as small as possible to WASI using fat LTO, and I keep getting trapped on the same address. Under NodeJS with a shim WASI implementation, the whole libtcc fat LTO WASI actually works. So it has to be a problem in tinywasm

Claude said this (it is Fable 5.1 btw):

visit_local_tee peeked at the last emitted instruction and, when it was a local.get, replaced the pair with LocalCopy(src, dst); LocalGet(dst). It never checked whether the tee's position is a jump target. When the local.get is a block's fallthrough result and the local.tee is the first instruction after end, every branch to that label lands on LocalGet(dst) instead of the tee: the branch's value is left dangling on the stack and a stale local is pushed on top. Loop starts and if/else joins have the same shape.

TinyCC compiled with clang -Os/-Oz hit this in musl's realloc: the if (!p) { r = malloc(n); br 1 } path returned n instead of r, so the caller wrote into address 1200 and later trapped with an out-of-bounds load, while V8 and wabt ran the same module correctly.

The lowering step now always emits a plain LocalTee. The fusion moves into the peephole rewriter, which only matches within a basic block and so cannot cross a label. Regression test covers the block-end, if/else-join and loop-start cases.

I will try to use https://www.npmjs.com/package/@yowasp/clang to see if there are more VM shenanigans, though a simple benchmark shows me that the interpreter is 17x slower than native, and I guess the clang in tinywasm wouldn't take less than 3 minutes to get a meaningful outcome -- except if it blows up midway during execution.

`visit_local_tee` peeked at the last emitted instruction and, when it was a
`local.get`, replaced the pair with `LocalCopy(src, dst); LocalGet(dst)`.
It never checked whether the tee's position is a jump target. When the
`local.get` is a block's fallthrough result and the `local.tee` is the first
instruction after `end`, every branch to that label lands on `LocalGet(dst)`
instead of the tee: the branch's value is left dangling on the stack and a
stale local is pushed on top. Loop starts and if/else joins have the same
shape.

TinyCC compiled with clang -Os/-Oz hit this in musl's realloc: the
`if (!p) { r = malloc(n); br 1 }` path returned n instead of r, so the
caller wrote into address 1200 and later trapped with an out-of-bounds
load, while V8 and wabt ran the same module correctly.

The lowering step now always emits a plain `LocalTee`. The fusion moves
into the peephole rewriter, which only matches within a basic block and so
cannot cross a label. Regression test covers the block-end, if/else-join
and loop-start cases.
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

Successfully merging this pull request may close these issues.

1 participant