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.

§One register for one value, and not one for every place it is given to

That safety is also what makes the cost of it worth watching. A branch writes the registers of every label it can reach, so a register for every parameter of every label is a whole table’s worth of moves in front of every jump in the function, and a dispatch table is a branch that reaches hundreds of labels. An interpreter hands each of them whatever its loop had in hand at the jump, which is the same few values over and over, so a register for each place one of them lands means those values written a hundred times over before every instruction the interpreter runs. That is not a small constant. It is what makes an interpreter built this way ten times slower than the same interpreter built with a switch instead of the computed goto.

So the register belongs to the value rather than to the place. Two parameters are given one register when they are drawn from the same class and every branch in the function gives them the same register, which is exactly when one register can stand for both, and a branch writes each register it has to write once however many labels asked for it. A dispatch table where every label wants the instruction pointer writes the instruction pointer once. A label something else reaches, or a label given something no other label is given, keeps a register of its own, and a branch that gives one label nothing shares nothing with it, since a register that branch never wrote is not one the label can be given.

§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.