This patch implements replacement of certain conditional stores with
unconditional ones (subject to constraints).
A triangle shaped part of the CFG like:
header: ... br %cond, label %if.then, label %if.end if.then: store %s.new, %addr br label %if.end if.end: ...
would become
header: ... br %cond, label %if.then, label %if.else if.then: br %if.end if.else: %s.old = load %addr br label %if.end if.end: %s = phi [%s.new, if.then], [%s.old, if.else] store %s, %addr
This transformation is correct as long as the store:
a) is not volatile
b) does not introduce an invalid memory access in the new location
c) does not introduce a data race in the new location
For a) volatile stores are simply disqualified from the transformation.
To satisfy b) we can check that on all paths leading up to the end of the
header block
- the program already contains a write (or, for local objects only, a read) to precisely the same memory location, and
- for non-local objects only, following that write, there is no instruction that could possibly make subsequent writes invalid (local objects are considered always writable).
To satisfy c), for local objects only, we can check that the address of the
object does not escape the function.
For non-local objects or for escaping local objects we can check that on all
paths leading up to the end of the header block
- the program already contains a write to precisely the same memory location, and
- following that write, there is no instruction that could possibly be the tail edge of a "synchronizes-with" relation
If the candidate store is to a local variable, we first traverse the users of
the alloca instruction, noting whether the address escapes and whether a load
or a store to the same address dominates the candidate store (domination is a
stronger constraint than the above "on all paths" one).
Failing that, or for stores to non-local objects, we then traverse the MemorySSA
graph, starting from the MemoryDef that corresponds to the candidate
store. During that travesal:
- if we reach the initial liveOnEntry, nothing is guaranteed and we fail
- if we reach a call to an unknown function, a volatile memory access, or an atomic memory access we bail
- if we reach a simple store to the same memory location, we stop traversing upwards from this MemoryDef only
- otherwise we contionue the traverse to the incoming MemoryDef
If we didn't bail anywhere in the above traversal, the transformation is
considered correct.
clang-format: please reformat the code