Skip to content
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

Simplify endian conversion functions in std.bitmanip. #8822

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 105 additions & 151 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 @@ -3006,13 +2954,21 @@ if (__traits(isIntegral, T))
and therefore could vary from machine to machine (which could make it
unusable if you tried to transfer it to another machine).
+/
auto nativeToBigEndian(T)(const T val) @safe pure nothrow @nogc
auto nativeToBigEndian(T)(const T val) @trusted 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 @@ -3145,13 +3081,25 @@ if (__traits(isIntegral, T))
because the FPU will mess up any swapped floating point values. So, you
can't actually have swapped floating point values as floating point values).
+/
T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
T bigEndianToNative(T, size_t n)(ubyte[n] val) @trusted 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 @@ -3175,13 +3124,21 @@ if (canSwapEndianness!T && n == T.sizeof)
because the FPU will mess up any swapped floating point values. So, you
can't actually have swapped floating point values as floating point values).
+/
auto nativeToLittleEndian(T)(const T val) @safe pure nothrow @nogc
auto nativeToLittleEndian(T)(const T val) @trusted 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 All @@ -3191,9 +3148,21 @@ if (canSwapEndianness!T)
ubyte[4] swappedI = nativeToLittleEndian(i);
assert(i == littleEndianToNative!int(swappedI));

float f = 123.45f;
ubyte[4] swappedF = nativeToLittleEndian(f);
assert(f == littleEndianToNative!float(swappedF));

const float cf = 123.45f;
ubyte[4] swappedCF = nativeToLittleEndian(cf);
assert(cf == littleEndianToNative!float(swappedCF));

double d = 123.45;
ubyte[8] swappedD = nativeToLittleEndian(d);
assert(d == littleEndianToNative!double(swappedD));

const double cd = 123.45;
ubyte[8] swappedCD = nativeToLittleEndian(cd);
assert(cd == littleEndianToNative!double(swappedCD));
}

@safe unittest
Expand Down Expand Up @@ -3255,13 +3224,25 @@ if (canSwapEndianness!T)
and therefore could vary from machine to machine (which could make it
unusable if you tried to transfer it to another machine).
+/
T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
T littleEndianToNative(T, size_t n)(ubyte[n] val) @trusted 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 +3257,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 +3319,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));
}

jmdavis marked this conversation as resolved.
Show resolved Hide resolved
/++
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