Add software implementation of float16

This commit is contained in:
Nikita Lisitsa 2026-07-30 17:00:46 +03:00
parent e56242ca62
commit ceb5c8014c
5 changed files with 184 additions and 30 deletions

View file

@ -441,7 +441,7 @@ namespace pslang::interpreter
}
else if constexpr (std::is_same_v<H, types::half_float>)
{
return primitive_value(primitive_value_base<H>{{static_cast<float>(value.value)}});
return primitive_value(primitive_value_base<H>{types::half_float::from_float(static_cast<float>(value.value))});
}
else
{

View file

@ -152,6 +152,8 @@ namespace pslang::jit::macos_aarch64
{
lcontext.f16_constants[f16_literal->value.repr] = pcontext.storage.size();
push_bytes(f16_literal->value.repr);
// Ensure 4-byte alignment
push_bytes(std::uint16_t{0});
}
else if (auto f32_literal = std::get_if<ast::f32_literal>(&node.value))
{
@ -235,8 +237,8 @@ namespace pslang::jit::macos_aarch64
{
auto offset = lcontext.f16_constants.at(node.value.repr);
std::int32_t current = pcontext.storage.size();
builder.ldr_fp_pc(0, 0, (offset - current) / 4);
builder.fcvt(0, 0b10, 0, 0b01);
builder.adr(0, (offset - current) / 4);
builder.ldr_fp(0, 1, 0, 0);
}
void operator()(ast::f32_literal const & node)

View file

@ -48,7 +48,7 @@ template <typename T>
T value;
#ifdef __clang__
if constexpr (std::is_same_v<T, ::pslang::types::half_float>)
value = {float(std::atof(str.data()))};
value = value.from_float(float(std::atof(str.data())));
else if constexpr (std::is_floating_point_v<T>)
value = std::atof(str.data());
else if constexpr (std::is_signed_v<T>)

View file

@ -13,35 +13,20 @@ namespace pslang::types
struct half_float
{
float repr = 0.f;
using repr_type = std::uint16_t;
repr_type repr;
friend bool operator == (half_float const &, half_float const &) = default;
friend auto operator <=> (half_float const &, half_float const &) = default;
static float to_float(half_float value);
static half_float from_float(float value);
};
inline half_float operator - (half_float f)
{
return {-f.repr};
}
bool operator == (half_float const &, half_float const &);
std::partial_ordering operator <=> (half_float const &, half_float const &);
inline half_float operator + (half_float f1, half_float f2)
{
return {f1.repr + f2.repr};
}
inline half_float operator - (half_float f1, half_float f2)
{
return {f1.repr - f2.repr};
}
inline half_float operator * (half_float f1, half_float f2)
{
return {f1.repr * f2.repr};
}
inline half_float operator / (half_float f1, half_float f2)
{
return {f1.repr / f2.repr};
}
half_float operator - (half_float f);
half_float operator + (half_float f1, half_float f2);
half_float operator - (half_float f1, half_float f2);
half_float operator * (half_float f1, half_float f2);
half_float operator / (half_float f1, half_float f2);
}

View file

@ -0,0 +1,167 @@
#include <pslang/types/half_float.hpp>
#include <bit>
#include <cstring>
namespace pslang::types
{
using repr_type = half_float::repr_type;
namespace
{
constexpr repr_type SIGN_BIT = 0x8000u;
constexpr repr_type EXPONENT_MASK = 0x7c00u;
constexpr repr_type MANTISSA_MASK = 0x03ff;
bool is_nan(half_float value)
{
return ((value.repr & EXPONENT_MASK) == EXPONENT_MASK) && ((value.repr & MANTISSA_MASK) != 0);
}
bool is_zero(half_float value)
{
return (value.repr & (~SIGN_BIT)) == 0;
}
repr_type ordered(half_float value)
{
return ((value.repr & SIGN_BIT) != 0) ? ~value.repr : (value.repr ^ SIGN_BIT);
}
}
bool operator == (half_float const & lhs, half_float const & rhs)
{
if (is_nan(lhs) || is_nan(rhs)) [[unlikely]]
return false;
if (is_zero(lhs) && is_zero(rhs))
return true;
return lhs == rhs;
}
std::partial_ordering operator <=> (half_float const & lhs, half_float const & rhs)
{
if (is_nan(lhs) || is_nan(rhs)) [[unlikely]]
return std::partial_ordering::unordered;
if (is_zero(lhs) && is_zero(rhs))
return std::partial_ordering::equivalent;
return ordered(lhs) <=> ordered(rhs);
}
float half_float::to_float(half_float value)
{
std::uint32_t sign = (value.repr & SIGN_BIT) >> 15;
std::uint32_t exponent = (value.repr & EXPONENT_MASK) >> 10;
std::uint32_t mantissa = value.repr & MANTISSA_MASK;
if (exponent == 0)
{
// Zero
if (mantissa == 0) [[likely]]
return std::bit_cast<float>(sign << 31);
// Subnormal
int shift = -14;
while ((mantissa & 0x400) == 0)
{
mantissa <<= 1;
--shift;
}
mantissa &= 0x3ff;
return std::bit_cast<float>((sign << 31) | ((shift + 127) << 23) | (mantissa << 13));
}
// Extend exponent for infinity & NaNs to all ones
if (exponent == 0x1fu) [[unlikely]]
exponent = 0xffu;
// Shifting NaN mantissa bits is mostly useless,
// but at least we preserve quiet vs signaling NaNs
return std::bit_cast<float>((sign << 31) | ((exponent - 15 + 127) << 23) | (mantissa << 13));
}
half_float half_float::from_float(float value)
{
uint32_t repr;
std::memcpy(&repr, &value, sizeof(repr));
std::uint32_t sign = (repr >> 16) & 0x8000;
std::uint32_t mantissa = repr & 0x007FFFFF;
std::int32_t exponent = ((repr >> 23) & 0xFF) - 127 + 15;
if (exponent <= 0)
{
if (exponent < -10)
return {repr_type(sign)};
mantissa |= 0x00800000;
uint32_t t = mantissa >> (1 - exponent);
// Rounding
if (t & 0x00001000)
t += 0x00002000;
return {repr_type(sign | (t >> 13))};
}
if (exponent >= 31)
{
// Infinities
if (mantissa == 0)
return {repr_type(sign | 0x7C00)};
// NaNs
return {repr_type(sign | 0x7E00)};
}
// Rounding
mantissa += 0x00001000;
if (mantissa & 0x00800000)
{
mantissa = 0;
exponent++;
}
if (exponent >= 31)
return {repr_type(sign | 0x7C00)};
return {repr_type(sign | (exponent << 10) | (mantissa >> 13))};
}
inline half_float operator - (half_float f)
{
return {repr_type(f.repr ^ SIGN_BIT)};
}
inline half_float operator + (half_float f1, half_float f2)
{
return {half_float::from_float(half_float::to_float(f1) + half_float::to_float(f2))};
}
inline half_float operator - (half_float f1, half_float f2)
{
return {half_float::from_float(half_float::to_float(f1) - half_float::to_float(f2))};
}
inline half_float operator * (half_float f1, half_float f2)
{
return {half_float::from_float(half_float::to_float(f1) * half_float::to_float(f2))};
}
inline half_float operator / (half_float f1, half_float f2)
{
return {half_float::from_float(half_float::to_float(f1) / half_float::to_float(f2))};
}
}