Skip to main content

Module load

Module load 

Source
Expand description

A load of something the block has already read or written is that value, not a second read.

Design: spec/optimizer/16-gvn-and-pre.md section 16.2, which calls redundant load elimination the real prize of that document. Value numbering over arithmetic is worth less than people expect on C, because the front end does not generate the same expression twice and the programmer does not write it twice. Loads are different. p->x three times in a function is three reads of memory, and if nothing wrote through an aliasing pointer in between, two of them are work the program does not have to do.

§The restricted version, and why this is it

Section 16.2 asks for two of these. The one at -O2 walks memory SSA back from each load to its clobbering definition, translates an address backwards through a block’s parameters, and sees through a memcpy. The one here is the other: same block only, no phi translation, no memcpy, and no memory SSA at all. That is the version the section says should be at -O1, and it says why: it catches the repeated p->x in one basic block, which is the majority of the opportunities, for a fraction of the machinery.

The two are not alternatives and this is not a stand-in for the other one. What it is is the part that can be written without an alias oracle, and the part whose cost is one walk over each block.

§What it knows

One table per block, from an address to the value that address holds, thrown away at the end of the block because what reaches a block from its predecessors is the question this version does not ask. A store writes what it stored. A load that had to happen writes what it read. Either way the next load of that address is that value.

An address is one SSA value, compared by identity. Two pointers that are the same address by arithmetic and not by name are two addresses here, which costs opportunities and no correctness: an address computed twice out of the same parts is tracked twice and neither copy is forwarded to the other. What would give the two one name is value numbering over the arithmetic, which is the other half of document 16 and is not built. It costs more than it sounds like it should. a[i] = v; total += a[i]; written in C is a store and a load whose addresses are two separate runs of the same multiply and add, because the front end emits the subscript twice, so the shape this pass is most obviously for is one it cannot see until that lands.

§What throws the table away

Anything that could write anywhere. There is no alias analysis in this pass, so a store to one address is treated as a possible write to every address, and the table is emptied before the store records what it just wrote. A call, an atomic, a fence and a memcpy empty it and record nothing. That is Opcode::touches_memory, which is the conservative predicate, so an opcode added to the IR later throws the table away rather than being quietly assumed harmless.

This costs less than it sounds like. A store immediately followed by a read of what was stored still works, because the store empties the table and then puts back the one entry the load is about to ask for. What the barrier really costs is the second address: *p = v; total += *q; with two locals is refused even where the two cannot be the same object, and telling them apart is the alias oracle’s answer rather than this pass’s.

A volatile access empties the table and records nothing either way. Whether a volatile store could be forwarded from is an argument about what volatile promises, and this pass does not need to have it.

§The width, which is where the miscompilation would be

Section 16.6 names it as the single most likely wrong answer in that document: a load forwarded from a store of a different size. Section 09.5 has the three-way distinction, which is that a store covering the load exactly is the value, one covering it partially needs an extract, and one not covering it at all means the walk should continue.

This pass only ever takes the first of the three. The address has to be the same SSA value and the type has to be equal, which is the same width and the same reading of the bits, and anything else is left alone and counted. Two-way is what somebody writes first and it is right most of the time, which is what makes it worth being explicit that this is not that.

§What it leaves behind

The load goes, rather than staying and having its result forwarded. crate::dce would not remove it: has_effects is true of every load, because a pass that removed one would need to know the address is dereferenced anyway, and this is the pass that knows it. The load being removed is safe for a reason nothing else in the pipeline has: something already read or wrote that exact address in this block, so the address is one the program dereferences whatever happens next.

The store stays. Removing a store that a later store covers is dead store elimination, which is document 17 and a different pass.

Structs§

LoadForward
The pass.