This is an archive of the discontinued LLVM Phabricator instance.

[LICM] Ensure LICM can hoist invariant.group
ClosedPublic

Authored by wsmoses on Feb 14 2023, 3:37 PM.

Details

Summary

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.

Diff Detail

Event Timeline

wsmoses created this revision.Feb 14 2023, 3:37 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 14 2023, 3:37 PM
wsmoses requested review of this revision.Feb 14 2023, 3:37 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 14 2023, 3:37 PM

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

wsmoses updated this revision to Diff 497749.Feb 15 2023, 11:06 AM

Address feedback

wsmoses updated this revision to Diff 497750.Feb 15 2023, 11:07 AM

Run linter

wsmoses updated this revision to Diff 497751.Feb 15 2023, 11:08 AM

Actually run linter

wsmoses marked 3 inline comments as done.Feb 15 2023, 11:10 AM

Looks reasonable to me

llvm/lib/Transforms/Scalar/LICM.cpp
167

Nit: Style, also below.

wsmoses updated this revision to Diff 497766.Feb 15 2023, 12:01 PM

Fix style

wsmoses marked an inline comment as done.Feb 15 2023, 12:53 PM
nikic accepted this revision.Feb 16 2023, 12:00 AM

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 *.

This revision is now accepted and ready to land.Feb 16 2023, 12:00 AM
StephenFan added a comment.EditedFeb 16 2023, 7:37 AM

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.

This revision was landed with ongoing or failed builds.Feb 26 2023, 9:41 AM
This revision was automatically updated to reflect the committed changes.

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.