-
Notifications
You must be signed in to change notification settings - Fork 773
[SYCL] Trick to avoid strict pointer aliasing warnings #1203
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
[SYCL] Trick to avoid strict pointer aliasing warnings #1203
Conversation
The problem is that gcc < 7.2 emit the following warning: ``` warning: dereferencing type-punned pointer will break stric t-aliasing rules [-Wstrict-aliasing] (*(T *)m_Mem).~T(); ^ ``` Interesting, that this only happens with `-O2` optimization level Replaced C-style casts with C++ `reinterpret_cast` and this is actually quite a hack, because according to the docs [1]: > the resulting pointer may only be dereferenced safely if allowed by > the type aliasing rules Type aliasing rules allow to represent any object as `char *`, but not the way around, i.e. array of characters cannot be reinterpreted as an object. `std::memcpy` also doesn't work here, because there is no requirement that `T` is trivially copyable. Another way to trick a compiler is to save pointer returned from placement new: it already has type `T *`, so, we can store it within the class and avoid casts. Hovewer, this is also a tricky thing, because since `m_Mem` and this new pointer point to different types, compiler could assume that they don't alias (when they actually point to the same memory location) and perform some undesired transformations. [1]: https://en.cppreference.com/w/cpp/language/reinterpret_cast Signed-off-by: Alexey Sachkov <[email protected]>
@Ruyk, @Alexander-Johnston, could you please take a look? This seems to be a regression introduced in #1091 (in particular, here) BTW, patch for the second possible trick mentioned in the description: diff --git a/sycl/include/CL/sycl/property_list.hpp b/sycl/include/CL/sycl/property_list.hpp
index 439b2b1..b3d927a 100644
--- a/sycl/include/CL/sycl/property_list.hpp
+++ b/sycl/include/CL/sycl/property_list.hpp
@@ -77,48 +77,47 @@ public:
PropertyHolder(const PropertyHolder &P) {
if (P.isInitialized()) {
- new (m_Mem) T(P.getProp());
- m_Initialized = true;
+ m_MemPtr = new (m_Mem) T(P.getProp());
}
}
~PropertyHolder() {
- if (m_Initialized) {
- (*(T *)m_Mem).~T();
+ if (isInitialized()) {
+ m_MemPtr->~T();
+ m_MemPtr = nullptr;
}
}
PropertyHolder &operator=(const PropertyHolder &Other) {
if (this != &Other) {
- if (m_Initialized) {
- (*(T *)m_Mem).~T();
- m_Initialized = false;
+ if (isInitialized()) {
+ m_MemPtr->~T();
+ m_MemPtr = nullptr;
}
- if (Other.m_Initialized) {
- new (m_Mem) T(Other.getProp());
- m_Initialized = true;
+ if (Other.isInitialized()) {
+ m_MemPtr = new (m_Mem) T(Other.getProp());
}
}
return *this;
}
void setProp(const T &Rhs) {
- new (m_Mem) T(Rhs);
- m_Initialized = true;
+ m_MemPtr = new (m_Mem) T(Rhs);
}
const T &getProp() const {
- assert(true == m_Initialized && "Property was not set!");
- return *(const T *)m_Mem;
+ assert(isInitialized() && "Property was not set!");
+ return *m_MemPtr;
}
- bool isInitialized() const { return m_Initialized; }
+ bool isInitialized() const { return m_MemPtr != nullptr; }
private:
// Memory that is used for property allocation
alignas(T) unsigned char m_Mem[sizeof(T)];
- // Indicate whether property initialized or not.
- bool m_Initialized = false;
+ // Used to avoid problems with strict aliasing rules (looks like a hack,
+ // though). Also used to indicate wheter property initialized or not
+ T *m_MemPtr = nullptr;
};
// This macro adds specialization of class Prop which provides possibility to |
@steffenlarsen @StuartDAdams The |
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.
Just some minor suggestions.
I believe, that this text |
I agree, |
@s-kanaev,
I'm not quite sure. According to the Type aliasing section, the code I've written is a UB: when we try to reinterpret
This is not true in general. int A = 10; float F = 3.14;
A = (int)F; // works
A = reinterpret_cast<int>(F); // doesn't work
A = reinterpret_cast<int &>(F); // works, but this is UB (absolutely the same as I exploit in this PR) |
If you're sure that the compiler has no opportunity to break this code, and you just want to silence the warning, then |
A = reinterpret_cast<int &>(F); // works, but this is UB (absolutely the same as I exploit in this PR) What you've actually exploited in this PR is more like this (only need a char array somewhere about it): A = *reinterpret_cast<int *>(&F); |
If anyone wonders what is "wrong" with the code: https://lkml.org/lkml/2009/1/12/369 |
@AlexeySachkov, are you going to make any changes to this PR or I can merge? |
@bader, go ahead with merge. Right now I don't have any better solution, so, no plans for changes |
The problem is that gcc < 7.2 emit the following warning: ``` warning: dereferencing type-punned pointer will break stric t-aliasing rules [-Wstrict-aliasing] (*(T *)m_Mem).~T(); ^ ``` Interesting, that this only happens with `-O2` optimization level Replaced C-style casts with C++ `reinterpret_cast` and this is actually quite a hack, because according to the docs [1]: > the resulting pointer may only be dereferenced safely if allowed by > the type aliasing rules Type aliasing rules allow to represent any object as `char *`, but not the way around, i.e. array of characters cannot be reinterpreted as an object. `std::memcpy` also doesn't work here, because there is no requirement that `T` is trivially copyable. Another way to trick a compiler is to save pointer returned from placement new: it already has type `T *`, so, we can store it within the class and avoid casts. Hovewer, this is also a tricky thing, because since `m_Mem` and this new pointer point to different types, compiler could assume that they don't alias (when they actually point to the same memory location) and perform some undesired transformations. [1]: https://en.cppreference.com/w/cpp/language/reinterpret_cast Signed-off-by: Alexey Sachkov <[email protected]>
The problem is that gcc < 7.2 emit the following warning:
Interesting, that this only happens with
-O2
optimization levelReplaced C-style casts with C++
reinterpret_cast
and this is actuallyquite a hack, because according to the docs 1:
Type aliasing rules allow to represent any object as
char *
, but notthe way around, i.e. array of characters cannot be reinterpreted as an
object.
std::memcpy
also doesn't work here, because there is no requirementthat
T
is trivially copyable.Another way to trick a compiler is to save pointer returned from
placement new: it already has type
T *
, so, we can store it within theclass and avoid casts. Hovewer, this is also a tricky thing, because since
m_Mem
and this new pointer point to different types, compiler couldassume that they don't alias (when they actually point to the same memory
location) and perform some undesired transformations.