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

[InstCombine] Remove Store when its Ptr is removable #79565

Closed
wants to merge 1 commit into from

Conversation

ParkHanbum
Copy link
Contributor

@ParkHanbum ParkHanbum commented Jan 26, 2024

If the store target is OneUse and the user is AddrSpaceCastInst, proper processing was not performed. Therefore, when processing Alloca in the second process, this storage space was deleted and a problem occurred.

This patch removes the store when the store target is OneUse and the user is AddrSpaceCastInst.

This is a sample IR a crash occurred. crafted by @arsenm

define void @test(ptr align 8 %arg) {
bb:
  %i = alloca ptr, align 8, addrspace(5)
  %i1 = addrspacecast ptr addrspace(5) %i to ptr
  store ptr %arg, ptr %i1, align 8
  %i2 = load ptr, ptr %i1, align 8
  store ptr %i2, ptr null, align 8
  ret void
}

RUN RECORD:

$ bllvm0/bin/clang --version
clang version 18.0.0git (git@github.com:ParkHanbum/llvm-project.git 0633226612a37933ebd41e523bb9383d80b4cf6a)
$ bllvm0/bin/opt --passes=instcombine crash.ll -debug

INSTCOMBINE ITERATION #1 on test
ADD:   ret void
ADD:   store ptr %i2, ptr null, align 8
ADD:   %i2 = load ptr, ptr %i1, align 8
ADD:   store ptr %arg, ptr %i1, align 8
ADD:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
ADD:   %i = alloca ptr, align 8, addrspace(5)
IC: Visiting:   %i = alloca ptr, align 8, addrspace(5)
IC: Visiting:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
IC: Visiting:   store ptr %arg, ptr %i1, align 8
IC: Visiting:   %i2 = load ptr, ptr %i1, align 8
IC: Replacing   %i2 = load ptr, ptr %i1, align 8
    with ptr %arg
IC: Mod =   %i2 = load ptr, ptr %i1, align 8
    New =   %i2 = load ptr, ptr %i1, align 8
IC: ERASE   %i2 = load ptr, ptr %i1, align 8
ADD DEFERRED:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
ADD DEFERRED:   store ptr %arg, ptr %i1, align 8
ADD:   store ptr %arg, ptr %i1, align 8
ADD:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
IC: Visiting:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
IC: Visiting:   store ptr %arg, ptr %i1, align 8
IC: Visiting:   store ptr %arg, ptr null, align 8
IC: Mod =   store ptr %arg, ptr null, align 8
    New =   store ptr poison, ptr null, align 8
ADD:   store ptr poison, ptr null, align 8
IC: Visiting:   store ptr poison, ptr null, align 8
IC: Visiting:   ret void


INSTCOMBINE ITERATION #2 on test
ADD:   ret void
ADD:   store ptr poison, ptr null, align 8
ADD:   store ptr %arg, ptr %i1, align 8
ADD:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
ADD:   %i = alloca ptr, align 8, addrspace(5)
IC: Visiting:   %i = alloca ptr, align 8, addrspace(5)
IC: Replacing   %i1 = addrspacecast ptr addrspace(5) %i to ptr
    with ptr poison
IC: ERASE   %i1 = addrspacecast ptr addrspace(5) %i to ptr
ADD DEFERRED:   %i = alloca ptr, align 8, addrspace(5)
IC: ERASE   store ptr %arg, ptr poison, align 8
IC: ERASE   %i = alloca ptr, align 8, addrspace(5)
IC: Visiting:   store ptr poison, ptr null, align 8
IC: Visiting:   ret void
LLVM ERROR: Instruction Combining did not reach a fixpoint after 1 iterations

ANALYZE :

This problem seems to come from incomplete processing of the addrspacecast instruction.
In the first instcombine process, after the load instruction is removed as follows:

IC: Replacing   %i2 = load ptr, ptr %i1, align 8
    with ptr %arg
IC: Mod =   %i2 = load ptr, ptr %i1, align 8
    New =   %i2 = load ptr, ptr %i1, align 8
IC: ERASE   %i2 = load ptr, ptr %i1, align 8
ADD DEFERRED:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
ADD DEFERRED:   store ptr %arg, ptr %i1, align 8
ADD:   store ptr %arg, ptr %i1, align 8
ADD:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
IC: Visiting:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
IC: Visiting:   store ptr %arg, ptr %i1, align 8
IC: Visiting:   store ptr %arg, ptr null, align 8
IC: Mod =   store ptr %arg, ptr null, align 8
    New =   store ptr poison, ptr null, align 8
ADD:   store ptr poison, ptr null, align 8
IC: Visiting:   store ptr poison, ptr null, align 8
IC: Visiting:   ret void

In the processing the first instruction alloc in the second instcombine process, isAllocSiteRemovable inside the visitAllocSite function is called.

ADD:   ret void
ADD:   store ptr poison, ptr null, align 8
ADD:   store ptr %arg, ptr %i1, align 8
ADD:   %i1 = addrspacecast ptr addrspace(5) %i to ptr
ADD:   %i = alloca ptr, align 8, addrspace(5)
IC: Visiting:   %i = alloca ptr, align 8, addrspace(5)
IC: Replacing   %i1 = addrspacecast ptr addrspace(5) %i to ptr
    with ptr poison
IC: ERASE   %i1 = addrspacecast ptr addrspace(5) %i to ptr
ADD DEFERRED:   %i = alloca ptr, align 8, addrspace(5)

In this function, the addrspacecast and store instructions are removed.

This is the processing that should have occurred when the load instruction was removed. However, addrspacecast does not do this.

@llvmbot
Copy link
Collaborator

llvmbot commented Jan 26, 2024

@llvm/pr-subscribers-llvm-transforms

Author: hanbeom (ParkHanbum)

Changes

If the store target is OneUse and the user is AddrSpaceCastInst, proper processing was not performed. Therefore, when processing Alloca in the second process, this storage space was deleted and a problem occurred.

This patch removes the store when the store target is OneUse and the user is AddrSpaceCastInst.


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp (+4-3)
  • (modified) llvm/test/Transforms/InstCombine/store.ll (+16)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp b/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp
index bb2a77daa60a764..94a0d0cc4acc58a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp
@@ -1385,9 +1385,10 @@ Instruction *InstCombinerImpl::visitStoreInst(StoreInst &SI) {
   if (Ptr->hasOneUse()) {
     if (isa<AllocaInst>(Ptr))
       return eraseInstFromFunction(SI);
-    if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Ptr)) {
-      if (isa<AllocaInst>(GEP->getOperand(0))) {
-        if (GEP->getOperand(0)->hasOneUse())
+    if (isa<GetElementPtrInst>(Ptr) || isa<AddrSpaceCastInst>(Ptr)) {
+      Instruction *PtrI = dyn_cast<Instruction>(Ptr);
+      if (isa<AllocaInst>(PtrI->getOperand(0))) {
+        if (PtrI->getOperand(0)->hasOneUse())
           return eraseInstFromFunction(SI);
       }
     }
diff --git a/llvm/test/Transforms/InstCombine/store.ll b/llvm/test/Transforms/InstCombine/store.ll
index 673395464c85aaf..969d766a2d1e2bf 100644
--- a/llvm/test/Transforms/InstCombine/store.ll
+++ b/llvm/test/Transforms/InstCombine/store.ll
@@ -345,6 +345,22 @@ define void @store_to_readonly_noalias(ptr readonly noalias %0) {
   ret void
 }
 
+; if ptr which store to is addrspacecast, and its target is alloca. remove it.
+define void @src_store_oneuse_addrspaceast_alloca(ptr align 8 %arg) {
+; CHECK-LABEL: @src_store_oneuse_addrspaceast_alloca(
+; CHECK-NEXT:  bb:
+; CHECK-NEXT:    store ptr poison, ptr null, align 8
+; CHECK-NEXT:    ret void
+;
+bb:
+  %i = alloca ptr, align 8, addrspace(5)
+  %i1 = addrspacecast ptr addrspace(5) %i to ptr
+  store ptr %arg, ptr %i1, align 8
+  %i2 = load ptr, ptr %i1, align 8
+  store ptr %i2, ptr null, align 8
+  ret void
+}
+
 !0 = !{!4, !4, i64 0}
 !1 = !{!"omnipotent char", !2}
 !2 = !{!"Simple C/C++ TBAA"}

llvm/test/Transforms/InstCombine/store.ll Outdated Show resolved Hide resolved
llvm/test/Transforms/InstCombine/store.ll Outdated Show resolved Hide resolved
if (isa<AllocaInst>(GEP->getOperand(0))) {
if (GEP->getOperand(0)->hasOneUse())
if (isa<GetElementPtrInst>(Ptr) || isa<AddrSpaceCastInst>(Ptr)) {
Instruction *PtrI = dyn_cast<Instruction>(Ptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Unchecked dyn_cast, use cast<>

Comment on lines 356 to 358
%i = alloca ptr, align 8, addrspace(5)
%i1 = addrspacecast ptr addrspace(5) %i to ptr
store ptr %arg, ptr %i1, align 8
Copy link
Contributor

Choose a reason for hiding this comment

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

This test case doesn't contain a getelementptr. I don't see how anything could have gone wrong previously? Either way, should have a test with and without GEP

Copy link
Contributor Author

Choose a reason for hiding this comment

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

to summarize, do you mean that a test with GEP is also necessary? but imo, this issue have not related with GEP. I would appreciate it if you could tell me why you are comment about GEP.

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean you changed the handling under the dyn_cast. As such I don't see how this could possibly fix anything for the AddrSpaceCastInst case. There's nothing special about AddrSpaceCast specifically. I assume the same would manifest for any transitive pointer user, so I don't understand how this is avoiding the issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@arsenm Now I understand!! I think it would be solve to implement a function like visitallocsite in the alloca instruction. thanks!!

@ParkHanbum
Copy link
Contributor Author

I left a comment about the issue explaining how to fix it. I will close this pull request as it seems unnecessary until this issue is resolved. may I? @arsenm

@ParkHanbum ParkHanbum changed the title [InstCombine] Remove Store which has one-use AddrSpaceCastInst as Ptr (#68120) [InstCombine] Remove Store when its Ptr is removable Jan 29, 2024
Copy link

github-actions bot commented Jan 29, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Previously, if the `Store`'s `Ptr` was `Alloca` and satisfy OneUse()
it would be removed. However, if `Alloca` was overlapped and
referenced by instructions such as `GEP` or `AddrSpaceCast`, it could
not be removed and an error occurred as a result.

This patch fixes this by having the `Store`'s `Ptr` find out
if the target is `Alloca`, and remove the `Store` if possible.
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.

This issue is Won't Fix as far as I'm concerned. Reaching a fix-point in all cases is an explicit non-goal -- the verification exists to catch bugs like failing to add things back to the worklist. You should not perform contortions to reach the fixpoint, unless there is phase-ordering proof that doing so would be useful.

@arsenm
Copy link
Contributor

arsenm commented May 8, 2024

This issue is Won't Fix as far as I'm concerned. Reaching a fix-point in all cases is an explicit non-goal -- the verification exists to catch bugs like failing to add things back to the worklist.

If this is the case, I think the current behavior of error-by-default isn't really reasonable. You should be able to run instcombine standalone without triggering errors like this

I think this should be fixed, but I feel like it should be nowhere near this complicated to address

@arsenm
Copy link
Contributor

arsenm commented May 8, 2024

I think this is resolvable by just using stripPointerCasts in visitStoreInst when it's looking for the alloca/GEP source

@arsenm
Copy link
Contributor

arsenm commented May 8, 2024

@nikic
Copy link
Contributor

nikic commented May 9, 2024

This issue is Won't Fix as far as I'm concerned. Reaching a fix-point in all cases is an explicit non-goal -- the verification exists to catch bugs like failing to add things back to the worklist.

If this is the case, I think the current behavior of error-by-default isn't really reasonable. You should be able to run instcombine standalone without triggering errors like this

Our requirement is that fix-point verification is enabled by default when writing instcombine tests, to prevent accidental introduction of worklist management bugs. The way we currently do this is to make -passes=instcombine enable fix-point verification, as that's what we use in tests. When instantiating InstCombinePass() from C++ code we disable it by default, as the verification should not be enabled in any production use.

Those are basically the constraints here -- happy to hear alternative ways we could have this "verify in tests, don't verify in production" behavior. Possibly the error message just needs to be improved to make it clear that this is not necessary a problem?

Put up a WIP https://github.com/arsenm/llvm-project/tree/issue68120-worklist-issue

This will miscompile cases if there are multiple uses along the stripped chain, I think?

@arsenm
Copy link
Contributor

arsenm commented May 9, 2024

Possibly the error message just needs to be improved to make it clear that this is not necessary a problem?

I think the message definitely should be improved. Downgrade the message to warning?

Put up a WIP https://github.com/arsenm/llvm-project/tree/issue68120-worklist-issue

This will miscompile cases if there are multiple uses along the stripped chain, I think?

I started guarding against these, but so far I haven't had much luck avoiding the second iteration in the mixed multiple use cases.

@ParkHanbum ParkHanbum closed this May 31, 2024
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.

None yet

4 participants