view libphobos/libdruntime/core/internal/convert.d @ 158:494b0b89df80 default tip

...
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 25 May 2020 18:13:55 +0900
parents 1830386684a0
children
line wrap: on
line source

/**
 * Written in the D programming language.
 * This module provides functions to converting different values to const(ubyte)[]
 *
 * Copyright: Copyright Igor Stepanov 2013-2013.
 * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
 * Authors:   Igor Stepanov
 * Source: $(DRUNTIMESRC core/internal/_convert.d)
 */
module core.internal.convert;
import core.internal.traits : Unqual;

/+
A @nogc function can allocate memory during CTFE.
+/
@nogc nothrow pure @trusted
private ubyte[] ctfe_alloc()(size_t n)
{
    if (!__ctfe)
    {
        assert(0, "CTFE only");
    }
    else
    {
        static ubyte[] alloc(size_t x) nothrow pure
        {
            if (__ctfe) // Needed to prevent _d_newarray from appearing in compiled prorgam.
                return new ubyte[x];
            else
                assert(0);
        }
        return (cast(ubyte[] function(size_t) @nogc nothrow pure) &alloc)(n);
    }
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real) ||
                                        is(Unqual!T == ifloat) || is(Unqual!T == idouble) || is(Unqual!T == ireal))
{
    static const(ubyte)[] reverse_(const(ubyte)[] arr)
    {
        ubyte[] buff = ctfe_alloc(arr.length);
        foreach (k, v; arr)
        {
            buff[$-k-1] = v;
        }
        return buff;
    }
    if (__ctfe)
    {
        auto parsed = parse(val);

        ulong mantissa = parsed.mantissa;
        uint exp = parsed.exponent;
        uint sign = parsed.sign;

        ubyte[] buff = ctfe_alloc(T.sizeof);
        size_t off_bytes = 0;
        size_t off_bits  = 0;
        // Quadruples won't fit in one ulong, so check for that.
        enum mantissaMax = FloatTraits!T.MANTISSA < ulong.sizeof*8 ?
                           FloatTraits!T.MANTISSA : ulong.sizeof*8;

        for (; off_bytes < mantissaMax/8; ++off_bytes)
        {
            buff[off_bytes] = cast(ubyte)mantissa;
            mantissa >>= 8;
        }

        static if (floatFormat!T == FloatFormat.Quadruple)
        {
            ulong mantissa2 = parsed.mantissa2;
            off_bytes--; // go back one, since mantissa only stored data in 56
                         // bits, ie 7 bytes
            for (; off_bytes < FloatTraits!T.MANTISSA/8; ++off_bytes)
            {
                buff[off_bytes] = cast(ubyte)mantissa2;
                mantissa2 >>= 8;
            }
        }
        else
        {
            off_bits = FloatTraits!T.MANTISSA%8;
            buff[off_bytes] = cast(ubyte)mantissa;
        }

        for (size_t i=0; i<FloatTraits!T.EXPONENT/8; ++i)
        {
            ubyte cur_exp = cast(ubyte)exp;
            exp >>= 8;
            buff[off_bytes] |= (cur_exp << off_bits);
            ++off_bytes;
            buff[off_bytes] |= cur_exp >> 8 - off_bits;
        }


        exp <<= 8 - FloatTraits!T.EXPONENT%8 - 1;
        buff[off_bytes] |= exp;
        sign <<= 7;
        buff[off_bytes] |= sign;

        version (LittleEndian)
        {
            return buff;
        }
        else
        {
            return reverse_(buff);
        }
    }
    else
    {
        return (cast(const(ubyte)*)&val)[0 .. T.sizeof];
    }
}

@safe pure nothrow @nogc
private Float parse(bool is_denormalized = false, T)(T x) if (is(Unqual!T == ifloat) || is(Unqual!T == idouble) || is(Unqual!T == ireal))
{
    return parse(x.im);
}

@safe pure nothrow @nogc
private Float parse(bool is_denormalized = false, T:real)(T x_) if (floatFormat!T != FloatFormat.Real80)
{
    Unqual!T x = x_;
    static assert(floatFormat!T != FloatFormat.DoubleDouble,
           "doubledouble float format not supported in CTFE");
    if (x is cast(T)0.0) return FloatTraits!T.ZERO;
    if (x is cast(T)-0.0) return FloatTraits!T.NZERO;
    if (x is T.nan) return FloatTraits!T.NAN;
    if (x is -T.nan) return FloatTraits!T.NNAN;
    if (x is T.infinity || x > T.max) return FloatTraits!T.INF;
    if (x is -T.infinity || x < -T.max) return FloatTraits!T.NINF;

    uint sign = x < 0;
    x = sign ? -x : x;
    int e = binLog2(x);
    real x2 = x;
    uint exp = cast(uint)(e + (2^^(FloatTraits!T.EXPONENT-1) - 1));

    if (!exp)
    {
        if (is_denormalized)
            return Float(0, 0, sign);
        else
            return denormalizedMantissa(x, sign);
    }

    x2 /= binPow2(e);

    static if (!is_denormalized)
        x2 -= 1.0;

    static if (floatFormat!T == FloatFormat.Quadruple)
    {
        // Store the 112-bit mantissa in two ulongs, specifically the lower 56
        // bits of each, with the most significant bits in mantissa2. There's
        // an edge case exposed by the labeled test below, where only a subnormal
        // with the highest bit set being the 57th bit will "overflow" to the
        // 57th bit in mantissa2 with the following logic, but that special case
        // is handled by an additional check in denormalizedMantissa for
        // Quadruples below.

        x2 *=  2UL<<(FloatTraits!T.MANTISSA - (ulong.sizeof - 1)*8 - 1);
        ulong mant2 = cast(ulong) x2;
        x2 -= mant2;

        x2 *=  2UL<<((ulong.sizeof - 1)*8 - 1);
        ulong mant = cast(ulong) x2;
        return Float(mant, exp, sign, mant2);
    }
    else
    {
        x2 *=  2UL<<(FloatTraits!T.MANTISSA);
        ulong mant = shiftrRound(cast(ulong)x2);
        return Float(mant, exp, sign);
    }
}

@safe pure nothrow @nogc
private Float parse(bool _ = false, T:real)(T x_) if (floatFormat!T == FloatFormat.Real80)
{
    Unqual!T x = x_;
    //HACK @@@3632@@@

    if (x == 0.0L)
    {
        real y = 1.0L/x;
        if (y == real.infinity) // -0.0
            return FloatTraits!T.ZERO;
        else
            return FloatTraits!T.NZERO; //0.0
    }

    if (x != x) //HACK: should be if (x is real.nan) and if (x is -real.nan)
    {
        auto y = cast(double)x;
        if (y is double.nan)
            return FloatTraits!T.NAN;
        else
            return FloatTraits!T.NNAN;
    }

    if (x == real.infinity) return FloatTraits!T.INF;
    if (x == -real.infinity) return FloatTraits!T.NINF;

    enum EXPONENT_MED = (2^^(FloatTraits!T.EXPONENT-1) - 1);
    uint sign = x < 0;
    x = sign ? -x : x;

    int e = binLog2(x);
    uint exp = cast(uint)(e + EXPONENT_MED);
    if (!exp)
    {
        return denormalizedMantissa(x, sign);
    }
    int pow = (FloatTraits!T.MANTISSA-1-e);
    x *=  binPow2((pow / EXPONENT_MED)*EXPONENT_MED); //To avoid overflow in 2.0L ^^ pow
    x *=  binPow2(pow % EXPONENT_MED);
    ulong mant = cast(ulong)x;
    return Float(mant, exp, sign);
}

private struct Float
{
    ulong mantissa;
    uint exponent;
    uint sign;
    ulong mantissa2;
}

private template FloatTraits(T) if (floatFormat!T == FloatFormat.Float)
{
    enum DATASIZE = 4;
    enum EXPONENT = 8;
    enum MANTISSA = 23;
    enum ZERO     = Float(0, 0, 0);
    enum NZERO    = Float(0, 0, 1);
    enum NAN      = Float(0x400000UL, 0xff, 0);
    enum NNAN     = Float(0x400000UL, 0xff, 1);
    enum INF      = Float(0, 255, 0);
    enum NINF     = Float(0, 255, 1);
}

private template FloatTraits(T) if (floatFormat!T == FloatFormat.Double)
{
    enum DATASIZE = 8;
    enum EXPONENT = 11;
    enum MANTISSA = 52;
    enum ZERO     = Float(0, 0, 0);
    enum NZERO    = Float(0, 0, 1);
    enum NAN      = Float(0x8000000000000UL, 0x7ff, 0);
    enum NNAN     = Float(0x8000000000000UL, 0x7ff, 1);
    enum INF      = Float(0, 0x7ff, 0);
    enum NINF     = Float(0, 0x7ff, 1);
}

private template FloatTraits(T) if (floatFormat!T == FloatFormat.Real80)
{
    enum DATASIZE = 10;
    enum EXPONENT = 15;
    enum MANTISSA = 64;
    enum ZERO     = Float(0, 0, 0);
    enum NZERO    = Float(0, 0, 1);
    enum NAN      = Float(0xC000000000000000UL, 0x7fff, 0);
    enum NNAN     = Float(0xC000000000000000UL, 0x7fff, 1);
    enum INF      = Float(0x8000000000000000UL, 0x7fff, 0);
    enum NINF     = Float(0x8000000000000000UL, 0x7fff, 1);
}

private template FloatTraits(T) if (floatFormat!T == FloatFormat.DoubleDouble) //Unsupported in CTFE
{
    enum DATASIZE = 16;
    enum EXPONENT = 11;
    enum MANTISSA = 106;
    enum ZERO     = Float(0, 0, 0);
    enum NZERO    = Float(0, 0, 1);
    enum NAN      = Float(0x8000000000000UL, 0x7ff, 0);
    enum NNAN     = Float(0x8000000000000UL, 0x7ff, 1);
    enum INF      = Float(0, 0x7ff, 0);
    enum NINF     = Float(0, 0x7ff, 1);
}

private template FloatTraits(T) if (floatFormat!T == FloatFormat.Quadruple)
{
    enum DATASIZE = 16;
    enum EXPONENT = 15;
    enum MANTISSA = 112;
    enum ZERO     = Float(0, 0, 0);
    enum NZERO    = Float(0, 0, 1);
    enum NAN      = Float(0, 0x7fff, 0, 0x80000000000000UL);
    enum NNAN     = Float(0, 0x7fff, 1, 0x80000000000000UL);
    enum INF      = Float(0, 0x7fff, 0);
    enum NINF     = Float(0, 0x7fff, 1);
}


@safe pure nothrow @nogc
private real binPow2(int pow)
{
    static real binPosPow2(int pow) @safe pure nothrow @nogc
    {
        assert(pow > 0);

        if (pow == 1) return 2.0L;

        int subpow = pow/2;
        real p = binPosPow2(subpow);
        real ret = p*p;

        if (pow%2)
        {
            ret *= 2.0L;
        }

        return ret;
    }

    if (!pow) return 1.0L;
    if (pow > 0) return binPosPow2(pow);
    return 1.0L/binPosPow2(-pow);
}


//Need in CTFE, because CTFE float and double expressions computed more precisely that run-time expressions.
@safe pure nothrow @nogc
private ulong shiftrRound(ulong x)
{
    return (x >> 1) + (x & 1);
}

@safe pure nothrow @nogc
private uint binLog2(T)(const T x)
{
    assert(x > 0);
    int max = 2 ^^ (FloatTraits!T.EXPONENT-1)-1;
    int min = -max+1;
    int med = (min + max) / 2;

    if (x < T.min_normal) return -max;

    while ((max - min) > 1)
    {
        if (binPow2(med) > x)
        {
            max = med;
        }
        else
        {
            min = med;
        }
        med = (min + max) / 2;
    }

    if (x < binPow2(max))
        return min;
    return max;
}

@safe pure nothrow @nogc
private Float denormalizedMantissa(T)(T x, uint sign) if (floatFormat!T == FloatFormat.Real80)
{
    x *= 2.0L^^FloatTraits!T.MANTISSA;
    auto fl = parse(x);
    uint pow = FloatTraits!T.MANTISSA - fl.exponent + 1;
    return Float(fl.mantissa >> pow, 0, sign);
}

@safe pure nothrow @nogc
private Float denormalizedMantissa(T)(T x, uint sign)
    if (floatFormat!T == FloatFormat.Float || floatFormat!T == FloatFormat.Double)
{
    x *= 2.0L^^FloatTraits!T.MANTISSA;
    auto fl = parse!true(x);
    ulong mant = fl.mantissa >> (FloatTraits!T.MANTISSA - fl.exponent);
    return Float(shiftrRound(mant), 0, sign);
}

@safe pure nothrow @nogc
private Float denormalizedMantissa(T)(T x, uint sign) if (floatFormat!T == FloatFormat.Quadruple)
{
    x *= 2.0L^^FloatTraits!T.MANTISSA;
    auto fl = parse!true(x);
    uint offset = FloatTraits!T.MANTISSA - fl.exponent + 1;
    enum mantissaSize = (ulong.sizeof - 1) * 8;

    if (offset < mantissaSize)
    {   // Create a new mantissa ulong with the trailing mantissa2 bits that
        // need to be shifted into mantissa, by shifting the needed bits left,
        // zeroing out the first byte, and then ORing it with mantissa shifted
        // right by offset.

        ulong shiftedMantissa = ((fl.mantissa2 << (mantissaSize - offset)) &
                                 0x00FFFFFFFFFFFFFFUL) | fl.mantissa >> offset;
        return Float(shiftedMantissa, 0, sign, fl.mantissa2 >> offset);
    }
    else if (offset > mantissaSize)
        return Float(fl.mantissa2 >> offset - mantissaSize , 0, sign, 0);
    else
        // Handle special case mentioned in parse() above by zeroing out the
        // 57'th bit of mantissa2, "shifting" it into mantissa, and setting the
        // first bit of mantissa2.
        return Float(fl.mantissa2 & 0x00FFFFFFFFFFFFFFUL , 0, sign, 1);
}

version (unittest)
{
    private const(ubyte)[] toUbyte2(T)(T val)
    {
        return toUbyte(val).dup;
    }

    private void testNumberConvert(string v)()
    {
        enum ctval = mixin(v);

        alias TYPE = typeof(ctval);
        auto rtval = ctval;
        auto rtbytes = *cast(ubyte[TYPE.sizeof]*)&rtval;

        enum ctbytes = toUbyte2(ctval);

        // don't test pad bytes because can be anything
        enum testsize =
            (FloatTraits!TYPE.EXPONENT + FloatTraits!TYPE.MANTISSA + 1)/8;
        assert(rtbytes[0..testsize] == ctbytes[0..testsize]);
    }

    private void testConvert()
    {
        /**Test special values*/
        testNumberConvert!("-float.infinity");
        testNumberConvert!("float.infinity");
        testNumberConvert!("-0.0F");
        testNumberConvert!("0.0F");
        //testNumberConvert!("-float.nan"); //BUG @@@3632@@@
        testNumberConvert!("float.nan");

        testNumberConvert!("-double.infinity");
        testNumberConvert!("double.infinity");
        testNumberConvert!("-0.0");
        testNumberConvert!("0.0");
        //testNumberConvert!("-double.nan"); //BUG @@@3632@@@
        testNumberConvert!("double.nan");

        testNumberConvert!("-real.infinity");
        testNumberConvert!("real.infinity");
        testNumberConvert!("-0.0L");
        testNumberConvert!("0.0L");
        //testNumberConvert!("-real.nan"); //BUG @@@3632@@@
        testNumberConvert!("real.nan");

        /**
            Test min and max values values: min value has an '1' mantissa and minimal exponent,
            Max value has an all '1' bits mantissa and max exponent.
        */
        testNumberConvert!("float.min_normal");
        testNumberConvert!("float.max");

        /**Test common values*/
        testNumberConvert!("-0.17F");
        testNumberConvert!("3.14F");

        /**Test immutable and const*/
        testNumberConvert!("cast(const)3.14F");
        testNumberConvert!("cast(immutable)3.14F");

        /**The same tests for double and real*/
        testNumberConvert!("double.min_normal");
        testNumberConvert!("double.max");
        testNumberConvert!("-0.17");
        testNumberConvert!("3.14");
        testNumberConvert!("cast(const)3.14");
        testNumberConvert!("cast(immutable)3.14");

        testNumberConvert!("real.min_normal");
        testNumberConvert!("real.max");
        testNumberConvert!("-0.17L");
        testNumberConvert!("3.14L");
        testNumberConvert!("cast(const)3.14L");
        testNumberConvert!("cast(immutable)3.14L");

        /**Test denormalized values*/

        /**Max denormalized value, first bit is 1*/
        testNumberConvert!("float.min_normal/2");
        /**Min denormalized value, last bit is 1*/
        testNumberConvert!("float.min_normal/2UL^^23");

        /**Denormalized values with round*/
        testNumberConvert!("float.min_normal/19");
        testNumberConvert!("float.min_normal/17");

        testNumberConvert!("double.min_normal/2");
        testNumberConvert!("double.min_normal/2UL^^52");
        testNumberConvert!("double.min_normal/19");
        testNumberConvert!("double.min_normal/17");

        testNumberConvert!("real.min_normal/2");
        testNumberConvert!("real.min_normal/2UL^^63");
        // check subnormal storage edge case for Quadruple
        testNumberConvert!("real.min_normal/2UL^^56");
        testNumberConvert!("real.min_normal/19");
        testNumberConvert!("real.min_normal/17");

        /**Test imaginary values: convert algorithm is same with real values*/
        testNumberConvert!("0.0Fi");
        testNumberConvert!("0.0i");
        testNumberConvert!("0.0Li");

        /**True random values*/
        testNumberConvert!("-0x9.0f7ee55df77618fp-13829L");
        testNumberConvert!("0x7.36e6e2640120d28p+8797L");
        testNumberConvert!("-0x1.05df6ce4702ccf8p+15835L");
        testNumberConvert!("0x9.54bb0d88806f714p-7088L");

        testNumberConvert!("-0x9.0f7ee55df7ffp-338");
        testNumberConvert!("0x7.36e6e264012dp+879");
        testNumberConvert!("-0x1.05df6ce4708ep+658");
        testNumberConvert!("0x9.54bb0d888061p-708");

        testNumberConvert!("-0x9.0f7eefp-101F");
        testNumberConvert!("0x7.36e6ep+87F");
        testNumberConvert!("-0x1.05df6p+112F");
        testNumberConvert!("0x9.54bb0p-70F");

        /**Big overflow or underflow*/
        testNumberConvert!("cast(double)-0x9.0f7ee55df77618fp-13829L");
        testNumberConvert!("cast(double)0x7.36e6e2640120d28p+8797L");
        testNumberConvert!("cast(double)-0x1.05df6ce4702ccf8p+15835L");
        testNumberConvert!("cast(double)0x9.54bb0d88806f714p-7088L");

        testNumberConvert!("cast(float)-0x9.0f7ee55df77618fp-13829L");
        testNumberConvert!("cast(float)0x7.36e6e2640120d28p+8797L");
        testNumberConvert!("cast(float)-0x1.05df6ce4702ccf8p+15835L");
        testNumberConvert!("cast(float)0x9.54bb0d88806f714p-7088L");
    }


    unittest
    {
        testConvert();
    }
}



private enum FloatFormat
{
    Float,
    Double,
    Real80,
    DoubleDouble,
    Quadruple
}

template floatFormat(T) if (is(T:real) || is(T:ireal))
{
    static if (T.mant_dig == 24)
        enum floatFormat = FloatFormat.Float;
    else static if (T.mant_dig == 53)
        enum floatFormat = FloatFormat.Double;
    else static if (T.mant_dig == 64)
        enum floatFormat = FloatFormat.Real80;
    else static if (T.mant_dig == 106)
        enum floatFormat = FloatFormat.DoubleDouble;
    else static if (T.mant_dig == 113)
        enum floatFormat = FloatFormat.Quadruple;
    else
        static assert(0);

}

package template floatSize(T) if (is(T:real) || is(T:ireal))
{
    enum floatSize = FloatTraits!(T).DATASIZE;
}

//  all toUbyte functions must be evaluable at compile time
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const T[] arr) if (T.sizeof == 1)
{
    return cast(const(ubyte)[])arr;
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const T[] arr) if (T.sizeof > 1)
{
    if (__ctfe)
    {
        ubyte[] ret = ctfe_alloc(T.sizeof * arr.length);
        static if (is(T EType == enum)) // Odd style is to avoid template instantiation in most cases.
            alias E = OriginalType!EType;
        else
            alias E = T;
        static if (is(E == struct) || is(E == union) || __traits(isStaticArray, E) || !is(typeof(arr[0] is null)))
        {
            size_t offset = 0;
            foreach (ref cur; arr)
            {
                ret[offset .. offset + T.sizeof] = toUbyte(cur)[0 .. T.sizeof];
                offset += T.sizeof;
            }
        }
        else
        {
            foreach (cur; arr)
                assert(cur is null, "Unable to compute byte representation of non-null pointer at compile time");
        }
        return ret;
    }
    else
    {
        return (cast(const(ubyte)*)(arr.ptr))[0 .. T.sizeof*arr.length];
    }
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (__traits(isIntegral, T) && !is(T == enum) && !is(T == __vector))
{
    static if (T.sizeof == 1)
    {
        if (__ctfe)
        {
            ubyte[] result = ctfe_alloc(1);
            result[0] = cast(ubyte) val;
            return result;
        }
        else
        {
            return (cast(const(ubyte)*)(&val))[0 .. T.sizeof];
        }
    }
    else if (__ctfe)
    {
        ubyte[] tmp = ctfe_alloc(T.sizeof);
        Unqual!T val_ = val;
        for (size_t i = 0; i < T.sizeof; ++i)
        {
            size_t idx;
            version (LittleEndian) idx = i;
            else idx = T.sizeof-i-1;
            tmp[idx] = cast(ubyte)(val_&0xff);
            val_ >>= 8;
        }
        return tmp;
    }
    else
    {
        return (cast(const(ubyte)*)(&val))[0 .. T.sizeof];
    }
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == __vector))
{
    if (!__ctfe)
        return (cast(const ubyte*) &val)[0 .. T.sizeof];
    else static if (is(typeof(val[0]) : void))
        assert(0, "Unable to compute byte representation of " ~ T.stringof ~ " at compile time.");
    else
    {
        // This code looks like it should work in CTFE but it segfaults:
        //    auto a = val.array;
        //    return toUbyte(a);
        alias E = typeof(val[0]);
        ubyte[] result = ctfe_alloc(T.sizeof);
        for (size_t i = 0, j = 0; i < T.sizeof; i += E.sizeof, ++j)
        {
            result[i .. i + E.sizeof] = toUbyte(val[j]);
        }
        return result;
    }
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == cfloat) || is(Unqual!T == cdouble) ||is(Unqual!T == creal))
{
    if (__ctfe)
    {
        auto re = val.re;
        auto im = val.im;
        auto a = re.toUbyte();
        auto b = im.toUbyte();
        ubyte[] result = ctfe_alloc(a.length + b.length);
        result[0 .. a.length] = a[0 .. a.length];
        result[a.length .. $] = b[0 .. b.length];
        return result;
    }
    else
    {
        return (cast(const(ubyte)*)&val)[0 .. T.sizeof];
    }
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == enum))
{
    if (__ctfe)
    {
        static if (is(T V == enum)){}
        return toUbyte(cast(const V) val);
    }
    else
    {
        return (cast(const(ubyte)*)&val)[0 .. T.sizeof];
    }
}

nothrow pure @safe unittest
{
    // Issue 19008 - check toUbyte works on enums.
    enum Month : uint { jan = 1}
    Month m = Month.jan;
    const bytes = toUbyte(m);
    enum ctfe_works = (() => { Month x = Month.jan; return toUbyte(x).length > 0; })();
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == delegate) || is(T : V*, V) && __traits(getAliasThis, T).length == 0)
{
    if (__ctfe)
    {
        if (val !is null) assert(0, "Unable to compute byte representation of non-null pointer at compile time");
        return ctfe_alloc(T.sizeof);
    }
    else
    {
        return (cast(const(ubyte)*)&val)[0 .. T.sizeof];
    }
}

@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == struct) || is(T == union))
{
    if (__ctfe)
    {
        ubyte[] bytes = ctfe_alloc(T.sizeof);
        foreach (key, ref cur; val.tupleof)
        {
            static if (is(typeof(cur) EType == enum)) // Odd style is to avoid template instantiation in most cases.
                alias CurType = OriginalType!EType;
            else
                alias CurType = typeof(cur);
            static if (is(CurType == struct) || is(CurType == union) || __traits(isStaticArray, CurType) || !is(typeof(cur is null)))
            {
                bytes[val.tupleof[key].offsetof .. val.tupleof[key].offsetof + CurType.sizeof] = toUbyte(cur)[];
            }
            else
            {
                assert(cur is null, "Unable to compute byte representation of non-null reference field at compile time");
                //skip, because val bytes are zeros
            }
        }
        return bytes;
    }
    else
    {
        return (cast(const(ubyte)*)&val)[0 .. T.sizeof];
    }
}

// Strips off all `enum`s from type `T`.
// Perhaps move to core.internal.types.
private template OriginalType(T)
{
    static if (is(T EType == enum))
        alias OriginalType = .OriginalType!EType;
    else
        alias OriginalType = T;
}