Expand description
Taking out a reload that reads back the slot the instruction in front of it has just filled.
Design: spec/optimizer/37-machine-level-optimization.md sections 37.4 and 37.6, the group of
passes that run after allocation and clean up what the allocator could not.
The allocator decides one value at a time. It writes a value out when the range it was given a register for ends, and it reads a value back in front of the instruction that wants it, and neither decision looks at the other. So a value written by one instruction and wanted by the next comes out as a store and then the load of the same slot on the very next line:
movq %r10, 16(%rsp)
movq 16(%rsp), %r10The load reads a word the store has just written, into the register the store read it out of, and nothing stands between the two, so the register already holds what the load would put in it. It is a memory access that cannot change anything, on the two instructions of the pair that are the expensive one. That is what this takes out.
§Why it is not a rule over the instructions
A store followed by a load of the same address is not on its own a dead load. The same pair of
instructions is what a write to a local variable and a read of it back look like, and when that
variable is volatile the read is one the program insisted on and the standard says happens.
Machine IR does not carry that word, and by the time the pass runs it could not: a volatile
access and an ordinary one are the same instruction with the same operands.
So this does not look for the pattern. crate::finish records which instruction each of the
allocator’s moves became, and this pass only ever takes out one of those. A spill slot belongs
to the allocator, nothing else reads it, and no part of the program said anything about it,
which is what makes removing a read of one safe when removing a read of a variable is not.
§Why after the whole allocator rather than inside it
The two edits are decided in different places for different reasons, so neither of the two
decisions is wrong on its own and there is no one place inside the allocator that sees the
pair. Adjacency is also not a property either decision has: whether anything ends up between
them is settled by crate::finish writing every edit into the function, which is after the
allocator has finished. The pair is visible once, here, and nowhere earlier.
§What it does not do
Only the pair that is next to itself. A reload with an instruction between it and the spill is left alone even when that instruction writes neither the register nor the slot, because answering that needs liveness over the written function rather than a look at the line above. A reload into a different register than the one spilled is left alone too, though a copy would do instead of a load. Both belong to the post-allocation copy propagation of section 37.4, which is a pass rather than a peephole, and this is deliberately the part of it that costs one walk over the function.
Functions§
- dead
- Takes out every reload of a slot the instruction in front of it spilled, and says how many.