[PATCH] Clarify wording in the LangRef around !invariant.load
Sanjoy Das
sanjoy at playingwithpointers.com
Mon Nov 24 14:35:14 PST 2014
Just to be clear: I have no problem with this change. However, I
think the current semantics of invariant loads is counter-intuitive,
and should be clarified sometime in the future.
> This is definitely valid:
>
> int *p = <known dereferenceable>
> m1 = load(p) !invariant
> store(q)
> m2 = load(p)
> if (something)
> foo(m1)
> else
> bar(m2)
I claim that this IR is only conditionally well-defined -- the
semantics say that "once the location is known dereferenceable along a
particular execution path that it's value is henceforth unchanging".
If p == q, and the value being stored by store(q) is not the same as
what p originally had, the program is ill-defined. But it was also
ill-defined before in that case, so the transform itself is valid.
> You would be forced to do this, which I think would prevent reordering store(q) and m1 = load(p):
>
> int *p = <known dereferenceable>
> store(q)
> if (something)
> assume_invariant(p)
> m1 = load(p) ;; ***
> foo(m1)
> else
> m2 = load(p)
> bar(m2)
My argument is that the above is the only intuitive interpretation of an
invariant load (if the *** load was marked invariant). With the
current semantics of invariant loads, the following transforms (which
I believe are all correct and intuitive) make a well-defined program
undefined:
%a = < known dereferenceable >
store 0, %a
store 1, %a
if ($false-at-runtime) {
call undef(%a) ;; well-defined, because it never executes
}
suitable choice for undef, and inlining ==>
%a = < known dereferenceable >
store 0, %a
store 1, %a
if ($false-at-runtime) {
%b = load %a !invariant
use(%b)
}
to ==>
%a = < known dereferenceable >
store 0, %a
%b = load %a !invariant
store 1, %a
if ($false-at-runtime) {
use(%b) ;; this is still the original use undef(%b)
;; which initially was okay because it would never
;; execute
}
to ==>
%a = < known dereferenceable >
unreachable ;; modifying invariant location, "cannot happen"
;; since the original value at %a is either != 0
;; or != 1
As far as I can tell, there are three options for invariant_load:
1. we treat invariant_loads as side-effecting operations. this
prevents hoisting through control flow.
2. we accept this weird corner case of semantics not just following
from what was actually executed, and teach the rest of the
pipeline about it (so, for example, there are some values undef
cannot be replaced by, and the first transform is declared
illegal).
3. define a mutable location that has invariant loads in a way that
the last transform is not sound (i.e. locations that have
invariant loads can be modified), but I cannot think of a way to
do that in a sound way.
The other option is to introduce a notion of an invariant attribute
that we can put on function arguments and return values (just like
nonnull). This lets us mark incoming arguments as invariant
locations, and "make_invariant" is a just another function that has
its return value tagged with the "invariant" attribute. This can then
be used exactly as you and Philip have suggested on this and other
threads. Once that is in place, we can make the definition of an
invariant_load weaker, sound and just a convenient notation for
int *p = <known dereferenceable>
store(q)
if (something)
p1 = make_invariant(p)
m1 = load(p1) ;; was m1 = load-invariant p
foo(m1)
else
m2 = load(p)
bar(m2)
-- Sanjoy
More information about the llvm-commits
mailing list