Skip to main content

Module frame

Module frame 

Source
Expand description

The frame: what a function’s stack looks like while it runs.

Design: spec/10-backend.md section 10.7.

This is worked out after register allocation and not before, because the largest area in most frames is the spill slots and nothing knows how many of those there are until the allocator has finished running out of registers. It is worked out from the rewritten function rather than from the assignment alone, because the rewrite is what decides which scratch registers a reload uses, and a scratch register a call preserves is one the prologue has to save.

§What is in one

Section 10.7 lists the areas and this is the order they are in, from the stack pointer upward, which is the order of increasing address on every machine here.

  incoming stack arguments      the caller wrote these and they are above everything
  return address                the call instruction pushed it, on a machine that does
  saved frame pointer           when the function keeps one
  saved general purpose regs    pushed, one word each
  saved vector registers        stored rather than pushed, since no machine here pushes one
  stack protector canary        when the function has one, above everything a local reaches
  locals                        what an alloca becomes, widest alignment first
  spill slots                   one for every value the allocator ran out of registers for
  outgoing argument area        at the bottom, because a call reads its stack arguments from
                                the stack pointer upward

Every offset reported here is from the stack pointer as it stands in the body of the function, which is after the prologue and before the epilogue. That is the one base register always available. A frame pointer is a second way to reach the same bytes and the prologue is what knows the distance between the two, so nothing here reports an offset from it. There are two exceptions and Frame::incoming is one of them, because the bytes it reports are the caller’s rather than this function’s, which is the one part of the picture a realigned frame loses sight of. It says which register it counted from. The other is a frame that grows, which is the next section and where the stack pointer stops being a base register at all.

§Where the alignment comes from

A call has to leave the stack pointer on a multiple of the convention’s alignment, so a function’s own frame is what puts it back: the call that reached this function pushed a return address and left the stack pointer one word off, and the prologue’s pushes either fix that or make it worse depending on how many there are. The size the prologue subtracts is therefore not the size of the areas. It is whatever brings the stack pointer back to a multiple of the alignment given the pushes in front of it, which is the arithmetic in Frame::of.

§The red zone

A leaf function may use the bytes below the stack pointer without moving it, which is what red_zone on a convention says and what makes a small leaf function’s prologue and epilogue empty. Then the offsets are negative, which is why they are signed, and the areas are in the same order as ever, below the line rather than above it. Anything that calls, or is too big for the zone, or wants more alignment than the stack pointer has for free, moves the stack pointer.

§Realignment

A local wanting more alignment than a call leaves the stack pointer with cannot be placed by arithmetic, because nothing in the frame knows what the caller’s stack pointer was a multiple of. The prologue has to force it, and forcing it destroys the only record of where the caller’s stack was, so a realigned frame needs a frame pointer and the distance from the body’s stack pointer to the incoming arguments stops being a constant. Frame::realign is where that is reported and it is why Frame::incoming answers from the frame pointer in such a frame and from the stack pointer in every other one.

§Growing

A variable length array is bytes the function takes off the stack pointer where the declaration stands, so in a function that has one the stack pointer is in a different place in the middle of the body than it was at the top of it. Every other offset in the frame was a distance from the stack pointer, and a distance from a register that moves is not a distance, so in a frame like this they are all distances from the frame pointer instead. That is what Layout::grows says and Frame::grows reports, and it is why such a frame keeps a frame pointer whatever the flags asked for, the same way a realigned one does and for a version of the same reason.

Three other things follow from it. The red zone is gone, because the zone is the bytes below the stack pointer and the first thing an array like this does is move the stack pointer down over them. The frame asks for the convention’s alignment even when nothing in it wanted that much, so that the stack pointer is on a multiple of it when the body starts and stays on one as each array rounds its own size up. And the bytes the array hands out start above the outgoing argument area rather than at the stack pointer, because that area stays at the bottom of the frame wherever the bottom has moved to, which is what Frame::below is for.

Realigning and growing together is the one combination that is not here. After the prologue has forced an alignment the distance from the frame pointer to the body’s stack pointer is already not a constant, so there is no register left for the rest of the frame to be counted from, and what fixes that is a second pointer held for the purpose. The lowering refuses that pair rather than this guessing at it.

Structs§

Frame
What a function’s stack looks like while it runs.
Incoming
Where the arguments the caller passed on the stack are, and which register reaches them.
Layout
Everything about a function’s frame that does not come out of its allocation.
Local
A piece of memory the function needs for its own use, which is what an alloca becomes.
Save
One register the prologue puts away in the frame, and where in the frame it goes.

Functions§

widths
How many bytes each of an allocation’s spill slots takes on the stack.