Skip to main content

indirect

Function indirect 

Source
pub fn indirect(
    func: &mut Func,
    branch: &BranchInsts,
    frame: &FrameInsts,
    names: &mut Interner,
) -> usize
Expand description

Takes the values off every edge out of a computed goto, and gives back how many blocks it made to hold them.

Run after lowering and before critical, which then sees edges with nothing on them and leaves them alone. Running it twice is running it once, for the reason the splitting above is: the blocks it adds end in a jump rather than in a branch through a register.

§What is wrong with the edge it takes the values off

Every other edge in the function is out of a block whose last instruction the layout writes, so an edge that is the only way out of its block can put its moves at the end of that block and they land in front of the jump. A block that leaves through a register already ends in the jump when the allocator runs, because where it goes is a value and a value is something selection reads rather than something the layout knows. Moves at the end of that block would be written after the jump, where nothing runs them, and moves in front of it would be written across the register the jump reads, which the allocator believes is dead from the jump onwards and is free to hand to one of the moves.

So the moves go somewhere else. Each label an indirect branch reaches gets a block in front of it that carries the values, the branch goes to that block with nothing on the edge, and the address the &&label produces is the address of that block rather than of the label’s own. The new block is arrived at one way and leaves one way, so its own edge has both of the places the splitting above talks about and the allocator is content.

§One label, one address, and two branches that disagree

A label has one address, so two computed gotos that reach it both arrive at whatever block that address names, and the values they carry are not the same values. One block in front of the label cannot move two different sets of registers.

So they are made to agree first. Each parameter of the label gets a register of its own, every branch writes that register in front of its jump, and the block in front of the label carries those registers and nothing else. That is what gcc does about the same problem, which it calls coalescing across an abnormal edge, done here rather than while the values are still the optimizer’s.

Writing them in front of the jump is safe, which is not obvious, since a branch that goes five ways writes the registers of one of those ways on the path to all five. What makes it safe is that nothing reads those registers except the block in front of the label, and the only way to reach that block is an edge out of a branch, which writes them on the way. So a value written here and not used is a value overwritten before anything looks, whichever way the jump went.

§Panics

Panics on a class of register the machine named no move for, which is a function carrying a value of a kind the target never said how to copy, and on a branch that has lost the terminator it was found by, which nothing between the finding and the use of it can do. Both are a target description or a function that was built wrongly, and both are worth finding here rather than as a value that arrives somewhere it was never written.