[llvm] [SCCP] Support structure in freeze (PR #226537)

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 26 15:12:45 PDT 2026


https://github.com/aokblast updated https://github.com/llvm/llvm-project/pull/226537

>From 363b0797a30e11564fd64d674686e4fe6f3e6a5f Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Fri, 25 Sep 2026 12:01:04 -0500
Subject: [PATCH 1/2] [SCCP] Support structure in freeze

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp   |  31 ++---
 llvm/test/Transforms/SCCP/freeze-struct.ll | 125 +++++++++++++++++++++
 2 files changed, 141 insertions(+), 15 deletions(-)
 create mode 100644 llvm/test/Transforms/SCCP/freeze-struct.ll

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index 114389ff057a7..7327c80aa0ded 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -1724,27 +1724,28 @@ void SCCPInstVisitor::visitUnaryOperator(Instruction &I) {
 }
 
 void SCCPInstVisitor::visitFreezeInst(FreezeInst &I) {
-  // If this freeze returns a struct, just mark the result overdefined.
-  // TODO: We could do a lot better than this.
-  if (I.getType()->isStructTy())
-    return (void)markOverdefined(&I);
-
-  ValueLatticeElement V0State = getValueState(I.getOperand(0));
-  ValueLatticeElement &IV = ValueState[&I];
   // resolvedUndefsIn might mark I as overdefined. Bail out, even if we would
   // discover a concrete value later.
-  if (IV.isOverdefined())
+  if (isInstFullyOverDefined(I))
     return (void)markOverdefined(&I);
 
-  // If something is unknown/undef, wait for it to resolve.
-  if (V0State.isUnknownOrUndef())
-    return;
+  forEachLatticeElement(&I, [&](auto GetValueState) {
+    ValueLatticeElement V0State = GetValueState(I.getOperand(0));
+    ValueLatticeElement &IV = GetValueState(&I);
+    // If something is unknown/undef, wait for it to resolve.
+    if (V0State.isUnknownOrUndef())
+      return;
 
-  if (SCCPSolver::isConstant(V0State) &&
-      isGuaranteedNotToBeUndefOrPoison(getConstant(V0State, I.getType())))
-    return (void)markConstant(IV, &I, getConstant(V0State, I.getType()));
+    // Freeze is a no-op on a constant that is guaranteed not to be undef or
+    // poison. Integer constants are represented as single-element ranges and
+    // can never be undef or poison.
+    if (SCCPSolver::isConstant(V0State) &&
+        (!V0State.isConstant() ||
+         isGuaranteedNotToBeUndefOrPoison(V0State.getConstant())))
+      return (void)mergeInValue(IV, &I, V0State);
 
-  markOverdefined(&I);
+    markOverdefined(IV, &I);
+  });
 }
 
 // Handle Binary Operators.
diff --git a/llvm/test/Transforms/SCCP/freeze-struct.ll b/llvm/test/Transforms/SCCP/freeze-struct.ll
new file mode 100644
index 0000000000000..db40cdfb1b77e
--- /dev/null
+++ b/llvm/test/Transforms/SCCP/freeze-struct.ll
@@ -0,0 +1,125 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=sccp -S | FileCheck %s
+
+declare void @use(i32)
+
+define { i32, i32 } @freeze_const_struct() {
+; CHECK-LABEL: define { i32, i32 } @freeze_const_struct() {
+; CHECK-NEXT:    ret { i32, i32 } { i32 1, i32 2 }
+;
+  %fr = freeze { i32, i32 } { i32 1, i32 2 }
+  ret { i32, i32 } %fr
+}
+
+; The poison member must stay frozen; the constant member is propagated.
+define i32 @freeze_partial_poison_struct() {
+; CHECK-LABEL: define i32 @freeze_partial_poison_struct() {
+; CHECK-NEXT:    [[FR:%.*]] = freeze { i32, i32 } { i32 1, i32 poison }
+; CHECK-NEXT:    [[B:%.*]] = extractvalue { i32, i32 } [[FR]], 1
+; CHECK-NEXT:    call void @use(i32 [[B]])
+; CHECK-NEXT:    ret i32 1
+;
+  %fr = freeze { i32, i32 } { i32 1, i32 poison }
+  %a = extractvalue { i32, i32 } %fr, 0
+  %b = extractvalue { i32, i32 } %fr, 1
+  call void @use(i32 %b)
+  ret i32 %a
+}
+
+; A struct with one constant and one overdefined member keeps the constant.
+define i32 @freeze_insertvalue_struct({ i32, i32 } %s) {
+; CHECK-LABEL: define i32 @freeze_insertvalue_struct(
+; CHECK-SAME: { i32, i32 } [[S:%.*]]) {
+; CHECK-NEXT:    [[S1:%.*]] = insertvalue { i32, i32 } [[S]], i32 7, 0
+; CHECK-NEXT:    [[FR:%.*]] = freeze { i32, i32 } [[S1]]
+; CHECK-NEXT:    [[B:%.*]] = extractvalue { i32, i32 } [[FR]], 1
+; CHECK-NEXT:    call void @use(i32 [[B]])
+; CHECK-NEXT:    ret i32 7
+;
+  %s1 = insertvalue { i32, i32 } %s, i32 7, 0
+  %fr = freeze { i32, i32 } %s1
+  %a = extractvalue { i32, i32 } %fr, 0
+  %b = extractvalue { i32, i32 } %fr, 1
+  call void @use(i32 %b)
+  ret i32 %a
+}
+
+; Member 0 agrees on both phi edges and is propagated through the freeze.
+define i32 @freeze_struct_phi(i1 %c) {
+; CHECK-LABEL: define i32 @freeze_struct_phi(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br i1 [[C]], label %[[T:.*]], label %[[F:.*]]
+; CHECK:       [[T]]:
+; CHECK-NEXT:    br label %[[M:.*]]
+; CHECK:       [[F]]:
+; CHECK-NEXT:    br label %[[M]]
+; CHECK:       [[M]]:
+; CHECK-NEXT:    [[P:%.*]] = phi { i32, i32 } [ { i32 3, i32 4 }, %[[T]] ], [ { i32 3, i32 5 }, %[[F]] ]
+; CHECK-NEXT:    [[FR:%.*]] = freeze { i32, i32 } [[P]]
+; CHECK-NEXT:    [[B:%.*]] = extractvalue { i32, i32 } [[FR]], 1
+; CHECK-NEXT:    call void @use(i32 [[B]])
+; CHECK-NEXT:    ret i32 3
+;
+entry:
+  br i1 %c, label %t, label %f
+
+t:
+  br label %m
+
+f:
+  br label %m
+
+m:
+  %p = phi { i32, i32 } [ { i32 3, i32 4 }, %t ], [ { i32 3, i32 5 }, %f ]
+  %fr = freeze { i32, i32 } %p
+  %a = extractvalue { i32, i32 } %fr, 0
+  %b = extractvalue { i32, i32 } %fr, 1
+  call void @use(i32 %b)
+  ret i32 %a
+}
+
+; Member 1 is a range, not a single constant, so it must stay frozen.
+define i32 @freeze_struct_phi_range(i1 %c) {
+; CHECK-LABEL: define range(i32 0, 2) i32 @freeze_struct_phi_range(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br i1 [[C]], label %[[T:.*]], label %[[F:.*]]
+; CHECK:       [[T]]:
+; CHECK-NEXT:    br label %[[M:.*]]
+; CHECK:       [[F]]:
+; CHECK-NEXT:    br label %[[M]]
+; CHECK:       [[M]]:
+; CHECK-NEXT:    [[P:%.*]] = phi { i32, i32 } [ { i32 3, i32 4 }, %[[T]] ], [ { i32 3, i32 5 }, %[[F]] ]
+; CHECK-NEXT:    [[FR:%.*]] = freeze { i32, i32 } [[P]]
+; CHECK-NEXT:    [[B:%.*]] = extractvalue { i32, i32 } [[FR]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[B]], 6
+; CHECK-NEXT:    [[R:%.*]] = zext i1 [[CMP]] to i32
+; CHECK-NEXT:    ret i32 [[R]]
+;
+entry:
+  br i1 %c, label %t, label %f
+
+t:
+  br label %m
+
+f:
+  br label %m
+
+m:
+  %p = phi { i32, i32 } [ { i32 3, i32 4 }, %t ], [ { i32 3, i32 5 }, %f ]
+  %fr = freeze { i32, i32 } %p
+  %b = extractvalue { i32, i32 } %fr, 1
+  %cmp = icmp ult i32 %b, 6
+  %r = zext i1 %cmp to i32
+  ret i32 %r
+}
+
+; Non-integer member types: a float constant and a pointer constant.
+define { float, ptr } @freeze_const_struct_float_ptr() {
+; CHECK-LABEL: define { float, ptr } @freeze_const_struct_float_ptr() {
+; CHECK-NEXT:    ret { float, ptr } { float 2.500000e+00, ptr null }
+;
+  %fr = freeze { float, ptr } { float 2.5, ptr null }
+  ret { float, ptr } %fr
+}

>From 010ea1302765eec2fe4b085e5b7e6a54fb8ee13b Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Sat, 26 Sep 2026 17:07:28 -0500
Subject: [PATCH 2/2] fixup! [SCCP] Support structure in freeze

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp   |  6 ++
 llvm/test/Transforms/SCCP/freeze-struct.ll | 83 ++++++++++++++++++++++
 2 files changed, 89 insertions(+)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index 7327c80aa0ded..5b7f29667a5e3 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -1736,6 +1736,12 @@ void SCCPInstVisitor::visitFreezeInst(FreezeInst &I) {
     if (V0State.isUnknownOrUndef())
       return;
 
+    // freeze must yield one value that every use observes. Replacing an
+    // aggregate is not atomic: visitExtractValueInst folds a single element
+    // independently of whether the freeze itself is replaced.
+    if (I.getType()->isStructTy() && V0State.isConstantRangeIncludingUndef())
+      return (void)markOverdefined(IV, &I);
+
     // Freeze is a no-op on a constant that is guaranteed not to be undef or
     // poison. Integer constants are represented as single-element ranges and
     // can never be undef or poison.
diff --git a/llvm/test/Transforms/SCCP/freeze-struct.ll b/llvm/test/Transforms/SCCP/freeze-struct.ll
index db40cdfb1b77e..83f3752a77aa7 100644
--- a/llvm/test/Transforms/SCCP/freeze-struct.ll
+++ b/llvm/test/Transforms/SCCP/freeze-struct.ll
@@ -123,3 +123,86 @@ define { float, ptr } @freeze_const_struct_float_ptr() {
   %fr = freeze { float, ptr } { float 2.5, ptr null }
   ret { float, ptr } %fr
 }
+
+declare { i32, i32 } @opaque()
+declare void @usep({ i32, i32 })
+
+; freeze guarantees that every use observes the same value, but
+; replacing an aggregate is not atomic -- extractvalue folds one element
+; independently of whether the freeze itself is replaced.
+define void @freeze_struct_poison_elt_split_uses(i1 %c) {
+; CHECK-LABEL: define void @freeze_struct_poison_elt_split_uses(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[O:%.*]] = call { i32, i32 } @opaque()
+; CHECK-NEXT:    br i1 [[C]], label %[[T:.*]], label %[[F:.*]]
+; CHECK:       [[T]]:
+; CHECK-NEXT:    [[A:%.*]] = insertvalue { i32, i32 } [[O]], i32 poison, 1
+; CHECK-NEXT:    br label %[[M:.*]]
+; CHECK:       [[F]]:
+; CHECK-NEXT:    [[B:%.*]] = insertvalue { i32, i32 } [[O]], i32 9, 1
+; CHECK-NEXT:    br label %[[M]]
+; CHECK:       [[M]]:
+; CHECK-NEXT:    [[P:%.*]] = phi { i32, i32 } [ [[A]], %[[T]] ], [ [[B]], %[[F]] ]
+; CHECK-NEXT:    [[FR:%.*]] = freeze { i32, i32 } [[P]]
+; CHECK-NEXT:    [[E1:%.*]] = extractvalue { i32, i32 } [[FR]], 1
+; CHECK-NEXT:    call void @use(i32 [[E1]])
+; CHECK-NEXT:    call void @usep({ i32, i32 } [[FR]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  %o = call { i32, i32 } @opaque()
+  br i1 %c, label %t, label %f
+t:
+  %a = insertvalue { i32, i32 } %o, i32 poison, 1
+  br label %m
+f:
+  %b = insertvalue { i32, i32 } %o, i32 9, 1
+  br label %m
+m:
+  %p = phi { i32, i32 } [ %a, %t ], [ %b, %f ]
+  %fr = freeze { i32, i32 } %p
+  %e1 = extractvalue { i32, i32 } %fr, 1
+  call void @use(i32 %e1)
+  call void @usep({ i32, i32 } %fr)
+  ret void
+}
+
+; Same shape, but element 1 is a real constant on both paths, so nothing may be
+; poison and folding is correct.
+define void @freeze_struct_const_elt_split_uses(i1 %c) {
+; CHECK-LABEL: define void @freeze_struct_const_elt_split_uses(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[O:%.*]] = call { i32, i32 } @opaque()
+; CHECK-NEXT:    br i1 [[C]], label %[[T:.*]], label %[[F:.*]]
+; CHECK:       [[T]]:
+; CHECK-NEXT:    [[A:%.*]] = insertvalue { i32, i32 } [[O]], i32 9, 1
+; CHECK-NEXT:    br label %[[M:.*]]
+; CHECK:       [[F]]:
+; CHECK-NEXT:    [[B:%.*]] = insertvalue { i32, i32 } [[O]], i32 9, 1
+; CHECK-NEXT:    br label %[[M]]
+; CHECK:       [[M]]:
+; CHECK-NEXT:    [[P:%.*]] = phi { i32, i32 } [ [[A]], %[[T]] ], [ [[B]], %[[F]] ]
+; CHECK-NEXT:    [[FR:%.*]] = freeze { i32, i32 } [[P]]
+; CHECK-NEXT:    call void @use(i32 9)
+; CHECK-NEXT:    call void @usep({ i32, i32 } [[FR]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  %o = call { i32, i32 } @opaque()
+  br i1 %c, label %t, label %f
+t:
+  %a = insertvalue { i32, i32 } %o, i32 9, 1
+  br label %m
+f:
+  %b = insertvalue { i32, i32 } %o, i32 9, 1
+  br label %m
+m:
+  %p = phi { i32, i32 } [ %a, %t ], [ %b, %f ]
+  %fr = freeze { i32, i32 } %p
+  %e1 = extractvalue { i32, i32 } %fr, 1
+  call void @use(i32 %e1)
+  call void @usep({ i32, i32 } %fr)
+  ret void
+}



More information about the llvm-commits mailing list