-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Reduce default stack size to 512KB. #10019
Reduce default stack size to 512KB. #10019
Conversation
bf7aa73
to
9e109ab
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I agree with this, good idea. However, that we have 2 tests in the test suite that fail on it, and required fixing, makes me worried it will affect users. Curious to hear other's thoughts on the risk level here.
This should be mentioned in the Changelog.
fwiw I didn't have any trouble with ogv.js with my modules built with this change ( With my modules built at |
I agree, and in addition, I noticed that on the wasm backend assertions don't turn on full stack checks currently. I'm working to fix that now. With that at least with assertions the errors should be very clear. |
(Crazy thought -- is it possible to move the stack to before the static data segment, so attempts to read or write beyond the end of the stack trigger a trap on out of range memory access wrapping around from 0?) |
Funny you should say that. We have a lld option to enable just that and I got a request at the WebAssembly meetup last night to make it the default. I'm think I might make it the default even if emscripten chooses to say with stack second. IIRC the rational that is can actually make the binary smaller, since the LEB encoding of the addresses of you static data can often fix in a byte or two rather than three. Strange optimization I admit .. |
In emscripten perhaps we'd use that option when not optimizing for size? |
In normal mode we call a JS import, but we can't import from JS in standalone mode. Instead, just trap in that case with an unreachable. (The error reporting is not as good in this case, but at least it catches all errors and halts, and the emitted wasm is valid for standalone mode.) Helps emscripten-core/emscripten#10019
Added Changelog entry.
Yeah, in optimized builds without the stack guards in place, things will certainly crash randomly. I don't think that is an issue, since developers have a habit of doing a debug build to troubleshoot random errors. Once Wasm backend has the check in place, perhaps this could land? |
@sbc100 what's the default stack size in clang for wasm? |
looks like we currently default to just a single wasm page (64k): https://github.com/llvm/llvm-project/blob/a2cd4600ec6710f3218f071128e2a81edd23a2b2/lld/wasm/Driver.cpp#L357 |
Have there been complaints? Maybe we should have the same default in both places. But 64k does sound low on the other hand. |
I think most other wasm-ld users (i.e. wasi-sdk) are still only building fairly small projects, but no, no complaints yet. We did get requests to make stack-first the default though, so that stack overflow would trap. So that might be sign that people are hitting it. |
Btw, the current |
The cookie check may also fail to register some overflows; if a function allocates a too-large buffer on the stack and then writes into only part of it, it may not overwrite the cookie value even though it overwrites part of static data. (This seems to be what is happening with the module I found to fail with a 4 KiB stack, as it reads data into a |
… not need to be as big as in native code, because control flow and regular local variables are not part of this stack.
37bd6df
to
b013525
Compare
I would love to reduce the default stack all the way to 64KB here as well.. |
I think maybe we can do this change, or even to 64K, after (1) the upstream backend changes to put the stack first, at least when not optimizing for size, so there are no surprise errors, and (2) we remove fastcomp entirely - as otherwise we can't reduce the default stack size there without risk, as we don't have the stack first there. And in an assertions build we should check for memory traps and add extra logging if they seem like they could be stack overflows - that is, we can look at the position of the stack after such a trap, and if the pointer is in a suspicious place, warn. I think with all those we should be fairly safe? |
@sbc100's comment above indicated that stack-first actually makes the binary smaller. So why wouldn't we just use the same layout all the time? |
@dschuff I think it's more compact with the stack second. When it's first,
then the address of
then The key thing is that stack addresses are not hardcoded throughout the binary, but static global addresses are. |
Having stack seconds seems better to me as well, a lot of global references can occur in builds, so having them in low addresses is good. Also I don't think the location of the stack is a good measure of safety, since there are still pthreads to consider about. The best would be to have good stack checking codegen emitted for stack bumps, i.e. when stack is pushed to set up space for function local variables, the bump size is verified. That would guard against out of stack space errors, and along with a cookie check (perhaps change to a configurable sized guard area check?) that should help resolve all types of stack errors. |
Closing this to not leave unlanded PRs lingering around. |
@juj The default TOTAL_STACK is still 5MB in settings.js. Curious that why the change not merged? |
Here is a more recent (and still open) PR attempting to reduce TOTAL_SIZE, but currently stalled: #14177 |
Progress indeed got stalled. Commented on 14177 above. |
Hi, @juj, I have some questions that could you shed some light on:
Here
I always wondering that where the passed arguments were put, as I find nothing in the STAK area in linear memory of wasm. (I use C/C++ DevTools Support extension for debugging in Browser, and view memory with
I understand address taken variables. But as for Thanks very much. |
That's right, in Wasm, there are two stacks. There is the internal Wasm VM managed control flow stack, which is completely hidden from Wasm user code to observe and examine - there is no way to see or manipulate any data in that stack, it is completely hidden for browser security reasons. That stack would contain function call frames (the EBP registers). This stack that is Emscripten/LLVM established is only for "fat local data". If you have a general
The same story with passed arguments: they are part of the hidden Wasm VM stack, and not the LLVM data stack.
Anything that is accessed via addresses or array indexing will need to be part of the stack. The Wasm locals cannot be array indexed. If a local variable does not have its address taken and is not indexed via an array, it will become a Wasm code local var. If the variable's address is taken, or it is indexed via an array subscript, it must go on the LLVM data stack.
All of this information is internally implemented in a Wasm VM, and not exposed to user code to observe. The way it is implemented varies per each browser, so you may want to dig in to the browser source code to figure that out. Emscripten/LLVM data stack position is tracked using a |
Thanks for the detailed replies, @juj. I got following good points and that answers part of my confusions.
For Wasm function calling model, I think I have to dive into browser source (probably v8/src/wasm) to find out. |
Reduce default main thread and pthread stack sizes to 512KB. This does not need to be as big as in native code, because control flow is not part of this stack.
This is something I have been meaning to propose for ages. E.g. In Visual Studio native ARM, x86 and x64 built code, default stack size is 1MB.
WebAssembly/JS is very special compared to native stack sizes in that neither execution control flow and regular local variables count into this size, but control flow is guarded inside browser, and regular locals are handled by local variables in JS code or in wasm. Only variables that have their addresses taken, or large structs or arrays utilize this limit.
Because of that specialty, I have never seen applications to need much of a stack at all. The largest usage I ever see come from applications that do something like temp
char str[4096];
s on their stack for string manipulation and similar. Even 512KB stack size here seems much larger than is typically needed, and on the safe side. Something like 64KB is probably of the order that applications commonly use.And we have stack size checks in place, so when applications really need more, they should be able to catch any errors.