Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GVNSink] Fix incorrect codegen with respect to GEPs #85333 #88440

Merged
merged 1 commit into from
Apr 30, 2024

Conversation

hiraditya
Copy link
Collaborator

@hiraditya hiraditya commented Apr 11, 2024

As mentioned in #68882 and https://discourse.llvm.org/t/rfc-replacing-getelementptr-with-ptradd/68699

Gep arithmetic isn't consistent with different types. GVNSink didn't realize this and sank all geps
as long as their operands can be wired via PHIs
in a post-dominator.

Fixes: #85333

Copy link

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Discourse for more information.

@llvmbot
Copy link
Collaborator

llvmbot commented Apr 12, 2024

@llvm/pr-subscribers-llvm-transforms

Author: AdityaK (hiraditya)

Changes

As mentioned in #68882 and https://discourse.llvm.org/t/rfc-replacing-getelementptr-with-ptradd/68699

Gep arithmetic isn't consistent with different types. GVNSink didn't realize this and sank all geps
as long as their operands can be wired via PHIs
in a post-dominator.


Full diff: https://github.com/llvm/llvm-project/pull/88440.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Scalar/GVNSink.cpp (+19-8)
  • (added) llvm/test/Transforms/GVNSink/different-gep-types.ll (+47)
diff --git a/llvm/lib/Transforms/Scalar/GVNSink.cpp b/llvm/lib/Transforms/Scalar/GVNSink.cpp
index d4907326eb0a5a..a64ae46d2a72a3 100644
--- a/llvm/lib/Transforms/Scalar/GVNSink.cpp
+++ b/llvm/lib/Transforms/Scalar/GVNSink.cpp
@@ -719,21 +719,32 @@ GVNSink::analyzeInstructionForSinking(LockstepReverseIterator &LRI,
   // try and continue making progress.
   Instruction *I0 = NewInsts[0];
 
-  // If all instructions that are going to participate don't have the same
-  // number of operands, we can't do any useful PHI analysis for all operands.
-  auto hasDifferentNumOperands = [&I0](Instruction *I) {
-    return I->getNumOperands() != I0->getNumOperands();
+  auto hasDifferentOperands = [&I0](Instruction *I) {
+    // If all instructions that are going to participate don't have the same
+    // number of operands, we can't do any useful PHI analysis for all operands.
+    if (I->getNumOperands() != I0->getNumOperands())
+      return true;
+    // Having different source element types may result in incorrect
+    // pointer arithmetic on geps(github.com/llvm/llvm-project/issues/85333)
+    if (auto *II = dyn_cast<GetElementPtrInst>(I)) {
+      auto I0I = cast<GetElementPtrInst>(I0);
+      return II->getSourceElementType() != I0I->getSourceElementType();
+    }
+    return false;
   };
-  if (any_of(NewInsts, hasDifferentNumOperands))
+
+  if (any_of(NewInsts, hasDifferentOperands))
     return std::nullopt;
 
   for (unsigned OpNum = 0, E = I0->getNumOperands(); OpNum != E; ++OpNum) {
     ModelledPHI PHI(NewInsts, OpNum, ActivePreds);
     if (PHI.areAllIncomingValuesSame())
       continue;
-    if (!canReplaceOperandWithVariable(I0, OpNum))
-      // We can 't create a PHI from this instruction!
-      return std::nullopt;
+    for (auto &Candidate : NewInsts) {
+      if (!canReplaceOperandWithVariable(Candidate, OpNum))
+        // We can 't create a PHI from this instruction!
+        return std::nullopt;
+    }
     if (NeededPHIs.count(PHI))
       continue;
     if (!PHI.areAllIncomingValuesSameType())
diff --git a/llvm/test/Transforms/GVNSink/different-gep-types.ll b/llvm/test/Transforms/GVNSink/different-gep-types.ll
new file mode 100644
index 00000000000000..5016be997a2775
--- /dev/null
+++ b/llvm/test/Transforms/GVNSink/different-gep-types.ll
@@ -0,0 +1,47 @@
+; RUN: opt -passes=gvn-sink -S %s | FileCheck %s
+
+target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
+target triple = "thumbv7em-none-unknown-eabi"
+
+%"struct.std::pair" = type <{ i32, %struct.substruct, [2 x i8] }>
+%struct.substruct = type { i8, i8 }
+%"struct.std::random_access_iterator_tag" = type { i8 }
+
+; CHECK: if.end6
+; CHECK: %incdec.ptr.sink = phi ptr [ %incdec.ptr, %if.then ], [ %incdec.ptr4, %if.then3 ], [ %add.ptr, %if.else5 ]
+; CHECK-NEXT: store ptr %incdec.ptr.sink, ptr %__i, align 4
+
+define linkonce_odr dso_local void @__advance(ptr noundef nonnull align 4 dereferenceable(4) %__i, i32 noundef %__n) local_unnamed_addr {
+entry:
+  %0 = call i1 @llvm.is.constant.i32(i32 %__n)
+  %cmp = icmp eq i32 %__n, 1
+  %or.cond = and i1 %0, %cmp
+  br i1 %or.cond, label %if.then, label %if.else
+
+if.then:                                          ; preds = %entry
+  %1 = load ptr, ptr %__i, align 4
+  %incdec.ptr = getelementptr inbounds i8, ptr %1, i32 8
+  store ptr %incdec.ptr, ptr %__i, align 4
+  br label %if.end6
+
+if.else:                                          ; preds = %entry
+  %2 = call i1 @llvm.is.constant.i32(i32 %__n)
+  %cmp2 = icmp eq i32 %__n, -1
+  %or.cond7 = and i1 %2, %cmp2
+  br i1 %or.cond7, label %if.then3, label %if.else5
+
+if.then3:                                         ; preds = %if.else
+  %3 = load ptr, ptr %__i, align 4
+  %incdec.ptr4 = getelementptr inbounds i8, ptr %3, i32 -8
+  store ptr %incdec.ptr4, ptr %__i, align 4
+  br label %if.end6
+
+if.else5:                                         ; preds = %if.else
+  %4 = load ptr, ptr %__i, align 4
+  %add.ptr = getelementptr inbounds %"struct.std::pair", ptr %4, i32 %__n
+  store ptr %add.ptr, ptr %__i, align 4
+  br label %if.end6
+
+if.end6:                                          ; preds = %if.then3, %if.else5, %if.then
+  ret void
+}

Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this seems fine to me, but I do wonder about the situation arising in the first place. I was under the impression that GEPs had been canonicalized to always use i8 now when the IR is read in (e.g. auto upgrade). Is that logic in one of the simplification passes? Does that imply that such a pass should always run before GVNSink? IIRC, what I'm referring to is related to the ptradd proposal.

@nikic
Copy link
Contributor

nikic commented Apr 20, 2024

This is probably not the right way to fix this: GEPs are not the only instruction type that has non-operand state. Either there is some other place already taking this into account in GVNSink that needs to be adapted for GEPs, or GVNSink should be calling hasSameSpecialState() (or one of that family of functions).

@nikic
Copy link
Contributor

nikic commented Apr 20, 2024

Overall, this seems fine to me, but I do wonder about the situation arising in the first place. I was under the impression that GEPs had been canonicalized to always use i8 now when the IR is read in (e.g. auto upgrade). Is that logic in one of the simplification passes? Does that imply that such a pass should always run before GVNSink? IIRC, what I'm referring to is related to the ptradd proposal.

GVNSink still has to be correct when considered as a standalone pass. Besides, the i8 canonicalization is currently only applied to constant offsets, while GVNSink presumably also operates on non-constants.

Copy link
Contributor

@nikic nikic Apr 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a warning: GVNSink is non-deterministic, which makes it easy for tests written in one environment to fail on some buildbots. I don't know whether it will affect this specific test. This non-determinism (and the fact that it prevents us from writing tests) has been blocking various fixes in this incredibly broken pass.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikic I filed #77852 a while back when we ran into this, but are there other known issues of non-determinism that you can point me to? This sounds like something worth fixing, since we've found GVNHoist/GVNSink save a decent amount of size for embedded targets, and it would be ideal if we could stabilize it enough to enable in the -Oz pipeline by default.

llvm/test/Transforms/GVNSink/different-gep-types.ll Outdated Show resolved Hide resolved
@ilovepi
Copy link
Contributor

ilovepi commented Apr 22, 2024

GVNSink still has to be correct when considered as a standalone pass.

Agreed. That was kind of what I was getting at, though I didn't express it that clearly. I guess my sentiment is "hey, if GVNSink needs the IR to be in a certain state, shouldn't it ensure it's in that state itself (e.g., by running some canonicalization routine)?".

Besides, the i8 canonicalization is currently only applied to constant offsets, while GVNSink presumably also operates on non-constants.

Ah, that is true, I had forgotten we only do that for constant offsets.

@hiraditya
Copy link
Collaborator Author

This is probably not the right way to fix this: GEPs are not the only instruction type that has non-operand state. Either there is some other place already taking this into account in GVNSink that needs to be adapted for GEPs, or GVNSink should be calling hasSameSpecialState() (or one of that family of functions).

i see. this makes it simpler. Thanks!

@hiraditya hiraditya force-pushed the gvnsink branch 2 times, most recently from a73db81 to f472394 Compare April 27, 2024 16:30
@nikic nikic changed the title Fix incorrect codegen with respect to GEPs #85333 [GVNSink] Fix incorrect codegen with respect to GEPs #85333 Apr 29, 2024
@hiraditya hiraditya force-pushed the gvnsink branch 2 times, most recently from bb67efd to c5b3251 Compare April 29, 2024 22:04
Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

llvm/lib/Transforms/Scalar/GVNSink.cpp Outdated Show resolved Hide resolved
As mentioned in llvm#68882 and https://discourse.llvm.org/t/rfc-replacing-getelementptr-with-ptradd/68699

Gep arithmetic isn't consistent with different types. GVNSink didn't realize this and sank all geps
as long as their operands can be wired via PHIs
in a post-dominator.

Fixes: llvm#85333
@hiraditya hiraditya merged commit 1c979ab into llvm:main Apr 30, 2024
3 of 4 checks passed
@hiraditya hiraditya deleted the gvnsink branch April 30, 2024 18:31
@dyung
Copy link
Collaborator

dyung commented Apr 30, 2024

@hiraditya, the test you added seems to be failing on windows bots:

Can you take a look and revert if you need time to investigate?

@hiraditya
Copy link
Collaborator Author

let me revert it first.

hiraditya added a commit that referenced this pull request Apr 30, 2024
hiraditya added a commit that referenced this pull request Apr 30, 2024
…90658)

Reverts #88440

Test failing on Windows:
https://lab.llvm.org/buildbot/#/builders/233/builds/9396
```
Input file: <stdin>
# | Check file: C:\buildbot\as-builder-8\llvm-nvptx-nvidia-win\llvm-project\llvm\test\Transforms\GVNSink\different-gep-types.ll
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |            .
# |            .
# |            .
# |           42:  br label %if.end6 
# |           43:  
# |           44: if.else5: ; preds = %if.else 
# |           45:  br label %if.end6 
# |           46:  
# |           47: if.end6: ; preds = %if.else5, %if.then3, %if.then 
# | next:67'0             X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# | next:67'1                                                        with "IF_THEN" equal to "%if\\.then"
# | next:67'2                                                        with "IF_THEN3" equal to "%if\\.then3"
# | next:67'3                                                        with "IF_ELSE5" equal to "%if\\.else5"
# |           48:  %.sink1 = phi i32 [ -8, %if.then3 ], [ -4, %if.else5 ], [ 8, %if.then ] 
# | next:67'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | next:67'4      ?                                                                        possible intended match
# |           49:  %0 = load ptr, ptr %__i, align 4 
# | next:67'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           50:  %incdec.ptr4 = getelementptr inbounds i8, ptr %0, i32 %.sink1 
# | next:67'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           51:  store ptr %incdec.ptr4, ptr %__i, align 4 
# | next:67'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           52:  ret void 
# | next:67'0     ~~~~~~~~~~
# |           53: } 
# | next:67'0     ~~
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1
```
@nikic
Copy link
Contributor

nikic commented May 1, 2024

This is exactly the test non-determinism issue I've mentioned before. I think at this point we cannot make any fixes to GVNSink until the non-determinism issue is fixed and we can actually write tests for this pass.

@hiraditya
Copy link
Collaborator Author

makes sense. i'll update my investigation in the issue (#77852) created by @ilovepi

hiraditya added a commit that referenced this pull request May 14, 2024
As mentioned in #68882 and
https://discourse.llvm.org/t/rfc-replacing-getelementptr-with-ptradd/68699

Gep arithmetic isn't consistent with different types. GVNSink didn't
realize this and sank all geps as long as their operands can be wired
via PHIs
in a post-dominator.

Fixes: #85333
Reapply: #88440 after fixing the non-determinism issues in #90995
mub-at-arm pushed a commit to mub-at-arm/llvm-project that referenced this pull request May 16, 2024
As mentioned in llvm#68882 and
https://discourse.llvm.org/t/rfc-replacing-getelementptr-with-ptradd/68699

Gep arithmetic isn't consistent with different types. GVNSink didn't
realize this and sank all geps as long as their operands can be wired
via PHIs
in a post-dominator.

Fixes: llvm#85333
Reapply: llvm#88440 after fixing the non-determinism issues in llvm#90995
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GVNSink produces incorrect codegen with respect to GEPs
5 participants