/* Copyright 2024 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #ifndef TENSORFLOW_LITE_EXPERIMENTAL_SHLO_F16_H_ #define TENSORFLOW_LITE_EXPERIMENTAL_SHLO_F16_H_ #include #include #include "fp16.h" // from @FP16 // IWYU pragma: keep, used with no builtin float16 // Use __FLT16_MAX__ to determine whether _Float16 is builtin #if defined(__FLT16_MAX__) && !SHLO_REF_EMULATE_F16 #define SHLO_REF_HAS_BUILTIN_FLOAT16 1 #endif namespace shlo_ref { class alignas(uint16_t) F16 { public: F16() = default; template >> // Allow implicit conversions from types convertible to float. // NOLINTNEXTLINE(google-explicit-constructor) F16(T x); // Tagged constructor to allow construction from bits struct bitcast_construct_t {}; explicit F16(bitcast_construct_t, uint16_t bits) : bits_(bits) {} // Allow implicit conversions to float. // NOLINTNEXTLINE(google-explicit-constructor) operator float() const; explicit operator bool() const; F16& operator=(float x) { return *this = static_cast(x); } #ifdef SHLO_REF_HAS_BUILTIN_FLOAT16 #define SHLO_REF_DEFINE_BINARY_OP(OP) \ friend F16 operator OP(F16 x, F16 y) { return x.native_ OP y.native_; } \ \ template >> \ friend auto operator OP(F16 x, T y) { \ return x.native_ OP y; \ } \ \ template >> \ friend auto operator OP(T x, F16 y) { \ return x OP y.native_; \ } #define SHLO_REF_DEFINE_BINARY_ASSIGN_OP(OP) \ SHLO_REF_DEFINE_BINARY_OP(OP); \ friend F16& operator OP##=(F16 & x, F16 y) { \ x.native_ OP## = y.native_; \ return x; \ } \ \ template >> \ friend F16& operator OP##=(F16 & x, T y) { \ x.native_ OP## = y; \ return x; \ } #else // !SHLO_REF_HAS_BUILTIN_FLOAT16 #define SHLO_REF_DEFINE_BINARY_OP(OP) \ friend F16 operator OP(F16 x, F16 y) { \ return F16(static_cast(x) OP static_cast(y)); \ } \ \ template > \ friend C operator OP(F16 x, T y) { \ return static_cast(static_cast(x) OP static_cast(y)); \ } \ \ template > \ friend C operator OP(T x, F16 y) { \ return static_cast(static_cast(x) OP static_cast(y)); \ } #define SHLO_REF_DEFINE_BINARY_ASSIGN_OP(OP) \ SHLO_REF_DEFINE_BINARY_OP(OP); \ friend F16& operator OP##=(F16 & x, F16 y) { \ return x = F16(static_cast(x) OP static_cast(y)); \ } \ \ template >> \ friend F16& operator OP##=(F16 & x, T y) { \ return x = static_cast(x) OP y; \ } #endif // SHLO_REF_HAS_BUILTIN_FLOAT16 friend F16 operator+(F16 x); friend F16 operator-(F16 x); friend F16& operator++(F16& x); friend F16 operator++(F16& x, int); friend F16& operator--(F16& x); friend F16 operator--(F16& x, int); SHLO_REF_DEFINE_BINARY_ASSIGN_OP(+); SHLO_REF_DEFINE_BINARY_ASSIGN_OP(-); SHLO_REF_DEFINE_BINARY_ASSIGN_OP(*); SHLO_REF_DEFINE_BINARY_ASSIGN_OP(/); SHLO_REF_DEFINE_BINARY_OP(<); SHLO_REF_DEFINE_BINARY_OP(<=); SHLO_REF_DEFINE_BINARY_OP(>); SHLO_REF_DEFINE_BINARY_OP(>=); SHLO_REF_DEFINE_BINARY_OP(==); SHLO_REF_DEFINE_BINARY_OP(!=); #undef SHLO_REF_DEFINE_BINARY_ASSIGN_OP #undef SHLO_REF_DEFINE_BINARY_OP private: union { #ifdef SHLO_REF_HAS_BUILTIN_FLOAT16 _Float16 native_; #endif uint16_t bits_; }; }; namespace detail { template struct F16CommonType {}; template struct F16CommonType>> { using type = F16; }; template struct F16CommonType>> { using type = T; }; template struct F16CommonType && std::is_convertible_v>> { using type = float; }; template struct F16CommonType && !std::is_convertible_v && std::is_convertible_v>> { using type = double; }; } // namespace detail } // namespace shlo_ref namespace std { template <> struct common_type { using type = shlo_ref::F16; }; template struct common_type : shlo_ref::detail::F16CommonType {}; template struct common_type : shlo_ref::detail::F16CommonType {}; } // namespace std namespace shlo_ref { static_assert(sizeof(F16) == sizeof(uint16_t)); static_assert(alignof(F16) == alignof(uint16_t)); #ifdef SHLO_REF_HAS_BUILTIN_FLOAT16 template F16::F16(T x) : native_(static_cast<_Float16>(static_cast(x))) {} inline F16::operator float() const { return native_; } inline F16::operator bool() const { return native_; } inline F16 operator+(F16 x) { return x.native_; } inline F16 operator-(F16 x) { return -x.native_; } inline F16& operator++(F16& x) { return x += 1; } inline F16 operator++(F16& x, int) { return x.native_++; } inline F16& operator--(F16& x) { return x -= 1; } inline F16 operator--(F16& x, int) { return x.native_--; } #else // !SHLO_REF_HAS_BUILTIN_FLOAT16 template inline F16::F16(T x) : bits_(fp16_ieee_from_fp32_value(static_cast(x))) {} inline F16::operator float() const { return fp16_ieee_to_fp32_value(bits_); } inline F16::operator bool() const { return bits_; } inline F16 operator-(F16 x) { return F16(-static_cast(x)); } inline F16 operator+(F16 x) { return F16(static_cast(x)); } inline F16& operator++(F16& x) { return x += 1; } inline F16 operator++(F16& x, int) { const F16 y = x; ++x; return y; } inline F16& operator--(F16& x) { return x -= 1; } inline F16 operator--(F16& x, int) { const F16 y = x; --x; return y; } #endif } // namespace shlo_ref #endif // TENSORFLOW_LITE_EXPERIMENTAL_SHLO_F16_H_