-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Description
Currently, fns annotated with fixed_stack_segment
(hereafter abbreviated as FSS) request a "large" stack (2MB, I think) from the system. They are supplied with a "larger" stack (3MB?). This overallocation typically ensures that if one FSS fn calls another, the second does not require a stack switch -- at least this is true if those calls occur quickly after the stack, without many intervening frames.
In addition, whenever an extern "C" fn
is called, the cstack
lint checks that the caller has a FSS annotation OR that the callee is annotated "rust_stack", meaning that it operates in the red-zone.
I propose a few revisions:
- A new
#[stack]
annotation to replace the existing two - Small changes to the FSS allocation protocol in morestack
- Use of guard pages (if we don't already)
New annotation: #[stack]
I propose a revised annotation #[stack(size)]
where size
can be:
- A specific number of extra bytes.
none
-- the default, no extra bytes, useful for annotating extern fns (see below)small
,medium
, orlarge
-- some arbitrarily chosen constantsfixed
-- requests a FSS (see below for more details)
This annotations requests extra stack space on top of the usual amount that is required for the fns allocas. It can be used to alleviate specific performance problems with stack-switching. For example, if it happens in profiles that fn A calls fn B many times and fn B often has to switch stacks, that can be expensive: annotating fn A with #[stack(medium)]
or some such may address the issue.
Extern fn declarations can also be labelled with explicit #[stack]
annotations. The cstack lint will ensure that amount of stack is available. The default is fixed
. This mechanism replaces the current rust_stack
annotation. Note that a stack(none)
annotation would be used to indicate an extern fn that operates in the red zone.
FSS allocation mechanism
The current "overallocation" scheme for FSS is risky. If a C callback invokes a Rust fn, this scheme could lead to multiple FSS stacks being allocated. I think in general once we switch to a FSS, we should not switch stacks ever again.
To this end, we can signal a FSS by setting the stack limit to 0 (for downward growing stacks). FSS functions will not check for 2MB of space but rather check for a limit of 0, indicating a FSS. They will call into morestack requesting MAX_UINT
bytes. Morestack will allocate a fixed segment and set stack limit to 0 and then return.
Guard pages
To prevent actual stack overrun, a guard page is used to catch programs that recurse too far (as is standard). Note that this also requires that we follow the typical protocol of "touching" the stack every 4K for functions with huge stack frames: hopefully LLVM has an option for this? It's not an unusual requirement afaik. (We probably want a guard page in all scenarios, not just FSS)
LLVM inlining
In an ideal world, we would modify the LLVM inliner to avoid inlining functions with a larger stack annotation into functions with a smaller one. This prevents "stack creep" and avoids the need for the manual #[inline(never)]
annotations we currently have.
Parting thoughts
I am unsure about the proper interaction between "fixed" stack segments and other stack segment requests. Above I outlined a protocol where switching to a fixed segment never switches again. I am not sure how well this interacts with the idea that tasks should default to a large stack and so forth. This might require some tweaking.
Thoughts?
Paging @brson and @pcwalton for sure, though I'm sure others will have an opinion.