-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libcxx] Improve libcxx tests when using Optimizations. #88897
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
Conversation
@llvm/pr-subscribers-libcxx Author: Jack Styles (Stylie777) ChangesWithin the libcxx tests, some tests compare a pointer to a Global Variable to one that is stored locally. This can cause issues where, both are the same value, but the assert is failing as the compiler cannot properly compare them. This changes how these variables are defined to specific tests to ensure the asserts pass. No change to the tests functionality is included. Full diff: https://github.com/llvm/llvm-project/pull/88897.diff 6 Files Affected:
diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.indirect.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.indirect.pass.cpp
index 0b540e09bab3cc..1ccf5501bcea33 100644
--- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.indirect.pass.cpp
+++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.indirect.pass.cpp
@@ -51,7 +51,8 @@ int main(int, char**) {
// Test with an overaligned type
{
new_called = delete_called = 0;
- OverAligned* x = DoNotOptimize(new OverAligned[3]);
+ OverAligned* dummy_data_block = new (std::nothrow) OverAligned;
+ OverAligned* x = DoNotOptimize(dummy_data_block);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(static_cast<void*>(x) == DummyData);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called == 1);
diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.pass.cpp
index 2d021ecb30e793..bdfed1bd8aae18 100644
--- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.pass.cpp
+++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.pass.cpp
@@ -27,16 +27,31 @@
#include "test_macros.h"
#include "../types.h"
+class Data{
+public:
+ Data() = default;
+ ~Data() = default;
+
+ char *getDummyData() {
+ return this->dummy_data_;
+ }
+
+ std::size_t getDummyDataSize() {
+ return sizeof(this->dummy_data_);
+ }
+private:
+ alignas(OverAligned) char dummy_data_[alignof(OverAligned) * 3];
+};
+
int new_called = 0;
int delete_called = 0;
-
-alignas(OverAligned) char DummyData[alignof(OverAligned) * 3];
+Data data_class;
void* operator new[](std::size_t s, std::align_val_t a) {
- assert(s <= sizeof(DummyData));
+ assert(s <= data_class.getDummyDataSize());
assert(static_cast<std::size_t>(a) == alignof(OverAligned));
++new_called;
- return DummyData;
+ return data_class.getDummyData();
}
void operator delete[](void*, std::align_val_t) noexcept {
@@ -49,7 +64,7 @@ int main(int, char**) {
{
new_called = delete_called = 0;
OverAligned* x = new OverAligned[3];
- assert(static_cast<void*>(x) == DummyData);
+ assert(static_cast<void*>(x) == data_class.getDummyData());
assert(new_called == 1);
delete[] x;
diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp
index 227b20f0b1e18b..6c12a9b65f129a 100644
--- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp
+++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp
@@ -55,7 +55,8 @@ int main(int, char**) {
// Test with an overaligned type
{
new_called = delete_called = 0;
- OverAligned* x = DoNotOptimize(new (std::nothrow) OverAligned[3]);
+ OverAligned* dummy_data_block = new (std::nothrow) OverAligned[3];
+ OverAligned* x = DoNotOptimize(dummy_data_block);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(static_cast<void*>(x) == DummyData);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called == 1);
diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align.replace.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align.replace.pass.cpp
index e5ef5f1669752b..254b864a74341f 100644
--- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align.replace.pass.cpp
+++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align.replace.pass.cpp
@@ -27,16 +27,31 @@
#include "test_macros.h"
#include "../types.h"
+class Data{
+public:
+ Data() = default;
+ ~Data() = default;
+
+ char *getDummyData() {
+ return this->dummy_data_;
+ }
+
+ std::size_t getDummyDataSize() {
+ return sizeof(this->dummy_data_);
+ }
+private:
+ alignas(OverAligned) char dummy_data_[alignof(OverAligned)];
+};
+
int new_called = 0;
int delete_called = 0;
-
-alignas(OverAligned) char DummyData[alignof(OverAligned)];
+Data data_class;
void* operator new(std::size_t s, std::align_val_t a) {
- assert(s <= sizeof(DummyData));
+ assert(s <= data_class.getDummyDataSize());
assert(static_cast<std::size_t>(a) == alignof(OverAligned));
++new_called;
- return DummyData;
+ return data_class.getDummyData();
}
void operator delete(void*, std::align_val_t) noexcept {
@@ -49,7 +64,7 @@ int main(int, char**) {
{
new_called = delete_called = 0;
OverAligned* x = new OverAligned;
- assert(static_cast<void*>(x) == DummyData);
+ assert(static_cast<void*>(x) == data_class.getDummyData());
assert(new_called == 1);
delete x;
diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp
index 7eab0729f9ef1a..df0c454ecd3e17 100644
--- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp
+++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp
@@ -54,7 +54,8 @@ int main(int, char**) {
// Test with an overaligned type
{
new_called = delete_called = 0;
- OverAligned* x = DoNotOptimize(new (std::nothrow) OverAligned);
+ OverAligned* dummy_data_block = new (std::nothrow) OverAligned;
+ OverAligned* x = DoNotOptimize(dummy_data_block);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(static_cast<void*>(x) == DummyData);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called == 1);
diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.pass.cpp
index 9a5b53e039025c..5e7f2523de4b10 100644
--- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.pass.cpp
+++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.pass.cpp
@@ -26,16 +26,31 @@
#include "test_macros.h"
#include "../types.h"
+class Data{
+public:
+ Data() = default;
+ ~Data() = default;
+
+ char *getDummyData() {
+ return this->dummy_data_;
+ }
+
+ std::size_t getDummyDataSize() {
+ return sizeof(this->dummy_data_);
+ }
+private:
+ alignas(OverAligned) char dummy_data_[alignof(OverAligned)];
+};
+
int new_nothrow_called = 0;
int delete_called = 0;
-
-alignas(OverAligned) char DummyData[alignof(OverAligned)];
+Data data_class;
void* operator new(std::size_t s, std::align_val_t a, std::nothrow_t const&) noexcept {
- assert(s <= sizeof(DummyData));
+ assert(s <= data_class.getDummyDataSize());
assert(static_cast<std::size_t>(a) == alignof(OverAligned));
++new_nothrow_called;
- return DummyData;
+ return data_class.getDummyData();
}
void operator delete(void*, std::align_val_t) noexcept {
@@ -48,7 +63,7 @@ int main(int, char**) {
{
new_nothrow_called = delete_called = 0;
OverAligned* x = new (std::nothrow) OverAligned;
- assert(static_cast<void*>(x) == DummyData);
+ assert(static_cast<void*>(x) == data_class.getDummyData());
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_nothrow_called == 1);
delete x;
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I am missing something obvious, but I basically don't understand this change. You mention that "the compiler cannot properly compare the values", what do you mean by that?
What issue are you encountering and with what configuration? Can you share a reproducer showcasing the issue?
@@ -51,7 +51,8 @@ int main(int, char**) { | |||
// Test with an overaligned type | |||
{ | |||
new_called = delete_called = 0; | |||
OverAligned* x = DoNotOptimize(new OverAligned[3]); | |||
OverAligned* dummy_data_block = new (std::nothrow) OverAligned; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change uses the nothrow variant of operator new
, but that is not what we are trying to test here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated - Copy Paste Error.
@@ -27,16 +27,31 @@ | |||
#include "test_macros.h" | |||
#include "../types.h" | |||
|
|||
class Data{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why this change is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #88897 (comment)
@ldionne Apologies, not sure I explained it very well there. The issue that has been observed is that with certain configurations when using C++17, when comparing the pointers of Example:
This is running on AArch64 V8 with the following compile options: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, once you've clang-formatted the code.
e32c591
to
f765ebe
Compare
Updated. Ran Clang-Format |
@@ -51,7 +51,8 @@ int main(int, char**) { | |||
// Test with an overaligned type | |||
{ | |||
new_called = delete_called = 0; | |||
OverAligned* x = DoNotOptimize(new OverAligned[3]); | |||
OverAligned* dummy_data_block = new OverAligned; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still incorrect. This test is trying to test operator new[]
, but you are not calling it anymore. You are calling the non-array version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
std::size_t getDummyDataSize() { return sizeof(this->dummy_data_); } | ||
|
||
private: | ||
alignas(OverAligned) char dummy_data_[alignof(OverAligned) * 3]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why indirecting through a class solves anything here. After all you're still returning the address of a global variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have removed the classes and reverted to the initial behaviour. Please see the initial comment as the changes made are different to those previously reviewed. Thanks
f765ebe
to
48a1b8e
Compare
@@ -47,7 +47,7 @@ int main(int, char**) { | |||
// Test with an overaligned type | |||
{ | |||
new_nothrow_called = delete_called = 0; | |||
OverAligned* x = new (std::nothrow) OverAligned; | |||
OverAligned* x = DoNotOptimize(new (std::nothrow) OverAligned); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! I understand this part of the change -- that looks like an oversight to me. However, I don't understand why you need to add a rvalue-reference overload to the function below, it seems to me that the const&
overload should handle this correctly, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change to &&value
was not the correct change, so have reverted this back to &value
for DoNotOptimize
.
In these test cases, we have found that the const&
overload was not allowing for comparison of the pointers correctly, even when the values were the same. See explanation from @ostannard below:
"think that's because DoNotOptimize only uses the value as an input. This prevents the compiler from removing and data-flow leading up to it, but it can still "see" that one pointer came from new, and the other came from the address of a global, so they "can't" alias
This works for me, using both input and output from the inline asm, so the compiler can't follow the data-flow through it:"
template <class Tp>
inline Tp DoNotOptimize(Tp value) {
asm volatile("" : "+r,m"(value) : : "memory");
return value;
}
I have now scaled back the changes to only apply to the tests rather than change the function itself.
384ef88
to
b5f2d77
Compare
@@ -51,7 +51,8 @@ int main(int, char**) { | |||
// Test with an overaligned type | |||
{ | |||
new_called = delete_called = 0; | |||
OverAligned* x = DoNotOptimize(new OverAligned[3]); | |||
OverAligned* dummy_data_block = new OverAligned[3]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really still see the bug if you change all of those back to OverAligned* x = DoNotOptimize(new ...)
?
It seems to me that the only necessary part of this patch is adding the missing DoNotOptimize
calls in libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.pass.cpp
, libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align.replace.pass.cpp
and libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align.replace.pass.cpp
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we do - hence the change in these tests to a different variant of the DoNotOptimize
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then let's delete dummy_data_block
instead of x
below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
As part of the libcxx tests, there is a function provided called `DoNotOptimize`. The tests currently utilise a version which only uses the value as an input, leading to the compiler not removing information relating to the data-flow for the value. This leads to issues when comparing the pointer values, where one is a global pointer and the other a pointer which is local to the function being executed. Another version of `DoNotOptimize` is available which allows for the value to be an input and output of the inline assembly, which allows for the pointers to be successfully compared to one another. This function however only accepts an L Value reference, where the values are initialized as R value reference, so use the variant of `DoNotOptmize` described in the first paragraph. To enable the use of the second variant in the tests, the type of reference has been updated within the test to be an L Value reference, to correctly utlize the correct variant of `DoNotOptimize` that allows for successful comparison of the pointers.
…opriate It has been seen, at high levels of optimizations, issues with comparing the pointer values in some libcxx tests attaining to the assert comparing the pointer of a global variable to that of one created locally within the test. These tests follow the same logic as others that utilize the `DoNotOptimize` function, however these tests do not. To ensure the correct behaviour at high levels of optimization, these tests now utilize the function when building the tests. This allows for the correct behaviour to be observed when running the tests.
b5f2d77
to
ea7a7f2
Compare
Thanks for merging @ldionne and thank you for the review. Much appreciated! |
Within the libcxx tests, some tests compare a pointer to a Global Variable to one that is stored locally. At high levels of optmization the pointers are the same, but the assert is failing as the compiler cannot properly compare them.
This changes how these variables are defined in specific tests to ensure the asserts pass. This is done by
DoNotOptimize
used to accept L value references. This is a different variant that is currently used to ensure that the compiler cannot follow the data-flow through it by using both the input and output from the inline asmDoNotOptimize
calls to tests. When using -O2 optimisations, these are failing because the pointer comparison fails when both values are the same. Adding the function call fixes this.No change to the tests functionality is included.