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
string literal storage optimization results in wrong-code #57957
Comments
The root issue seems to be that [CWG1823] was intended as an escape-hatch for inline functions, but inline functions are not fully exempt from this problem. Consider: inline const char* foo() {
static constexpr const char* ptr = "foo";
return ptr;
} or: constexpr const char* foo() {
return "foo"; // allowed to return pointers to different objects at different _evaluations_ (not instantiations)
}
inline constexpr const char* ptr = foo(); // foo() is evaluated once to initialize ptr |
Note that the problem doesn't appear at |
@llvm/issue-subscribers-clang-codegen |
This is primarily an ABI issue -- we need some way for the value of // TU 1
inline constexpr const char *ptr = "foo";
const char *ptrA = ptr; // TU 2
inline constexpr const char *ptr = "foo";
const char *ptrB = ptr; ... then The corresponding ABI issue is itanium-cxx-abi/cxx-abi#78 |
I have been discussing this issue with @leni536 and I'm of the view that a non-string-literal object cannot overlap a string literal object, and thus in the first code snippet, What gave rise to the Clang approach here? A belief that the standard intentionally gives the freedom to make string literal objects overlap non-string-literal objects? Or just the fact that the wording is not crystal clear in forbidding it? |
Maybe string literal symbol mangling can resolve part of the issue, however note that for the following TU there is no string literal symbol emitted at all: inline constexpr const char* ptr = "foo";
extern const char arr[] = "foo";
bool foo() {
return ptr == arr;
}
auto get_ptr() {
return ptr;
}
auto get_arr() {
return arr;
} This results in the following codegen when compiled on clang 16, with -std=c++20 -O1 -pedantic-errors: foo(): # @foo()
mov al, 1
ret
get_ptr(): # @get_ptr()
lea rax, [rip + arr]
ret
get_arr(): # @get_arr()
lea rax, [rip + arr]
ret
arr:
.asciz "foo" https://godbolt.org/z/j7rbx61zY Some observations:
So even if the ABI of string literals gets reworked, the optimization also needs to be reworked as it becomes unknown at compile time whether the string literal ends up sharing storage with the array. I'm not sure if it's worth replacing Note that in the original example with 3 TUs all three of |
Do you think the standard is clear that this is not allowed? The wording around string literal objects is vague (and the previous wording before we introduced string literal objects was even more vague). I think it'd be a good idea to file a core issue on this subject if there isn't one already.
The only tool that LLVM gives us to perform merging of string literals is the
There is, but it got optimized away. If you look at the LLVM IR prior to optimization you can see the |
clang++ 15.0.0
compiler options:
-std=c++20 -O2 -pedantic-errors
Consider the following code (this has expected behavior):
So the string literal object referred by
ptr
overlaps witharr1
. Unsurprisingly it can't overlap witharr2
at the same time, since it would mean thatarr1
andarr2
overlap, but that wouldn't be conforming.However if similar array initialisations and pointer comparisons are done in different translation units then we can get contradictory behavior.
https://godbolt.org/z/7K8eqMqrf
None of the pointer comparisons in the program are unspecified as per [expr.eq].
It is tempting to apply the following wording in [lex.string]:
But the string literal is evaluated exactly once, at the initialization of
ptr
. Further evaluations ofptr
itself refers to the same object.These out of the way, we have the following non-conforming behaviors of the executed program:
compare_arr1_to_ptr()
and(+arr1 == ptr)
evaluate to different values. But+arr1
either represents the same address ofptr
or not. The pointers have the same values in the two evaluations. The same applies to the comparisons involvingarr2
.compare_arr1_to_ptr()
andcompare_arr2_to_ptr()
evaluate totrue
, therefore+arr1
represents the same address asptr
, andptr
represents the same address as+arr2
. Butarr1
andarr2
are distinct objects with disjoint bytes of storage [basic.memobj].The text was updated successfully, but these errors were encountered: