Skip to content

Commit b6245b4

Browse files
authored
Simplify endian conversion functions in std.bitmanip. (#8822)
* Simplify endian conversion functions in std.bitmanip. 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. * Add additional tests for nativeToLittleEndian.
1 parent 231ae8b commit b6245b4

File tree

1 file changed

+104
-152
lines changed

1 file changed

+104
-152
lines changed

std/bitmanip.d

Lines changed: 104 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -2945,58 +2945,6 @@ if (isIntegral!T || isSomeChar!T || isBoolean!T)
29452945
}
29462946

29472947

2948-
private union EndianSwapper(T)
2949-
if (canSwapEndianness!T)
2950-
{
2951-
T value;
2952-
ubyte[T.sizeof] array;
2953-
2954-
static if (is(immutable FloatingPointTypeOf!(T) == immutable float))
2955-
uint intValue;
2956-
else static if (is(immutable FloatingPointTypeOf!(T) == immutable double))
2957-
ulong intValue;
2958-
2959-
}
2960-
2961-
// Can't use EndianSwapper union during CTFE.
2962-
private auto ctfeRead(T)(const ubyte[T.sizeof] array)
2963-
if (__traits(isIntegral, T))
2964-
{
2965-
Unqual!T result;
2966-
version (LittleEndian)
2967-
foreach_reverse (b; array)
2968-
result = cast() cast(T) ((result << 8) | b);
2969-
else
2970-
foreach (b; array)
2971-
result = cast() cast(T) ((result << 8) | b);
2972-
return cast(T) result;
2973-
}
2974-
2975-
// Can't use EndianSwapper union during CTFE.
2976-
private auto ctfeBytes(T)(const T value)
2977-
if (__traits(isIntegral, T))
2978-
{
2979-
ubyte[T.sizeof] result;
2980-
Unqual!T tmp = value;
2981-
version (LittleEndian)
2982-
{
2983-
foreach (i; 0 .. T.sizeof)
2984-
{
2985-
result[i] = cast(ubyte) tmp;
2986-
tmp = cast() cast(T) (tmp >>> 8);
2987-
}
2988-
}
2989-
else
2990-
{
2991-
foreach_reverse (i; 0 .. T.sizeof)
2992-
{
2993-
result[i] = cast(ubyte) tmp;
2994-
tmp = cast()(T) (tmp >>> 8);
2995-
}
2996-
}
2997-
return result;
2998-
}
2999-
30002948
/++
30012949
Converts the given value from the native endianness to big endian and
30022950
returns it as a `ubyte[n]` where `n` is the size of the given type.
@@ -3010,13 +2958,21 @@ if (__traits(isIntegral, T))
30102958
and therefore could vary from machine to machine (which could make it
30112959
unusable if you tried to transfer it to another machine).
30122960
+/
3013-
auto nativeToBigEndian(T)(const T val) @safe pure nothrow @nogc
2961+
auto nativeToBigEndian(T)(const T val) @trusted pure nothrow @nogc
30142962
if (canSwapEndianness!T)
30152963
{
3016-
version (LittleEndian)
3017-
return nativeToEndianImpl!true(val);
2964+
static if (isFloatOrDouble!T)
2965+
return nativeToBigEndian(*cast(const UnsignedOfSize!(T.sizeof)*) &val);
30182966
else
3019-
return nativeToEndianImpl!false(val);
2967+
{
2968+
enum len = T.sizeof;
2969+
ubyte[len] retval;
2970+
2971+
static foreach (i; 0 .. len)
2972+
retval[i] = cast(ubyte)(val >> (len - i - 1) * 8);
2973+
2974+
return retval;
2975+
}
30202976
}
30212977

30222978
///
@@ -3043,26 +2999,6 @@ if (canSwapEndianness!T)
30432999
assert(cd == bigEndianToNative!double(swappedCD));
30443000
}
30453001

3046-
private auto nativeToEndianImpl(bool swap, T)(const T val) @safe pure nothrow @nogc
3047-
if (__traits(isIntegral, T))
3048-
{
3049-
if (!__ctfe)
3050-
{
3051-
static if (swap)
3052-
return EndianSwapper!T(swapEndian(val)).array;
3053-
else
3054-
return EndianSwapper!T(val).array;
3055-
}
3056-
else
3057-
{
3058-
// Can't use EndianSwapper in CTFE.
3059-
static if (swap)
3060-
return ctfeBytes(swapEndian(val));
3061-
else
3062-
return ctfeBytes(val);
3063-
}
3064-
}
3065-
30663002
@safe unittest
30673003
{
30683004
import std.meta;
@@ -3149,13 +3085,25 @@ if (__traits(isIntegral, T))
31493085
because the FPU will mess up any swapped floating point values. So, you
31503086
can't actually have swapped floating point values as floating point values).
31513087
+/
3152-
T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
3088+
T bigEndianToNative(T, size_t n)(ubyte[n] val) @trusted pure nothrow @nogc
31533089
if (canSwapEndianness!T && n == T.sizeof)
31543090
{
3155-
version (LittleEndian)
3156-
return endianToNativeImpl!(true, T, n)(val);
3091+
static if (isFloatOrDouble!T)
3092+
{
3093+
auto retval = bigEndianToNative!(UnsignedOfSize!(T.sizeof))(val);
3094+
return *cast(const T*) &retval;
3095+
}
31573096
else
3158-
return endianToNativeImpl!(false, T, n)(val);
3097+
{
3098+
enum len = T.sizeof;
3099+
alias U = UnsignedOfSize!len;
3100+
U retval;
3101+
3102+
static foreach (i; 0 .. len)
3103+
retval |= (cast(U) val[i]) << (len - i - 1) * 8;
3104+
3105+
return cast(T) retval;
3106+
}
31593107
}
31603108

31613109
///
@@ -3179,13 +3127,21 @@ if (canSwapEndianness!T && n == T.sizeof)
31793127
because the FPU will mess up any swapped floating point values. So, you
31803128
can't actually have swapped floating point values as floating point values).
31813129
+/
3182-
auto nativeToLittleEndian(T)(const T val) @safe pure nothrow @nogc
3130+
auto nativeToLittleEndian(T)(const T val) @trusted pure nothrow @nogc
31833131
if (canSwapEndianness!T)
31843132
{
3185-
version (BigEndian)
3186-
return nativeToEndianImpl!true(val);
3133+
static if (isFloatOrDouble!T)
3134+
return nativeToLittleEndian(*cast(const UnsignedOfSize!(T.sizeof)*) &val);
31873135
else
3188-
return nativeToEndianImpl!false(val);
3136+
{
3137+
enum len = T.sizeof;
3138+
ubyte[len] retval;
3139+
3140+
static foreach (i; 0 .. len)
3141+
retval[i] = cast(ubyte)(val >> i * 8);
3142+
3143+
return retval;
3144+
}
31893145
}
31903146

31913147
///
@@ -3195,9 +3151,21 @@ if (canSwapEndianness!T)
31953151
ubyte[4] swappedI = nativeToLittleEndian(i);
31963152
assert(i == littleEndianToNative!int(swappedI));
31973153

3154+
float f = 123.45f;
3155+
ubyte[4] swappedF = nativeToLittleEndian(f);
3156+
assert(f == littleEndianToNative!float(swappedF));
3157+
3158+
const float cf = 123.45f;
3159+
ubyte[4] swappedCF = nativeToLittleEndian(cf);
3160+
assert(cf == littleEndianToNative!float(swappedCF));
3161+
31983162
double d = 123.45;
31993163
ubyte[8] swappedD = nativeToLittleEndian(d);
32003164
assert(d == littleEndianToNative!double(swappedD));
3165+
3166+
const double cd = 123.45;
3167+
ubyte[8] swappedCD = nativeToLittleEndian(cd);
3168+
assert(cd == littleEndianToNative!double(swappedCD));
32013169
}
32023170

32033171
@safe unittest
@@ -3259,13 +3227,25 @@ if (canSwapEndianness!T)
32593227
and therefore could vary from machine to machine (which could make it
32603228
unusable if you tried to transfer it to another machine).
32613229
+/
3262-
T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
3230+
T littleEndianToNative(T, size_t n)(ubyte[n] val) @trusted pure nothrow @nogc
32633231
if (canSwapEndianness!T && n == T.sizeof)
32643232
{
3265-
version (BigEndian)
3266-
return endianToNativeImpl!(true, T, n)(val);
3233+
static if (isFloatOrDouble!T)
3234+
{
3235+
auto retval = littleEndianToNative!(UnsignedOfSize!(T.sizeof))(val);
3236+
return *cast(const T*) &retval;
3237+
}
32673238
else
3268-
return endianToNativeImpl!(false, T, n)(val);
3239+
{
3240+
enum len = T.sizeof;
3241+
alias U = UnsignedOfSize!len;
3242+
U retval;
3243+
3244+
static foreach (i; 0 .. len)
3245+
retval |= (cast(U) val[i]) << i * 8;
3246+
3247+
return cast(T) retval;
3248+
}
32693249
}
32703250

32713251
///
@@ -3280,70 +3260,6 @@ if (canSwapEndianness!T && n == T.sizeof)
32803260
assert(c == littleEndianToNative!dchar(swappedC));
32813261
}
32823262

3283-
private T endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @nogc nothrow pure @trusted
3284-
if (__traits(isIntegral, T) && n == T.sizeof)
3285-
{
3286-
if (!__ctfe)
3287-
{
3288-
EndianSwapper!T es = { array: val };
3289-
static if (swap)
3290-
return swapEndian(es.value);
3291-
else
3292-
return es.value;
3293-
}
3294-
else
3295-
{
3296-
static if (swap)
3297-
return swapEndian(ctfeRead!T(val));
3298-
else
3299-
return ctfeRead!T(val);
3300-
}
3301-
}
3302-
3303-
private auto nativeToEndianImpl(bool swap, T)(const T val) @trusted pure nothrow @nogc
3304-
if (isFloatOrDouble!T)
3305-
{
3306-
if (!__ctfe)
3307-
{
3308-
EndianSwapper!T es = EndianSwapper!T(val);
3309-
static if (swap)
3310-
es.intValue = swapEndian(es.intValue);
3311-
return es.array;
3312-
}
3313-
else
3314-
{
3315-
static if (T.sizeof == 4)
3316-
uint intValue = *cast(const uint*) &val;
3317-
else static if (T.sizeof == 8)
3318-
ulong intValue = *cast(const ulong*) & val;
3319-
static if (swap)
3320-
intValue = swapEndian(intValue);
3321-
return ctfeBytes(intValue);
3322-
}
3323-
}
3324-
3325-
private auto endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @trusted pure nothrow @nogc
3326-
if (isFloatOrDouble!T && n == T.sizeof)
3327-
{
3328-
if (!__ctfe)
3329-
{
3330-
EndianSwapper!T es = { array: val };
3331-
static if (swap)
3332-
es.intValue = swapEndian(es.intValue);
3333-
return es.value;
3334-
}
3335-
else
3336-
{
3337-
static if (n == 4)
3338-
uint x = ctfeRead!uint(val);
3339-
else static if (n == 8)
3340-
ulong x = ctfeRead!ulong(val);
3341-
static if (swap)
3342-
x = swapEndian(x);
3343-
return *cast(T*) &x;
3344-
}
3345-
}
3346-
33473263
private template isFloatOrDouble(T)
33483264
{
33493265
enum isFloatOrDouble = isFloatingPoint!T &&
@@ -3405,6 +3321,42 @@ private template canSwapEndianness(T)
34053321
}
34063322
}
34073323

3324+
private template UnsignedOfSize(size_t n)
3325+
{
3326+
static if (n == 8)
3327+
alias UnsignedOfSize = ulong;
3328+
else static if (n == 4)
3329+
alias UnsignedOfSize = uint;
3330+
else static if (n == 2)
3331+
alias UnsignedOfSize = ushort;
3332+
else static if (n == 1)
3333+
alias UnsignedOfSize = ubyte;
3334+
else
3335+
alias UnsignedOfSize = void;
3336+
}
3337+
3338+
@safe unittest
3339+
{
3340+
static assert(is(UnsignedOfSize!(byte.sizeof) == ubyte));
3341+
static assert(is(UnsignedOfSize!(ubyte.sizeof) == ubyte));
3342+
static assert(is(UnsignedOfSize!(short.sizeof) == ushort));
3343+
static assert(is(UnsignedOfSize!(ushort.sizeof) == ushort));
3344+
static assert(is(UnsignedOfSize!(int.sizeof) == uint));
3345+
static assert(is(UnsignedOfSize!(uint.sizeof) == uint));
3346+
static assert(is(UnsignedOfSize!(long.sizeof) == ulong));
3347+
static assert(is(UnsignedOfSize!(ulong.sizeof) == ulong));
3348+
3349+
static assert(is(UnsignedOfSize!(bool.sizeof) == ubyte));
3350+
static assert(is(UnsignedOfSize!(char.sizeof) == ubyte));
3351+
static assert(is(UnsignedOfSize!(wchar.sizeof) == ushort));
3352+
static assert(is(UnsignedOfSize!(dchar.sizeof) == uint));
3353+
3354+
static assert(is(UnsignedOfSize!(float.sizeof) == uint));
3355+
static assert(is(UnsignedOfSize!(double.sizeof) == ulong));
3356+
3357+
static assert(is(UnsignedOfSize!10 == void));
3358+
}
3359+
34083360
/++
34093361
Takes a range of `ubyte`s and converts the first `T.sizeof` bytes to
34103362
`T`. The value returned is converted from the given endianness to the

0 commit comments

Comments
 (0)