Bug type: crash
Component: EarlyCSE
Bug location: llvm/lib/Transforms/Scalar/EarlyCSE.cpp: isEqualImpl
Issue title: Compiler asserts running pass "early-cse<memssa>"
Labels: crash
llvm:transforms
Reported by: romanova-ekaterina

Consider a testcase like that:
```
; ModuleID = 'test.bc'
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-sie-ps5"

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.smin.i32(i32, i32) #0

; Function Attrs: strictfp
define i32 @baz(i32 %arg, i32 %arg1) local_unnamed_addr #1 {
bb:
  %call = call i32 @llvm.smin.i32(i32 %arg, i32 %arg1) #1
  %call2 = call i32 @llvm.smin.i32(i32 %arg1, i32 %arg)
  %or = or i32 %call2, %call
  ret i32 %or
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #1 = { strictfp }
```

If you run early-cse test, it triggers the assertion (note: make sure to reproduce with version of the compiler with assertions enabled):
```
opt.exe -f test.ll --passes=early-cse
```

```
opt.exe -f test.ll --passes=early-cse                                                                                                 
Assertion failed: Success && "Failed to intersect attributes in callsites that " "passed identical check", file D:\Dev\UPSTREAM\llvm-project\llvm\lib\Transforms\Scalar\EarlyCSE.cpp, line 1312                                                                                                                                           PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.                                                          Stack dump:                                                                                                                                                          0.      Program arguments: opt.exe -f test.ll --passes=early-cse                                                                                                     1.      Running pass "function(early-cse<>)" on module "test.ll"                                                                                                     2.      Running pass "early-cse<>" on function "baz"                                                                                                                 Exception Code: 0x80000003                                                                                                                                           0x00007FF77F8639BC, D:\Dev\Testing\TOOLCHAIN-19080\opt.exe(0x00007FF778600000) + 0x72639BC byte(s)
```

Here is the analysis why the failure happens.

Function EarlyCSE::processNode determines that these two instructions are common subexpression and one of them could be replaced with another.

```
 %call = call i32 @llvm.smin.i32(i32 %arg, i32 %arg1) #1
  %call2 = call i32 @llvm.smin.i32(i32 %arg1, i32 %arg)

  attributes #1 ={ strictfp }
```
The piece of code below determines that these two instructions have the same value.

```
// See if the instruction has an available value.  If so, use it.
if (Value *V = AvailableValues.lookup(&Inst))
```
However, one of these instructions has 'strictfp' attribute, which signifies that optimizations to libm function calls should be prevented. 
I think that because of this attribute is present, these two instructions should have had different values. Hence the bug. 

The actual failure happens later in the function combineIRFlags(Inst, V) that is trying to combine attributes for these two function calls. 
Function tryIntersectAttributes determines the intersection of attributes for these two function calls. This intersection is empty, so it returns false.
Later, the assertion is triggered.

```
bool Success =
        cast<CallBase>(To)->tryIntersectAttributes(cast<CallBase>(&From));
    assert(Success && "Failed to intersect attributes in callsites that "
                      "passed identical check"); 
```


Comments: author: nikic
body: Using strictfp at a call-site should probably be a verifier error.
author: gregbedwell
body: Just to add some extra context to this, the original reproducer here is a large C++ project compiled with clang, and encountering this crash during ThinLTO (and in terms of that project the crash is a recent regression from the last week or two).  Let us know if this example is over-reduced and the bug is actually an earlier pass allowing this state to occur in the first place, and I'll work on another small reproducer.
author: nikic
body: > Using strictfp at a call-site should probably be a verifier error.

Looks like I'm wrong about that. Apparently clang places strictfp on all call-sites in a strictfp function.

In any case, I think the problem here is that we have some special handling for commutative intrinsics, which does not use the usual attribute comparison logic.

Fix patch:
commit 9a0197c3a443caf275a9b1b86669b398ea615c9f
Author: Nikita Popov <npopov@redhat.com>
Date:   Wed Jun 4 09:04:27 2025 +0200

    [EarlyCSE] Check attributes for commutative intrinsics (#142610)
    
    Commutative intrinsics go through a separate code path, which did not
    check for attribute compatibility, resulting in a later assertion
    failure.
    
    Fixes https://github.com/llvm/llvm-project/issues/142462.

diff --git a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
index 09cb2f4cb010..5c62a2cf526e 100644
--- a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
+++ b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
@@ -400,7 +400,9 @@ static bool isEqualImpl(SimpleValue LHS, SimpleValue RHS) {
     return LII->getArgOperand(0) == RII->getArgOperand(1) &&
            LII->getArgOperand(1) == RII->getArgOperand(0) &&
            std::equal(LII->arg_begin() + 2, LII->arg_end(),
-                      RII->arg_begin() + 2, RII->arg_end());
+                      RII->arg_begin() + 2, RII->arg_end()) &&
+           LII->hasSameSpecialState(RII, /*IgnoreAlignment=*/false,
+                                    /*IntersectAttrs=*/true);
   }
 
   // See comment above in `getHashValue()`.


Regression tests:
file: llvm/test/Transforms/EarlyCSE/replace-calls-def-attrs.ll
commands: opt -S -passes=early-cse  < %s
tests: test_name: same_parent_combine_diff_attrs_needs_intersect2
test_body: declare ptr @baz.ptr(i8, i8)

declare i8 @buz.ptr(ptr, ptr)

define i8 @same_parent_combine_diff_attrs_needs_intersect2(i8 %x, i8 %y) {
  %c1 = call nonnull ptr @baz.ptr(i8 noundef %x, i8 noundef %y) #0
  %c0 = call ptr @baz.ptr(i8 %x, i8 noundef %y) #0
  %r = call i8 @buz.ptr(ptr %c0, ptr %c1)
  ret i8 %r
}

attributes #0 = { memory(read) }

test_name: diff_parent_combine_diff_attrs_todo
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @diff_parent_combine_diff_attrs_todo(i1 %c, i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 %x, i8 noundef %y) #0
  br i1 %c, label %T, label %F

T:                                                ; preds = %0
  %c0 = call i8 @baz(i8 noundef %x, i8 noundef %y) #1
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r

F:                                                ; preds = %0
  %r2 = add i8 %c1, 4
  ret i8 %r2
}

attributes #0 = { memory(none) }
attributes #1 = { noinline optnone memory(none) }

test_name: same_parent_combine_diff_attrs
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @same_parent_combine_diff_attrs(i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 noundef %x, i8 noundef %y) #0
  %c0 = call i8 @baz(i8 %x, i8 noundef %y) #0
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r
}

attributes #0 = { memory(none) }

test_name: same_parent_combine_diff_attrs_needs_intersect
test_body: declare ptr @baz.ptr(i8, i8)

declare i8 @buz.ptr(ptr, ptr)

define i8 @same_parent_combine_diff_attrs_needs_intersect(i8 %x, i8 %y) {
  %c1 = call nonnull ptr @baz.ptr(i8 noundef %x, i8 noundef %y) #0
  %c0 = call ptr @baz.ptr(i8 %x, i8 noundef %y) #0
  %r = call i8 @buz.ptr(ptr %c0, ptr %c1)
  ret i8 %r
}

attributes #0 = { memory(none) }

test_name: diff_parent_combine_diff_attrs
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @diff_parent_combine_diff_attrs(i1 %c, i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 %x, i8 noundef %y) #0
  br i1 %c, label %T, label %F

T:                                                ; preds = %0
  %c0 = call i8 @baz(i8 noundef %x, i8 noundef %y) #1
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r

F:                                                ; preds = %0
  %r2 = add i8 %c1, 4
  ret i8 %r2
}

attributes #0 = { memory(none) }
attributes #1 = { memory(read) }

test_name: same_parent_combine_diff_attrs_fail_side_effects
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @same_parent_combine_diff_attrs_fail_side_effects(i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 noundef %x, i8 noundef %y)
  %c0 = call i8 @baz(i8 %x, i8 noundef %y)
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r
}

test_name: same_parent_combine_diff_attrs_todo
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @same_parent_combine_diff_attrs_todo(i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 %x, i8 noundef %y) #0
  %c0 = call i8 @baz(i8 noundef %x, i8 noundef %y) #1
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r
}

attributes #0 = { memory(none) }
attributes #1 = { alwaysinline memory(none) }

test_name: same_parent_combine_diff_attrs_fmf2
test_body: declare float @baz.fp(float, float)

declare i8 @buz.fp(float, float)

define i8 @same_parent_combine_diff_attrs_fmf2(float %x, float %y) {
  %c1 = call nnan float @baz.fp(float noundef %x, float noundef %y) #0
  %c0 = call nnan nsz float @baz.fp(float %x, float noundef %y) #0
  %r = call i8 @buz.fp(float %c0, float %c1)
  ret i8 %r
}

attributes #0 = { memory(none) }

test_name: same_parent_combine_diff_attrs_fmf
test_body: declare float @baz.fp(float, float)

declare i8 @buz.fp(float, float)

define i8 @same_parent_combine_diff_attrs_fmf(float %x, float %y) {
  %c1 = call nnan nsz float @baz.fp(float noundef %x, float noundef %y) #0
  %c0 = call nnan float @baz.fp(float %x, float noundef %y) #0
  %r = call i8 @buz.fp(float %c0, float %c1)
  ret i8 %r
}

attributes #0 = { memory(read) }

test_name: commutative_intrinsic_intersection_failure
test_body: define i32 @commutative_intrinsic_intersection_failure(i32 %arg, i32 %arg1) {
  %call = call i32 @llvm.smin.i32(i32 %arg, i32 %arg1) #1
  %call2 = call i32 @llvm.smin.i32(i32 %arg1, i32 %arg)
  %or = or i32 %call2, %call
  ret i32 %or
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.smin.i32(i32, i32) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #1 = { strictfp }

test_name: diff_parent_combine_diff_attrs_fail
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @diff_parent_combine_diff_attrs_fail(i1 %c, i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 %x, i8 noundef %y) #0
  br i1 %c, label %T, label %F

T:                                                ; preds = %0
  %c0 = call i8 @baz(i8 noundef %x, i8 noundef %y) #1
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r

F:                                                ; preds = %0
  %r2 = add i8 %c1, 4
  ret i8 %r2
}

attributes #0 = { memory(none) }
attributes #1 = { strictfp memory(none) }

test_name: same_parent_combine_diff_attrs_really_needs_intersect
test_body: declare ptr @baz.ptr(i8, i8)

declare i8 @buz.ptr(ptr, ptr)

define i8 @same_parent_combine_diff_attrs_really_needs_intersect(i8 %x, i8 %y) {
  %c1 = call nonnull ptr @baz.ptr(i8 noundef %x, i8 noundef %y) #0
  %c0 = call ptr @baz.ptr(i8 %x, i8 noundef %y) #0
  %r = call i8 @buz.ptr(ptr %c0, ptr noundef %c1)
  ret i8 %r
}

attributes #0 = { memory(read) }

test_name: diff_parent_combine_diff_attrs_preserves_return_attrs
test_body: declare ptr @baz.ptr(i8, i8)

declare i8 @buz.ptr(ptr, ptr)

define i8 @diff_parent_combine_diff_attrs_preserves_return_attrs(i1 %c, i8 %x, i8 %y) {
  %c1 = call nonnull ptr @baz.ptr(i8 %x, i8 noundef %y) #0
  br i1 %c, label %T, label %F

T:                                                ; preds = %0
  %c0 = call nonnull ptr @baz.ptr(i8 noundef %x, i8 noundef %y) #0
  %r = call i8 @buz.ptr(ptr %c0, ptr noundef %c1)
  ret i8 %r

F:                                                ; preds = %0
  ret i8 9
}

attributes #0 = { memory(read) }

test_name: same_parent_combine_diff_attrs_fail
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @same_parent_combine_diff_attrs_fail(i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 %x, i8 noundef %y) #0
  %c0 = call i8 @baz(i8 noundef %x, i8 noundef %y) #1
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r
}

attributes #0 = { memory(none) }
attributes #1 = { strictfp memory(none) }

test_name: same_parent_combine_diff_attrs_quasi_side_effects2
test_body: declare i8 @baz(i8, i8)

declare i8 @buz(i8, i8)

define i8 @same_parent_combine_diff_attrs_quasi_side_effects2(i8 %x, i8 %y) {
  %c1 = call i8 @baz(i8 noundef %x, i8 noundef %y) #0
  %c0 = call i8 @baz(i8 %x, i8 noundef %y)
  %r = call i8 @buz(i8 %c0, i8 %c1)
  ret i8 %r
}

attributes #0 = { memory(none) }