Skip to content

Commit 2f74cff

Browse files
swolchokpytorchmergebot
authored andcommitted
Remove reinterpret_casts with undefined behavior from stable/library.h (#151595)
There is a list of valid uses of `reinterpret_cast` (see https://en.cppreference.com/w/cpp/language/reinterpret_cast), and the use here was not on the list, hence undefined behavior. Implement what we meant using memcpy, which is well-defined. Differential Revision: [D73200791](https://our.internmc.facebook.com/intern/diff/D73200791/) Pull Request resolved: #151595 Approved by: https://github.com/janeyx99
1 parent 3380a46 commit 2f74cff

2 files changed

Lines changed: 33 additions & 11 deletions

File tree

docs/source/notes/libtorch_stable_abi.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ This note will eventually contain more details on how to use the APIs in torch/c
88

99
| type in custom extension | StableIValue representation | type in libtorch | Schema Type |
1010
| -------- | ------- | ------- | ------- |
11-
| std::optional\<S> | \*reinterpret_cast\<(StableIValue\*)\*>, pointer to a StableIValue recursively defined | std::optional\<T> | Type? |
12-
| std::nullopt | \*reinterpret_cast\<nullptr_t\*> | IValue() | None |
13-
| RAIIATH | \*reinterpret_cast\<uint64_t\*> of AtenTensorHandle | at::Tensor | Tensor |
14-
| int32_t | \*reinterpret_cast\<uint64_t\*> | at::ScalarType | ScalarType |
15-
| int32_t | \*reinterpret_cast\<uint64_t\*> | at::Layout | Layout |
16-
| int32_t | \*reinterpret_cast\<uint64_t\*> | at::MemoryFormat | MemoryFormat |
17-
| bool | \*reinterpret_cast\<uint64_t\*> | bool | bool |
18-
| int64_t | \*reinterpret_cast\<uint64_t\*> | int64_t | int |
19-
| double | \*reinterpret_cast\<uint64_t\*> | double | float |
11+
| std::optional\<S> | raw bitwise copy into leading bytes of uint64_t of pointer to a new StableIValue representing S | std::optional\<T> | Type? |
12+
| std::nullopt | nullptr | IValue() | None |
13+
| RAIIATH | raw bitwise copy of underlying AtenTensorHandle into leading bytes of uint64_t | at::Tensor | Tensor |
14+
| int32_t | raw bitwise copy into leading bytes of uint64_t | at::ScalarType | ScalarType |
15+
| int32_t | raw bitwise copy into leading bytes of uint64_t | at::Layout | Layout |
16+
| int32_t | raw bitwise copy into leading bytes of uint64_t | at::MemoryFormat | MemoryFormat |
17+
| bool | raw bitwise copy into leading bytes of uint64_t | bool | bool |
18+
| int64_t | raw bitwise copy into leading bytes of uint64_t | int64_t | int |
19+
| double | raw bitwise copy into leading bytes of uint64_t | double | float |
2020
| ? | ? | c10::Device | Device |
2121
| ? | ? | c10::Stream | Stream |
2222
| ? | ? | c10::complex<double> | complex |

torch/csrc/stable/library.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,17 @@ StableIValue from(T val) {
2525
static_assert(
2626
sizeof(T) <= sizeof(StableIValue),
2727
"StableLibrary stack does not support parameter types larger than 64 bits.");
28-
return *reinterpret_cast<StableIValue*>(&val);
28+
static_assert(std::is_trivially_copyable_v<T>);
29+
// Initialization should be cheap enough; let's give people well-specified
30+
// reproducible behavior.
31+
StableIValue result = 0;
32+
// NOTE [-Wclass-memaccess ]: reinterpret_cast to suppress
33+
// overzealous -Wclass-memaccess. (see
34+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107361) We have a
35+
// static_assert above that T is trivially copyable, which should be
36+
// enough.
37+
std::memcpy(&result, reinterpret_cast<void*>(&val), sizeof(val));
38+
return result;
2939
}
3040

3141
// Specialization for std::nullopt_t
@@ -76,7 +86,19 @@ template <
7686
typename T,
7787
std::enable_if_t<!detail::is_optional<T>::value, bool> = true>
7888
T to(StableIValue val) {
79-
return *reinterpret_cast<T*>(&val);
89+
static_assert(std::is_trivially_copyable_v<T>);
90+
// T may not have a default constructor. (For example, it might be
91+
// c10::Device.) However, std::memcpy implicitly creates a T at the
92+
// destination. So, we can use a union to work around this lack of
93+
// default constructor.
94+
union Result {
95+
Result() {}
96+
T t;
97+
};
98+
Result result;
99+
// See NOTE[ -Wclass-memaccess ] above.
100+
std::memcpy(reinterpret_cast<void*>(&result.t), &val, sizeof(result));
101+
return result.t;
80102
}
81103

82104
template <

0 commit comments

Comments
 (0)