Invariant.group's are not sufficiently handled by LICM. Specifically,
if a given invariant.group loaded pointer is not overwritten between
the start of a loop, and its use in the load, it can be hoisted.
The invariant.group (on an already invariant pointer operand) ensures
the result is the same. If it is not overwritten between the start
of the loop and the load, it is therefore legal to hoist.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Unit Tests
Time | Test | |
---|---|---|
60,060 ms | x64 debian > libFuzzer.libFuzzer::value-profile-load.test |
Event Timeline
I think this is correct, but I'm not very familiar with invariant.group nuances.
llvm/lib/Transforms/Scalar/LICM.cpp | ||
---|---|---|
1206 | Can we move this logic into pointerInvalidatedByLoop? It looks like the only difference for the invariant.group case is the last check for the clobber being the loop header MemoryPhi, right? | |
llvm/test/Transforms/LICM/invariant.group.ll | ||
4 | Second RUN line is unnecessary. | |
26 | i32* -> ptr |
LGTM. Possibly there is some way to nicely integrate this directly into the MemorySSA clobber walker (which already has invariant.group support, but doesn't handle this case), but I think this is fine for now.
llvm/lib/Transforms/Scalar/LICM.cpp | ||
---|---|---|
1180 | Doesn't seem like the variable is needed anymore. If you want to keep it, use auto *. |
I have a question what behavior the compiler raises if !invariant.group 's semantic is violated? For this case:
define i32 @foo(ptr nocapture %p, ptr nocapture %q) { entry: %0 = load i32, ptr %p, align 4, !invariant.load !0 store i8 2, ptr %p, align 1 %1 = load i32, ptr %p, align 4, !invariant.load !0 %add = add nsw i32 %1, %0 ret i32 %add } !0 = !{} }
if runs GVN on it, this program is not changed.
But in this patch, we just check if the load's clobber def is a memoryphi and locates in loopheader, for this case
define void @test(i64 %v, ptr %arg, ptr %arg1) { bb2: ; preds = %bb br label %bb5 bb5: ; preds = %bb5, %bb2 %tmp6 = phi i64 [ 0, %bb2 ], [ %tmp10, %bb5 ] %tmp3 = load i64, ptr %arg1, align 4, !invariant.group !0 store i64 %tmp6, ptr %arg1, align 8 %tmp10 = add nuw nsw i64 %tmp6, %tmp3 %tmp11 = icmp eq i64 %tmp10, 200 br i1 %tmp11, label %bb12, label %bb5 bb12: ; preds = %bb5, %bb ret void }
It hoists the load instruction even if there is a store that writes a new value to %arg1 pointer operand.
And I found that for
define i32 @foo(ptr nocapture %p, ptr nocapture %q) { entry: %0 = load i32, ptr %p, align 4, !invariant.load !3 %conv = trunc i32 %0 to i8 store i8 %conv, ptr %q, align 1 %1 = load i32, ptr %p, align 4, !invariant.load !3 %add = add nsw i32 %1, 1 ret i32 %add } !3 = !{}
GVN removes %1 = load i32, ptr %p, align 4, !invariant.load !3. The reason is although %q mayalias %p, they are not the same pointer operand. According to the docs, the value under %p will not be changed. So I think In this patch we can just check if clobber defs' pointer operand is the same as the load instruction to decide if we can hoist it.
I'm personally of the opinion that building a special case for an illegal use of the invariant metadata (e.g. where it would guarantee the memory is not changed), would produce more fragile code -- since it'll be harder for someone who accidentally creates such illegal metadata to find the actual source of the bug.
Nit: Style, also below.