[llvm] [SCCP] Support structure in freeze (PR #226537)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 26 15:07:42 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 1647d2bd9fc8bbd7245ba3a36e31882cf4f4a793 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..208034c1782eb 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_undef_elt_split_uses(i1 %c) {
+; CHECK-LABEL: define void @freeze_struct_undef_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 undef, 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 undef, 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 no undef is
+; involved 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