Skip to content

Conversation

@sbc100
Copy link
Collaborator

@sbc100 sbc100 commented Nov 4, 2022

Also, when not using santizers (which currently rely on using the start
of memory).

This allows STACK_OVERFLOW_CHECK=1 to detect stack overflow conditions
better without relying on the STACK_OVERFLOW_CHECK=2 binaryen pass.

This works because when stack is placed first in memory stack overflow
results in SP dropping below zero which is a runtime error. We can
then distinguish such runtime errors by looking at the SP value at the
time of the crash.

This change is part of a sequence of the effort to reduce the default
stack size. The hope here is that we will be able to accurately catch
overflows in debug builds even without the STACK_OVERFLOW_CHECK=2
binaryen pass, thus minimizing the impact of reducing the stack size.

See #14177

Copy link
Member

@dschuff dschuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything useful we can say in the changelog? Maybe just highlighting the user-visible difference in stack overflow behavior in unoptimized builds?

@sbc100 sbc100 force-pushed the ptrToString branch 3 times, most recently from 8ec1baa to 843dec7 Compare November 7, 2022 21:34
@sbc100 sbc100 force-pushed the stack_first branch 2 times, most recently from f8a077b to 31b275b Compare November 8, 2022 00:39
@sbc100
Copy link
Collaborator Author

sbc100 commented Nov 8, 2022

Added a ChangeLog entry

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the ptrToString changes are from the other PR I think?)

Base automatically changed from ptrToString to main November 8, 2022 20:50
@sbc100 sbc100 force-pushed the stack_first branch 2 times, most recently from f5074b6 to d92f169 Compare November 8, 2022 22:02
@sbc100
Copy link
Collaborator Author

sbc100 commented Nov 8, 2022

(the ptrToString changes are from the other PR I think?)

yes sorry, when I stack PRs like that and then one of them gets merged, it confuses github. Please ignore.

@sbc100 sbc100 closed this Nov 8, 2022
@sbc100 sbc100 reopened this Nov 8, 2022
@sbc100 sbc100 enabled auto-merge (squash) November 8, 2022 22:13
@sbc100 sbc100 force-pushed the stack_first branch 2 times, most recently from 0290330 to 07ac46a Compare November 9, 2022 23:57
@dschuff
Copy link
Member

dschuff commented Nov 10, 2022

I just found out that one of our users is doing something like the following in JS: EM_ASM_DOUBLE({"return HEAP8.length - STACK_SIZE"}). If you ignore the fact that it misses the static data, the intent is to give the total memory available to the app (i.e. the heap size). That obviously only works with globals-first and not with stack-first. Is there a portable way to get the heap size? On the emscripten side I see STACK_HIGH, STACK_LOW, HEAP_BASE, and GLOBAL_BASE but that's not enough to portably get the stack size. Is there maybe a linker-generated symbol?

@dschuff
Copy link
Member

dschuff commented Nov 10, 2022

actually, answering my own question: it looks like https://reviews.llvm.org/D136110 will allow that.
Nevermind; I think there is a way, because the heap is always last, right? So HEAP8.length - HEAP_BASE will always give that size, right?
And in C you could use the linker-generated symbol and do __builtin_wasm_memory_size() - __heap_base

@sbc100
Copy link
Collaborator Author

sbc100 commented Nov 10, 2022

actually, answering my own question: it looks like https://reviews.llvm.org/D136110 will allow that.

Using HEAP8.length - __heap_base is probably the quickest fix, and it works for growable memory too.

Also, the value of HEAP8.length - STACK_SIZE will be the same both before and after this change.. what makes you think this change will break this? HEAP8.length - STACK_SIZE is the size of linear memory minus the stack size, which is the combined size of the heap and static data... this is true regardless of whether the stack comes first.

@dschuff
Copy link
Member

dschuff commented Nov 10, 2022

oh, yeah, you're right. I was looking for a way that also correctly accounts for the globals size.

@sbc100 sbc100 force-pushed the stack_first branch 3 times, most recently from 730e936 to c55df19 Compare November 10, 2022 02:43
Also, when not using santizers (which currently rely on using the start
of memory).

This allows STACK_OVERFLOW_CHECK=1 to detect stack overflow conditions
better without relying on the STACK_OVERFLOW_CHECK=2 binaryen pass.

This works because when stack is placed first in memory stack overflow
results in SP dropping below zero which is a runtime error.  We can
then distinguish such runtime errors by looking at the SP value at the
time of the crash.

This change is part of a sequence of the effort to reduce the default
stack size.  The hope here is that we will be able to accurately catch
overflows in debug builds even without the STACK_OVERFLOW_CHECK=2
binaryen pass, thus minimizing the impact of reducing the stack size.

See #14177
@sbc100 sbc100 merged commit 0806384 into main Nov 10, 2022
@sbc100 sbc100 deleted the stack_first branch November 10, 2022 20:52
@juj
Copy link
Collaborator

juj commented Nov 12, 2022

When a function needs to push its stack frame to accommodate for new items, does LLVM not do a stack overflow check right there and then in debug/other suitable builds? Needing to rely on the stack being in front of the memory address to get stack overflow to work seems silly and brittle, since it complicates build modes, and the same strategy can't extend to pthreads.

@sbc100
Copy link
Collaborator Author

sbc100 commented Nov 13, 2022

When a function needs to push its stack frame to accommodate for new items, does LLVM not do a stack overflow check right there and then in debug/other suitable builds? Needing to rely on the stack being in front of the memory address to get stack overflow to work seems silly and brittle, since it complicates build modes, and the same strategy can't extend to pthreads.

No, by default in debug builds you don't get precise stack checking like that. In emscripten we have -sSTACK_OVERFLOW_CHECK=2 which does precise overflow checking, but that requires a binaryen pass.

I had initially proposed that we make -sSTACK_OVERFLOW_CHECK=2 the default in debug builds (currently its the cheaper -sSTACK_OVERFLOW_CHECK=1 that we get in debug builds). My PR to make that happen is here: #18040. Turns out it doesn't cost a lot a runtime, but forcing all debug biulds to run a binaryen pass is undesirable, because that can slow down time significantly. We are trying hard to eliminate the need to binaryen passes in debug builds for this reason.

There may also be other llvm options to enable stack checking, but I think that would require specific command line flags for all compilation units (not just a link flag), and I don't think we use them all in emscripten today. We only use the non-llvm STACK_OVERFLOW_CHECK options currently.

@juj
Copy link
Collaborator

juj commented Nov 14, 2022

Thanks - yeah, I was thinking LLVM would be able to do this out of the box, seems odd if it doesn't.

Though if/since it doesn't, then relying on the Wasm VM trap behavior does make sense.

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.

5 participants