[libcxx-commits] [libcxx] [llvm] [libc++] Fix optional<T>::transform to optional<T&> (GH#220332) (PR #220500)
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Sep 2 00:20:21 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-x86
Author: Yuvraj Choudhary (yuvrajmnit007)
<details>
<summary>Changes</summary>
Fixes https://github.com/llvm/llvm-project/issues/220332
## Problem
`std::optional<T>::transform(f)` fails to compile when `f` returns a
reference type, e.g.:
```cpp
struct foo {
int& member() { return i_; }
int i_ = 0;
};
std::optional<foo> opt(std::in_place);
auto mem = opt.transform(&foo::member); // error: calling a private constructor
---
Full diff: https://github.com/llvm/llvm-project/pull/220500.diff
4 Files Affected:
- (modified) libcxx/include/__optional/optional_ref.h (+3)
- (modified) libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp (+12)
- (modified) llvm/lib/Target/TargetLoweringObjectFile.cpp (+32)
- (added) llvm/test/CodeGen/X86/unnamed-addr-undef-not-mergeable.ll (+71)
``````````diff
diff --git a/libcxx/include/__optional/optional_ref.h b/libcxx/include/__optional/optional_ref.h
index 20270e0594df5..0ed3869c2136e 100644
--- a/libcxx/include/__optional/optional_ref.h
+++ b/libcxx/include/__optional/optional_ref.h
@@ -79,6 +79,9 @@ class optional<_Tp&> : public __optional_ref_iterator_base<_Tp&> {
private:
_Tp* __value_ = nullptr;
+ template <class>
+ friend class optional;
+
template <class _Up>
_LIBCPP_HIDE_FROM_ABI constexpr void __convert_init_ref_val(_Up&& __val) {
_Tp& __r(std::forward<_Up>(__val));
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
index 96303c5796f6d..9a0ab6488ca16 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
@@ -321,6 +321,18 @@ constexpr bool test_ref() {
assert(&(*o2) == &j);
}
+ // https://github.com/llvm/llvm-project/issues/220332
+ {
+ struct foo {
+ int& member() { return i_; }
+ int i_ = 0;
+ };
+ std::optional<foo> opt(std::in_place);
+ std::same_as<std::optional<int&>> decltype(auto) mem = opt.transform(&foo::member);
+ assert(mem.has_value());
+ assert(&(*mem) == &opt->i_);
+ }
+
return true;
}
#endif
diff --git a/llvm/lib/Target/TargetLoweringObjectFile.cpp b/llvm/lib/Target/TargetLoweringObjectFile.cpp
index b94c374e1dbd5..8562ef04e0550 100644
--- a/llvm/lib/Target/TargetLoweringObjectFile.cpp
+++ b/llvm/lib/Target/TargetLoweringObjectFile.cpp
@@ -80,6 +80,26 @@ static bool isNullOrUndef(const Constant *C) {
return true;
}
+/// Return true if the constant contains any undef or poison sub-elements,
+/// recursively traversing aggregate types. PoisonValue is a subclass of
+/// UndefValue, so isa<UndefValue> catches both.
+///
+/// Such constants must not be placed in mergeable sections: the ELF linker
+/// merges constants in those sections by comparing byte content, but undef/
+/// poison bytes are emitted as zeros, making them byte-identical to a
+/// zeroinitializer constant. Allowing this merge would violate unnamed_addr
+/// semantics. See https://github.com/llvm/llvm-project/issues/220091.
+static bool containsUndefOrPoison(const Constant *C) {
+ if (isa<UndefValue>(C))
+ return true;
+ if (!isa<ConstantAggregate>(C))
+ return false;
+ for (const auto *Operand : C->operand_values())
+ if (containsUndefOrPoison(cast<Constant>(Operand)))
+ return true;
+ return false;
+}
+
static bool isSuitableForBSS(const GlobalVariable *GV) {
const Constant *C = GV->getInitializer();
@@ -316,6 +336,18 @@ SectionKind TargetLoweringObjectFile::getKindForGlobal(const GlobalObject *GO,
if (!GVar->hasGlobalUnnamedAddr())
return SectionKind::getReadOnly();
+ // Do not place constants whose initializer contains undef or poison
+ // sub-elements into mergeable sections. The ELF linker deduplicates
+ // constants in such sections by comparing raw byte content, but undef
+ // and poison bytes are both emitted as zeros. This would make a constant
+ // like `<{ [4 x i8] undef, [4 x i8] zeroinitializer }>` byte-identical
+ // to `zeroinitializer`, allowing the linker to merge them to the same
+ // address even though they are not semantically equivalent. Placing the
+ // constant in non-mergeable .rodata prevents this incorrect merge.
+ // See https://github.com/llvm/llvm-project/issues/220091.
+ if (containsUndefOrPoison(C))
+ return SectionKind::getReadOnly();
+
// If initializer is a null-terminated string, put it in a "cstring"
// section of the right width.
if (ArrayType *ATy = dyn_cast<ArrayType>(C->getType())) {
diff --git a/llvm/test/CodeGen/X86/unnamed-addr-undef-not-mergeable.ll b/llvm/test/CodeGen/X86/unnamed-addr-undef-not-mergeable.ll
new file mode 100644
index 0000000000000..e6a2576fdd56d
--- /dev/null
+++ b/llvm/test/CodeGen/X86/unnamed-addr-undef-not-mergeable.ll
@@ -0,0 +1,71 @@
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu | FileCheck %s
+;
+; Regression test for https://github.com/llvm/llvm-project/issues/220091
+;
+; unnamed_addr constants that contain undef or poison sub-elements must NOT
+; be placed in ELF mergeable sections (.rodata.cstN, SHF_MERGE "aM" flag).
+;
+; The ELF linker merges constants in those sections by comparing raw byte
+; content. LLVM emits undef and poison bytes as zeros, which makes such a
+; constant byte-identical to a zeroinitializer constant of the same size.
+; The linker would then assign them the same address even though they are
+; semantically distinct under the LLVM IR spec, leading to miscompilation.
+;
+; Fix: getKindForGlobal() now returns ReadOnly (plain .rodata) instead of a
+; MergeableConst* section kind when the initializer contains undef or poison.
+
+; ---------------------------------------------------------------------------
+; @undef_and_zero: struct with undef first half, zero second half (8 bytes).
+; Even though it's 8 bytes and unnamed_addr, it must NOT go to .rodata.cst8
+; because the undef half would be emitted as zeros, making it byte-identical
+; to @all_zero below.
+; ---------------------------------------------------------------------------
+ at undef_and_zero = private unnamed_addr constant <{ [4 x i8], [4 x i8] }> <{ [4 x i8] undef, [4 x i8] zeroinitializer }>, align 4
+
+; CHECK: .type .Lundef_and_zero, at object
+; CHECK-NOT: .section .rodata.cst8
+; CHECK: .section .rodata,"a", at progbits
+
+; ---------------------------------------------------------------------------
+; @all_zero: pure zeroinitializer of the same 8-byte size.
+; This SHOULD still go to .rodata.cst8 (no regression for non-undef case).
+; ---------------------------------------------------------------------------
+ at all_zero = private unnamed_addr constant i64 0, align 8
+
+; CHECK: .type .Lall_zero, at object
+; CHECK: .section .rodata.cst8,"aM", at progbits,8
+
+; ---------------------------------------------------------------------------
+; @nested_undef: undef nested inside a struct field.
+; Must be non-mergeable even though it's an aggregate with a concrete field.
+; ---------------------------------------------------------------------------
+ at nested_undef = private unnamed_addr constant { i32, i32 } { i32 undef, i32 42 }, align 4
+
+; CHECK: .type .Lnested_undef, at object
+; CHECK: .section .rodata,"a", at progbits
+
+; ---------------------------------------------------------------------------
+; @poison_field: poison value (PoisonValue is a subclass of UndefValue).
+; Must also be non-mergeable.
+; ---------------------------------------------------------------------------
+ at poison_field = private unnamed_addr constant { i32, i32 } { i32 poison, i32 0 }, align 4
+
+; CHECK: .type .Lpoison_field, at object
+; (stays in .rodata from the previous section, no new .section directive needed)
+
+; ---------------------------------------------------------------------------
+; @no_undef_4: fully concrete constant -- still gets mergeable section.
+; ---------------------------------------------------------------------------
+ at no_undef_4 = private unnamed_addr constant i32 1234, align 4
+
+; CHECK: .type .Lno_undef_4, at object
+; CHECK: .section .rodata.cst4,"aM", at progbits,4
+
+; Dummy function to prevent globals from being dead-stripped.
+define ptr @use() {
+ %a = select i1 true, ptr @undef_and_zero, ptr @all_zero
+ %b = select i1 false, ptr @nested_undef, ptr @poison_field
+ %c = select i1 true, ptr %a, ptr @no_undef_4
+ %d = select i1 false, ptr %b, ptr %c
+ ret ptr %d
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/220500
More information about the libcxx-commits
mailing list