Skip to content

Commit

Permalink
Simplify endian conversion functions in std.bitmanip.
Browse files Browse the repository at this point in the history
They're overly complex (e.g. testing for the native endianness of the
machine when that's actually completely unnecessary), and they use
unions in a manner that is undefined behavior in C/C++ (since they write
to one field and then read from another). I don't see any mention of
whether that behavior is defined in D in D's spec, but it's probably
undefined in D given that it's undefined in C/C++. Either way, it's an
overly complex solution.

This solution gets rid of all of the endian version checks in
std.bitmanip, and it allows the implementations of the endian conversion
functions to be the same between CTFE and runtime.
  • Loading branch information
jmdavis committed Sep 24, 2023
1 parent 0c8e5e5 commit 4707c4a
Showing 1 changed file with 89 additions and 147 deletions.
236 changes: 89 additions & 147 deletions std/bitmanip.d
Original file line number Diff line number Diff line change
Expand Up @@ -2941,58 +2941,6 @@ if (isIntegral!T || isSomeChar!T || isBoolean!T)
}


private union EndianSwapper(T)
if (canSwapEndianness!T)
{
T value;
ubyte[T.sizeof] array;

static if (is(immutable FloatingPointTypeOf!(T) == immutable float))
uint intValue;
else static if (is(immutable FloatingPointTypeOf!(T) == immutable double))
ulong intValue;

}

// Can't use EndianSwapper union during CTFE.
private auto ctfeRead(T)(const ubyte[T.sizeof] array)
if (__traits(isIntegral, T))
{
Unqual!T result;
version (LittleEndian)
foreach_reverse (b; array)
result = cast(Unqual!T) ((result << 8) | b);
else
foreach (b; array)
result = cast(Unqual!T) ((result << 8) | b);
return cast(T) result;
}

// Can't use EndianSwapper union during CTFE.
private auto ctfeBytes(T)(const T value)
if (__traits(isIntegral, T))
{
ubyte[T.sizeof] result;
Unqual!T tmp = value;
version (LittleEndian)
{
foreach (i; 0 .. T.sizeof)
{
result[i] = cast(ubyte) tmp;
tmp = cast(Unqual!T) (tmp >>> 8);
}
}
else
{
foreach_reverse (i; 0 .. T.sizeof)
{
result[i] = cast(ubyte) tmp;
tmp = cast(Unqual!T) (tmp >>> 8);
}
}
return result;
}

/++
Converts the given value from the native endianness to big endian and
returns it as a `ubyte[n]` where `n` is the size of the given type.
Expand All @@ -3009,10 +2957,18 @@ if (__traits(isIntegral, T))
auto nativeToBigEndian(T)(const T val) @safe pure nothrow @nogc
if (canSwapEndianness!T)
{
version (LittleEndian)
return nativeToEndianImpl!true(val);
static if (isFloatOrDouble!T)
return nativeToBigEndian(*cast(const UnsignedOfSize!(T.sizeof)*) &val);
else
return nativeToEndianImpl!false(val);
{
enum len = T.sizeof;
ubyte[len] retval;

static foreach (i; 0 .. len)
retval[i] = cast(ubyte)(val >> (len - i - 1) * 8);

return retval;
}
}

///
Expand All @@ -3039,26 +2995,6 @@ if (canSwapEndianness!T)
assert(cd == bigEndianToNative!double(swappedCD));
}

private auto nativeToEndianImpl(bool swap, T)(const T val) @safe pure nothrow @nogc
if (__traits(isIntegral, T))
{
if (!__ctfe)
{
static if (swap)
return EndianSwapper!T(swapEndian(val)).array;
else
return EndianSwapper!T(val).array;
}
else
{
// Can't use EndianSwapper in CTFE.
static if (swap)
return ctfeBytes(swapEndian(val));
else
return ctfeBytes(val);
}
}

@safe unittest
{
import std.meta;
Expand Down Expand Up @@ -3148,10 +3084,22 @@ if (__traits(isIntegral, T))
T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
if (canSwapEndianness!T && n == T.sizeof)
{
version (LittleEndian)
return endianToNativeImpl!(true, T, n)(val);
static if (isFloatOrDouble!T)
{
auto retval = bigEndianToNative!(UnsignedOfSize!(T.sizeof))(val);
return *cast(const T*) &retval;
}
else
return endianToNativeImpl!(false, T, n)(val);
{
enum len = T.sizeof;
alias U = UnsignedOfSize!len;
U retval;

static foreach (i; 0 .. len)
retval |= (cast(U) val[i]) << (len - i - 1) * 8;

return cast(T) retval;
}
}

///
Expand All @@ -3166,6 +3114,7 @@ if (canSwapEndianness!T && n == T.sizeof)
assert(c == bigEndianToNative!dchar(swappedC));
}


/++
Converts the given value from the native endianness to little endian and
returns it as a `ubyte[n]` where `n` is the size of the given type.
Expand All @@ -3178,10 +3127,18 @@ if (canSwapEndianness!T && n == T.sizeof)
auto nativeToLittleEndian(T)(const T val) @safe pure nothrow @nogc
if (canSwapEndianness!T)
{
version (BigEndian)
return nativeToEndianImpl!true(val);
static if (isFloatOrDouble!T)
return nativeToLittleEndian(*cast(const UnsignedOfSize!(T.sizeof)*)&val);
else
return nativeToEndianImpl!false(val);
{
enum len = T.sizeof;
ubyte[len] retval;

static foreach (i; 0 .. len)
retval[i] = cast(ubyte)(val >> i * 8);

return retval;
}
}

///
Expand Down Expand Up @@ -3258,10 +3215,22 @@ if (canSwapEndianness!T)
T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
if (canSwapEndianness!T && n == T.sizeof)
{
version (BigEndian)
return endianToNativeImpl!(true, T, n)(val);
static if (isFloatOrDouble!T)
{
auto retval = littleEndianToNative!(UnsignedOfSize!(T.sizeof))(val);
return *cast(const T*) &retval;
}
else
return endianToNativeImpl!(false, T, n)(val);
{
enum len = T.sizeof;
alias U = UnsignedOfSize!len;
U retval;

static foreach (i; 0 .. len)
retval |= (cast(U) val[i]) << i * 8;

return cast(T) retval;
}
}

///
Expand All @@ -3276,69 +3245,6 @@ if (canSwapEndianness!T && n == T.sizeof)
assert(c == littleEndianToNative!dchar(swappedC));
}

private T endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @nogc nothrow pure @safe
if (__traits(isIntegral, T) && n == T.sizeof)
{
if (!__ctfe)
{
EndianSwapper!T es = { array: val };
static if (swap)
return swapEndian(es.value);
else
return es.value;
}
else
{
static if (swap)
return swapEndian(ctfeRead!T(val));
else
return ctfeRead!T(val);
}
}

private auto nativeToEndianImpl(bool swap, T)(const T val) @trusted pure nothrow @nogc
if (isFloatOrDouble!T)
{
if (!__ctfe)
{
EndianSwapper!T es = EndianSwapper!T(val);
static if (swap)
es.intValue = swapEndian(es.intValue);
return es.array;
}
else
{
static if (T.sizeof == 4)
uint intValue = *cast(const uint*) &val;
else static if (T.sizeof == 8)
ulong intValue = *cast(const ulong*) & val;
static if (swap)
intValue = swapEndian(intValue);
return ctfeBytes(intValue);
}
}

private auto endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @trusted pure nothrow @nogc
if (isFloatOrDouble!T && n == T.sizeof)
{
if (!__ctfe)
{
EndianSwapper!T es = { array: val };
static if (swap)
es.intValue = swapEndian(es.intValue);
return es.value;
}
else
{
static if (n == 4)
uint x = ctfeRead!uint(val);
else static if (n == 8)
ulong x = ctfeRead!ulong(val);
static if (swap)
x = swapEndian(x);
return *cast(T*) &x;
}
}

private template isFloatOrDouble(T)
{
Expand Down Expand Up @@ -3401,6 +3307,42 @@ private template canSwapEndianness(T)
}
}

private template UnsignedOfSize(size_t n)
{
static if (n == 8)
alias UnsignedOfSize = ulong;
else static if (n == 4)
alias UnsignedOfSize = uint;
else static if (n == 2)
alias UnsignedOfSize = ushort;
else static if (n == 1)
alias UnsignedOfSize = ubyte;
else
alias UnsignedOfSize = void;
}

@safe unittest
{
static assert(is(UnsignedOfSize!(byte.sizeof) == ubyte));
static assert(is(UnsignedOfSize!(ubyte.sizeof) == ubyte));
static assert(is(UnsignedOfSize!(short.sizeof) == ushort));
static assert(is(UnsignedOfSize!(ushort.sizeof) == ushort));
static assert(is(UnsignedOfSize!(int.sizeof) == uint));
static assert(is(UnsignedOfSize!(uint.sizeof) == uint));
static assert(is(UnsignedOfSize!(long.sizeof) == ulong));
static assert(is(UnsignedOfSize!(ulong.sizeof) == ulong));

static assert(is(UnsignedOfSize!(bool.sizeof) == ubyte));
static assert(is(UnsignedOfSize!(char.sizeof) == ubyte));
static assert(is(UnsignedOfSize!(wchar.sizeof) == ushort));
static assert(is(UnsignedOfSize!(dchar.sizeof) == uint));

static assert(is(UnsignedOfSize!(float.sizeof) == uint));
static assert(is(UnsignedOfSize!(double.sizeof) == ulong));

static assert(is(UnsignedOfSize!10 == void));
}

/++
Takes a range of `ubyte`s and converts the first `T.sizeof` bytes to
`T`. The value returned is converted from the given endianness to the
Expand Down

0 comments on commit 4707c4a

Please sign in to comment.