Skip to main content

Module finish

Module finish 

Source
Expand description

The prologue, the epilogue, and the moves the allocator asked for.

Design: spec/10-backend.md sections 10.4 and 10.7.

crate::frame works out what a function’s stack looks like and writes nothing. This is what writes it. Three things are still missing from a function the allocator has finished with, and all three of them are instructions no lowering rule chose:

  the prologue     takes the frame the layout worked out, and puts away the registers a call
                   leaves alone that this function writes anyway
  the moves        every spill, every reload and every copy the allocator handed back as an
                   edit, in the place it said and in the order it said
  the epilogue     gives the frame back and puts the registers back, at the end of every block
                   the function returns from

There is a fourth thing and it is not an instruction but a number. The lowering wrote an instruction for every alloca that computes the address of the memory it asked for, and could not write how far into the frame that memory is, because when it ran there was no frame. So the displacement of each of those is filled in here, out of the same Frame everything else here reads, and off the same stack pointer every other offset in it is from.

The loads that read the arguments the caller passed on the stack are waiting on the same number and on one more. Those bytes are the caller’s rather than this function’s, and a frame that had to force its own alignment cannot say how far away the caller’s stack pointer was, so it reaches back through the frame pointer instead. Which register a load reads through is therefore settled here too, and it is the only base register in a finished function that was not settled by whoever wrote the instruction.

After this the function is one an encoder can read: every register is physical, every offset into the frame is a constant, and the stack pointer is where the convention says it should be at every instruction that could look.

§Why the moves go in first

Every offset the frame reports is from the stack pointer as it stands in the body of the function. A spill written before the prologue exists would be written in front of the instruction it belongs to and behind nothing, which is where the prologue then goes, so the prologue ends up in front of it and the offsets stay true. Writing them the other way round would put the first reload above the instruction that takes the frame, and it would read from an address that is one frame out.

§Where a return is

A block that goes nowhere is a block the function leaves from. Mostly that is a return, and the other kind is a block ending in unreachable, which is a point the front end says control does not arrive at and which the lowering writes no instruction for. Both want the same thing here. A return wants the epilogue because that is what a return is once the frame is known, and an unreachable block wants it because the alternative is a function whose last instruction falls into whatever the assembler put after it, which is worse than an epilogue nothing runs. So the epilogue goes at the end of every block with an empty successor list, and there may be several, because nothing here insists a function has one exit.

§What is target-specific here

The names, and only the names. Which instruction pushes a register and which one moves the stack pointer is rucc_target::FrameInsts, which the target says and this reads, so what is written below is the shape of a prologue rather than any particular machine’s. That is spec/10-backend.md section 10.8 as it applies to the one pass that would otherwise be full of x64. by hand.

Functions§

finish
Writes the moves, the prologue and the epilogue into a function the allocator has finished with.