Skip to content

RFC: Revise or at least rename the fixed_stack_segment and rust_stack annotations #8822

@nikomatsakis

Description

@nikomatsakis

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, or large -- some arbitrarily chosen constants
  • fixed -- 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions