8247909: Improve PrimitiveConversions::cast using C++14

Reimpliment PrimitiveConversions::cast, with some cases now constexpr.
<!-- Anything below this marker will be automatically updated, please do not edit manually! -->
---------
### Progress
- [x] Change must not contain extraneous whitespace
- [x] Commit message must refer to an issue
- [ ] Change must be properly reviewed

### Issue
* [JDK-8247909](https://bugs.openjdk.java.net/browse/JDK-8247909): Improve PrimitiveConversions::cast using C++14


### Download
`$ git fetch https://git.openjdk.java.net/jdk pull/143/head:pull/143`
`$ git checkout pull/143`

Reviewed-by: dholmes, stefank
This commit is contained in:
Kim Barrett 2020-09-15 10:52:50 +00:00
parent fa30241ddb
commit 7eb4d4aa01
4 changed files with 130 additions and 128 deletions

View File

@ -27,6 +27,7 @@
#include "gc/z/zBitField.hpp"
#include "memory/allocation.hpp"
#include "metaprogramming/primitiveConversions.hpp"
#include <type_traits>
//
// Forwarding entry layout
@ -46,7 +47,7 @@
//
class ZForwardingEntry {
friend class PrimitiveConversions;
friend struct PrimitiveConversions::Translate<ZForwardingEntry>;
private:
typedef ZBitField<uint64_t, bool, 0, 1> field_populated;
@ -79,7 +80,7 @@ public:
// Needed to allow atomic operations on ZForwardingEntry
template <>
struct PrimitiveConversions::Translate<ZForwardingEntry> : public TrueType {
struct PrimitiveConversions::Translate<ZForwardingEntry> : public std::true_type {
typedef ZForwardingEntry Value;
typedef uint64_t Decayed;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,147 +25,102 @@
#ifndef SHARE_METAPROGRAMMING_PRIMITIVECONVERSIONS_HPP
#define SHARE_METAPROGRAMMING_PRIMITIVECONVERSIONS_HPP
#include "memory/allocation.hpp"
#include "memory/allStatic.hpp"
#include "metaprogramming/enableIf.hpp"
#include "metaprogramming/integralConstant.hpp"
#include "metaprogramming/isFloatingPoint.hpp"
#include "metaprogramming/isIntegral.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
#include <type_traits>
class PrimitiveConversions : public AllStatic {
// True if types are the same size and either is integral.
template<typename To, typename From>
static constexpr bool check_cast() {
return (sizeof(To) == sizeof(From)) &&
(std::is_integral<To>::value || std::is_integral<From>::value);
}
public:
// Return a value of type T with the same representation as x.
// template<typename To, typename From> To cast(From x)
//
// T and U must be of the same size.
// Return a value of type To with the same value representation as x.
//
// At least one of T or U must be an integral type. The other must
// be an integral, floating point, or pointer type.
template<typename T, typename U> static T cast(U x);
// To and From must be of the same size.
//
// At least one of To or From must be an integral type. The other must
// be an integral, enum, floating point, or pointer type.
// integer -> integer
// Use static_cast for conversion. See C++14 4.7 Integral
// conversions. If To is signed and From unsigned, the result is
// implementation-defined. All supported platforms provide two's
// complement behavior, and that behavior is required by C++20.
// Using an lvalue to reference cast (see C++03 3.10/15) involves a
// reinterpret_cast, which prevents constexpr support.
template<typename To, typename From,
ENABLE_IF(sizeof(To) == sizeof(From)),
ENABLE_IF(std::is_integral<To>::value),
ENABLE_IF(std::is_integral<From>::value)>
static constexpr To cast(From x) {
return static_cast<To>(x);
}
// integer -> enum, enum -> integer
// Use the enum's underlying type for integer -> integer cast.
template<typename To, typename From,
ENABLE_IF(check_cast<To, From>()),
ENABLE_IF(std::is_enum<To>::value)>
static constexpr To cast(From x) {
return static_cast<To>(cast<std::underlying_type_t<To>>(x));
}
template<typename To, typename From,
ENABLE_IF(check_cast<To, From>()),
ENABLE_IF(std::is_enum<From>::value)>
static constexpr To cast(From x) {
return cast<To>(static_cast<std::underlying_type_t<From>>(x));
}
// integer -> pointer, pointer -> integer
// Use reinterpret_cast, so no constexpr support.
template<typename To, typename From,
ENABLE_IF(check_cast<To, From>()),
ENABLE_IF(std::is_pointer<To>::value || std::is_pointer<From>::value)>
static To cast(From x) {
return reinterpret_cast<To>(x);
}
// integer -> floating point, floating point -> integer
// Use the union trick. The union trick is technically UB, but is
// widely and well supported, producing good code. In some cases,
// such as gcc, that support is explicitly documented. Using memcpy
// is the correct method, but some compilers produce wretched code
// for that method, even at maximal optimization levels. Neither
// the union trick nor memcpy provides constexpr support.
template<typename To, typename From,
ENABLE_IF(check_cast<To, From>()),
ENABLE_IF(std::is_floating_point<To>::value ||
std::is_floating_point<From>::value)>
static To cast(From x) {
union { From from; To to; } converter = { x };
return converter.to;
}
// Support thin wrappers over primitive types.
// If derived from TrueType, provides representational conversion
// If derived from std::true_type, provides representational conversion
// from T to some other type. When true, must provide
// - Value: typedef for T.
// - Decayed: typedef for decayed type.
// - static Decayed decay(T x): return value of type Decayed with
// the same representation as x.
// the same value representation as x.
// - static T recover(Decayed x): return a value of type T with the
// same representation as x.
template<typename T> struct Translate : public FalseType {};
private:
template<typename T,
typename U,
bool same_size = sizeof(T) == sizeof(U),
typename Enable = void>
struct Cast;
template<typename T, typename U> static T cast_using_union(U x);
// same value representation as x.
template<typename T> struct Translate : public std::false_type {};
};
// Return an object of type T with the same value representation as x.
//
// T and U must be of the same size. It is expected that one of T and
// U is an integral type, and the other is an integral type, an enum type,
// or a floating point type.
//
// This implementation uses the "union trick", which seems to be the
// best of a bad set of options. Though technically undefined
// behavior, it is widely and well supported, producing good code. In
// some cases, such as gcc, that support is explicitly documented.
//
// Using memcpy is the correct method, but some compilers produce
// wretched code for that method, even at maximal optimization levels.
//
// Using static_cast is only possible for integral and enum types, not
// for floating point types. And for integral and enum conversions,
// static_cast has unspecified or implementation-defined behavior for
// some cases. C++11 <type_traits> can be used to avoid most or all
// of those unspecified or implementation-defined issues, though that
// may require multi-step conversions.
//
// Using reinterpret_cast of references has undefined behavior for
// many cases, and there is much less empirical basis for its use, as
// compared to the union trick.
template<typename T, typename U>
inline T PrimitiveConversions::cast_using_union(U x) {
STATIC_ASSERT(sizeof(T) == sizeof(U));
union { T t; U u; };
u = x;
return t;
}
//////////////////////////////////////////////////////////////////////////////
// cast<T>(x)
//
// Cast<T, U, same_size, Enable>
// Give an informative error if the sizes differ.
template<typename T, typename U>
struct PrimitiveConversions::Cast<T, U, false> {
STATIC_ASSERT(sizeof(T) == sizeof(U));
};
// Conversion between integral types.
template<typename T, typename U>
struct PrimitiveConversions::Cast<
T, U, true,
typename EnableIf<IsIntegral<T>::value && IsIntegral<U>::value>::type>
{
T operator()(U x) const { return cast_using_union<T>(x); }
};
// Convert an enum or floating point value to an integer value.
template<typename T, typename U>
struct PrimitiveConversions::Cast<
T, U, true,
typename EnableIf<IsIntegral<T>::value &&
(std::is_enum<U>::value ||
IsFloatingPoint<U>::value)>::type>
{
T operator()(U x) const { return cast_using_union<T>(x); }
};
// Convert an integer to an enum or floating point value.
template<typename T, typename U>
struct PrimitiveConversions::Cast<
T, U, true,
typename EnableIf<IsIntegral<U>::value &&
(std::is_enum<T>::value ||
IsFloatingPoint<T>::value)>::type>
{
T operator()(U x) const { return cast_using_union<T>(x); }
};
// Convert a pointer to an integral value.
template<typename T, typename U>
struct PrimitiveConversions::Cast<
T, U*, true,
typename EnableIf<IsIntegral<T>::value>::type>
{
T operator()(U* x) const { return reinterpret_cast<T>(x); }
};
// Convert an integral value to a pointer.
template<typename T, typename U>
struct PrimitiveConversions::Cast<
T*, U, true,
typename EnableIf<IsIntegral<U>::value>::type>
{
T* operator()(U x) const { return reinterpret_cast<T*>(x); }
};
template<typename T, typename U>
inline T PrimitiveConversions::cast(U x) {
return Cast<T, U>()(x);
}
// jfloat and jdouble translation to integral types
template<>
struct PrimitiveConversions::Translate<jdouble> : public TrueType {
struct PrimitiveConversions::Translate<jdouble> : public std::true_type {
typedef double Value;
typedef int64_t Decayed;
@ -174,7 +129,7 @@ struct PrimitiveConversions::Translate<jdouble> : public TrueType {
};
template<>
struct PrimitiveConversions::Translate<jfloat> : public TrueType {
struct PrimitiveConversions::Translate<jfloat> : public std::true_type {
typedef float Value;
typedef int32_t Decayed;

View File

@ -95,6 +95,51 @@ TEST(PrimitiveConversionsTest, round_trip_int) {
EXPECT_EQ(ufive, PrimitiveConversions::cast<uint>(PrimitiveConversions::cast<UI>(ufive)));
}
TEST(PrimitiveConversionsTest, round_trip_int_constexpr) {
constexpr int sfive = 5;
constexpr int mfive = -5;
constexpr uint ufive = 5u;
typedef PrimitiveConversionsTestSupport::Signed<int>::type SI;
typedef PrimitiveConversionsTestSupport::Unsigned<int>::type UI;
{
constexpr SI i = PrimitiveConversions::cast<SI>(sfive);
constexpr int r = PrimitiveConversions::cast<int>(i);
EXPECT_EQ(sfive, r);
}
{
constexpr UI i = PrimitiveConversions::cast<UI>(sfive);
constexpr int r = PrimitiveConversions::cast<int>(i);
EXPECT_EQ(sfive, r);
}
{
constexpr SI i = PrimitiveConversions::cast<SI>(mfive);
constexpr int r = PrimitiveConversions::cast<int>(i);
EXPECT_EQ(mfive, r);
}
{
constexpr UI i = PrimitiveConversions::cast<UI>(mfive);
constexpr int r = PrimitiveConversions::cast<int>(i);
EXPECT_EQ(mfive, r);
}
{
constexpr SI i = PrimitiveConversions::cast<SI>(ufive);
constexpr uint r = PrimitiveConversions::cast<uint>(i);
EXPECT_EQ(ufive, r);
}
{
constexpr UI i = PrimitiveConversions::cast<UI>(ufive);
constexpr uint r = PrimitiveConversions::cast<uint>(i);
EXPECT_EQ(ufive, r);
}
}
TEST(PrimitiveConversionsTest, round_trip_float) {
float ffive = 5.0f;
double dfive = 5.0;

View File

@ -23,9 +23,10 @@
#include "precompiled.hpp"
#include "runtime/os.hpp"
#include "unittest.hpp"
#include "utilities/align.hpp"
#include "utilities/globalDefinitions.hpp"
#include <type_traits>
#include "unittest.hpp"
static ::testing::AssertionResult testPageAddress(
const char* expected_addr_expr,
@ -192,7 +193,7 @@ TEST(globalDefinitions, byte_size_in_exact_unit) {
#define EXPECT_EQ_LOG2(fn, type) \
{ \
int limit = sizeof (type) * BitsPerByte; \
if (IsSigned<type>::value) { \
if (std::is_signed<type>::value) { \
EXPECT_EQ(limit - 1, fn(std::numeric_limits<type>::min())); \
EXPECT_EQ(limit - 1, fn((type)-1)); \
limit--; \