-
Notifications
You must be signed in to change notification settings - Fork 5.8k
JDK-8299688: Adopt C++14 compatible std::bit_cast introduced in C++20 #11865
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
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
👋 Welcome back jcking! A progress list of the required criteria for merging this PR into |
…_cast Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Webrevs
|
Signed-off-by: Justin King <jcking@google.com>
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.
Leaving this review for Kim ... but saw a few nits that could be fixed in the meantime.
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
@kimbarrett Friendly poke. Benefit is when we eventually get to C++20 we can just do |
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.
std::bit_cast covers cases beyond what's covered by PrimitiveConversions::cast.
Trying to be compatible means we should be covering those additional cases,
which this change is doing. However, it's not obvious those additional cases
are useful to us. The additional constraints for PrimitiveConversions::cast
also simplify the implementation (and testing).
Indeed, there are some conversions provided by std::bit_cast and this change
that I think I would prefer to actively not support. The pointer to pointer
case is an example. I don't want such potentially dangerous casts "hidden" by
using bit_cast. I'd very much prefer explicit static_cast or reinterpret_cast
as appropriate. (I'm ambivalent about the integer<->pointer casts provided by
PrimitiveConversions::cast, now thinking they were probaby a mistake.)
And using bit_casts between pointers and floating point types? Yuck!
At some point when we support C++20 we can consider globally replacing
PrimitiveConversions::cast with std::bit_cast, but I'm not convinced this
interim replacement should be done. Given the less restrictive conversions
supported by std::bit_cast I'm not even entirely sure I'd want to do such a
renaming. I know in the past I've suggested that as a possibility, but having
now looked at the new function more carefully, I'm not so sure.
return __builtin_bit_cast(To, from); | ||
#else | ||
// Most modern compilers will produce optimal code for memcpy. | ||
To to; |
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 default constructs To, which is potentially problematic, and is also an extra requirement. I do see
the corresponding restriction in the enabling constraints, but that isn't part of the Standard function's
contract. The problem can be avoided, but since I'm not sure we should be making this change at all...
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.
Removed the requirement, since I am strictly only supported what PrimitiveConversions::cast supported for now.
std::is_integral<To>::value)> | ||
constexpr To bit_cast(const From& from) { | ||
#if HAS_BUILTIN(__builtin_bit_cast) && !defined(__APPLE__) | ||
return __builtin_bit_cast(To, from); |
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.
Throughout, I see no good reason to uglify the code with platform-specific conditional code
when the portable version is just fine. And then the supporting infrastructure to define that
macro isn't needed. The !defined(__APPLE__)
makes this seem particularly problematic.
Is the new HAS_BUILTIN
just broken? Or is __builtin_bit_cast
broken for Macs? Or what?
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 also don't like the direct call to __builtins either, gcc to my knowledge automatically replaces the appropriate builtins when optimization is specified
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.
Apple Clang seemed to have a bug with the version we are using in GHA. I bumped them to 12.5.1 and the error disappeared.
__builtin_bit_cast
is used to implement std::bit_cast for Clang/GCC. It is purely a compiler construct IIRC and has no runtime function equivalent. It's always replaced with machine code for performing a byte-wise copy and ensures no shenanigans occur. Additionally std::bit_cast is the only way to convert between floating point and integral without invoking undefined behavior, though the union trick does currently work. The other option is std::memcpy.
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 know what __builtin_bit_cast
is. I don't think we need to use it, so shouldn't uglify the code to do so.
There is also a risk of different semantics depending whether the builtin is used or not. For example,
std::bit_cast
between a floating point type and an integral type is constexpr, so presumably the builtin
is similarly constexpr. But the non-builtin implementation isn't.
|
||
// Trivial implementation when To and From are the same. | ||
template <typename To, typename From, ENABLE_IF(std::is_same<From, To>::value)> | ||
constexpr To bit_cast(const From& from) { |
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 missing the tivially copiable requirement for std::bit_cast. Also, it seems like the is_same
SFINAE should not be needed here. Why not just
template <typename T, ENABLE_IF(std::is_trivially_copyable<T>::value)>
constexpr T bit_cast(const T& value) {
return value;
}
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 need for this overload to match the Standard specification is one of the reasons why I had refrained
from doing something like this change. This overload's existence (slightly) complicates others by requiring
an additional !std::is_same<To, From>
constraint. Since such a nop conversion hasn't arisen in our code
(yet), there isn't currently a need for this. I also don't see any tests for it.
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 removed the is_same. It technically isn't needed to conform. I also decided to not conform to the full standard and restrict uses based on the existing PrimitiveConversions::cast restrictions.
memcpy(&to, &from, sizeof(to)); | ||
return to; | ||
} | ||
|
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 didn't know about this. The two uses should have been using PrimitiveConversions::cast rather than adding this.
inline jdouble jdouble_cast (jlong x) { return ((DoubleLongConv*)&x)->d; } | ||
inline jlong jlong_cast (jdouble x) { return bit_cast<jlong>(x); } | ||
inline julong julong_cast (jdouble x) { return bit_cast<julong>(x); } | ||
inline jdouble jdouble_cast (jlong x) { return bit_cast<jdouble>(x); } |
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 think the changes to the jXXX_cast's should not be made. Instead, I think these should be eliminated
and the relatively few uses should be changed to use PrimitiveConversions::cast / bit_cast directly.
These functions have potentially unintended and problematic implicit conversions of the arguments.
Such a change should have it's own PR. There's already a bug related to this:
https://bugs.openjdk.org/browse/JDK-8297539
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.
Reverted changes to globalDefinitions.hpp.
@@ -25,6 +25,7 @@ | |||
#ifndef SHARE_UTILITIES_GLOBALDEFINITIONS_HPP | |||
#define SHARE_UTILITIES_GLOBALDEFINITIONS_HPP | |||
|
|||
#include "utilities/bitCast.hpp" |
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 think including bitCast here is problematic. globalDefinitions is included nearly everywhere, while
bitcasting is only needed in a few. There is also a high risk of circular include dependencies when
adding anything to this file's include list. It looks like this addition happens to work today, but...
And as noted elsewhere, I'm not sure some of the changes being made here to use bit_cast should
be made anyway. And if they should, perhaps the relevant code ought to be split out of this file.
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.
Reverted changes to globalDefinitions.hpp.
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
Signed-off-by: Justin King <jcking@google.com>
std::is_integral<To>::value)> | ||
constexpr To bit_cast(const From& from) { | ||
#if HAS_BUILTIN(__builtin_bit_cast) && !defined(__APPLE__) | ||
return __builtin_bit_cast(To, from); |
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 know what __builtin_bit_cast
is. I don't think we need to use it, so shouldn't uglify the code to do so.
There is also a risk of different semantics depending whether the builtin is used or not. For example,
std::bit_cast
between a floating point type and an integral type is constexpr, so presumably the builtin
is similarly constexpr. But the non-builtin implementation isn't.
// | ||
// Casts from type From to type To without changing the underlying bit representation. This is | ||
// partially compatible with std::bit_cast introduced in C++20, but is more restrictive on the type | ||
// of conversions allowed. |
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.
If it's not compatible (or at least trying to be, in so far as possible within the restrictions we operate under)
with std::bit_cast
(and I've suggested there are reasons why we shouldn't be compatible), then using the
name bit_cast
seems like a mistake that will only cause confusion.
PrimitiveConversions::cast
might not be the best name either (John Rose certainly expressed dislike), but
discussing a name change is probably better done outside the context of a PR.
In the meantime, I don't think this part of the change should be made. There are various places that should
be using PrimitiveConversions::cast (under whatever name is ultimately chosen) but aren't, and they can be
changed to do so. Later, once we've picked the final name, we can do a global rename.
@@ -235,7 +235,7 @@ jobs: | |||
uses: ./.github/workflows/build-macos.yml | |||
with: | |||
platform: macos-x64 | |||
xcode-toolset-version: '11.7' | |||
xcode-toolset-version: '12.5.1' |
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 and the other toolchain update proposed here are to avoid the
mentioned bug in Apple Clang, so that __builtin_bit_cast
can be used
with that compiler. Since I don't think we should be using the
builtin, I don't think this is needed either. There may be other
reasons to move the GHA toolchain version forward, but that should be
a separate discussion.
Adopts a C++14 compatible implementation of std::bit_cast.
PrimitiveConversions::cast
is refactored intobit_cast
and extended to support all compatible types. Additionally it makes use of__builtin_bit_cast
when available, which is strictly well defined compared to fallback approaches which are sometimes lurking in undefined behavior territory.Lastly some legacy casting is updated to use
bit_cast
inutilities/globalDefinitions.hpp
.Progress
Issue
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/11865/head:pull/11865
$ git checkout pull/11865
Update a local copy of the PR:
$ git checkout pull/11865
$ git pull https://git.openjdk.org/jdk pull/11865/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 11865
View PR using the GUI difftool:
$ git pr show -t 11865
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/11865.diff