diff --git a/std/bitmanip.d b/std/bitmanip.d index c38a2a8cec4..fcbf6a57e58 100644 --- a/std/bitmanip.d +++ b/std/bitmanip.d @@ -9,6 +9,8 @@ $(TR $(TH Category) $(TH Functions)) $(TR $(TD Bit constructs) $(TD $(LREF BitArray) $(LREF bitfields) + $(LREF bitfields2) + $(LREF bitfields2Code) $(LREF bitsSet) )) $(TR $(TD Endianness conversion) $(TD @@ -64,12 +66,12 @@ version(unittest) } -private string myToString(ulong n) +private string myToString(ulong n, bool hex = false) { import core.internal.string : UnsignedStringBuf, unsignedToTempString; UnsignedStringBuf buf; - auto s = unsignedToTempString(n, buf); - return cast(string) s ~ (n > uint.max ? "UL" : "U"); + auto s = unsignedToTempString(n, buf, hex ? 16 : 10); + return (hex ? "0x" : null) ~ cast(string) s ~ (n > uint.max ? "UL" : "U"); } private template createAccessors( @@ -154,28 +156,6 @@ private template createStoreName(Ts...) enum createStoreName = "_" ~ Ts[1] ~ createStoreName!(Ts[3 .. $]); } -private template createStorageAndFields(Ts...) -{ - enum Name = createStoreName!Ts; - enum Size = sizeOfBitField!Ts; - static if (Size == ubyte.sizeof * 8) - alias StoreType = ubyte; - else static if (Size == ushort.sizeof * 8) - alias StoreType = ushort; - else static if (Size == uint.sizeof * 8) - alias StoreType = uint; - else static if (Size == ulong.sizeof * 8) - alias StoreType = ulong; - else - { - static assert(false, "Field widths must sum to 8, 16, 32, or 64"); - alias StoreType = ulong; // just to avoid another error msg - } - enum result - = "private " ~ StoreType.stringof ~ " " ~ Name ~ ";" - ~ createFields!(Name, 0, Ts).result; -} - private template createFields(string store, size_t offset, Ts...) { static if (Ts.length > 0) @@ -292,7 +272,208 @@ bool), followed by unsigned types, followed by signed types. template bitfields(T...) { - enum { bitfields = createStorageAndFields!T.result } + enum { bitfields = bitfields2CodeImpl!(false, T) } +} + +/** +Allows creating bit fields inside $(D_PARAM struct)s and $(D_PARAM class)es. + +Example: + +---- +struct A +{ + int a; + mixin bitfields!( + uint, "x", 2, + int, "y", 3, + uint, "z", 2, + bool, "flag", 1); +} +A obj; +obj.x = 2; +obj.z = obj.x; +---- + +The example above creates a bitfield pack of eight bits, which fit in +one $(D_PARAM ubyte). The bitfields are allocated starting from the +least significant bit, i.e. x occupies the two least significant bits +of the bitfields storage. + +The sum of all bit lengths in one $(D_PARAM bitfield) instantiation +must be exactly 8, 16, 32, or 64. If padding is needed, just allocate +one bitfield with an empty name. + +Example: + +---- +struct A +{ + mixin(bitfields!( + bool , "flag1", 1, + Flag!"flag2", "flag2", 1, + uint , "" , 6)); +} +---- + +The type of a bit field can be any integral type, enumerated +type, or type that can cast to/from an integral type. The most +efficient type to store in bitfields is $(D_PARAM bool), followed +by unsigned types, followed by signed types. +*/ +mixin template bitfields2(T...) +{ + mixin(bitfields2Code!T); +} + +/** +Template returns the mixin code used by $(LREF bitfields2). Useful +for debugging the generated mixin code. + +Example: + +---- +struct A +{ + // This will print the code generated by the bitfields2 mixin below. + pragma(msg, bitfields2Code!( + bool , "flag1", 1, + Flag!"flag2", "flag2", 1, + uint , "" , 6)); + mixin bitfields2!( + bool , "flag1", 1, + Flag!"flag2", "flag2", 1, + uint , "" , 6); +} +---- +*/ +template bitfields2Code(T...) +{ + enum bitfields2Code = bitfields2CodeImpl!(true, T); +} +private template bitfields2CodeImpl(bool useTypeAlias, T...) +{ + enum bitfields2CodeImpl = bitfieldsCodeGenerator(); + + // NOTE: the long-verbose name for this function is helpful for compiler error messages + private string bitfieldsCodeGenerator() + { + string store; + size_t storageSize = 0; + foreach (i, t; T) + { + static if (i % 3 == 0) + { + store ~= "_" ~ T[i + 1]; + storageSize += T[i + 2]; + } + } + + string storageType; + if (storageSize == ubyte.sizeof * 8) + storageType = "ubyte"; + else if (storageSize == ushort.sizeof * 8) + storageType = "ushort"; + else if (storageSize == uint.sizeof * 8) + storageType = "uint"; + else if (storageSize == ulong.sizeof * 8) + storageType = "ulong"; + else + assert(0, "bitfield widths sum to " ~ myToString(storageSize) ~ " but must sum to 8, 16, 32, or 64"); + + string mixinCode = "private " ~ storageType ~ " " ~ store ~ ";\n"; + + size_t bitOffset = 0; + foreach (i, type; T) + { + static if (i % 3 == 0) + { + static if (useTypeAlias) + { + enum typeString = "T[" ~ myToString(i) ~ "]"; + } + else + { + enum typeString = T[i].stringof; + } + enum name = T[i + 1]; + enum bitlength = T[i + 2]; + + static if (name.length == 0) + { + // No need to create any accessor + } + else static if (bitlength == 0) + { + // Fields of length 0 are always zero + mixinCode ~= "enum " ~ typeString ~ " " ~ name ~ " = 0;\n"; + } + else + { + string inverseMask = myToString(((~0uL) >> (64 - bitlength)) << bitOffset, true); + + static if (is(type : bool)) + { + static assert(bitlength == 1); + mixinCode ~= + // getter + "@property " ~ typeString ~ " " ~ name ~ "() @safe pure nothrow @nogc const {\n" ~ + " return cast(" ~ typeString ~ ") ((" ~ store ~ " & " ~ inverseMask ~ ") != 0);\n}\n" ~ + // setter + "@property void " ~ name ~ "(" ~ typeString ~ " v) @safe pure nothrow @nogc {\n" ~ + " if (v) " ~ store ~ " |= " ~ inverseMask ~ ";\n" ~ + " else " ~ store ~ " &= ~cast(typeof(" ~ store ~ ")) " ~ inverseMask ~ ";\n}\n"; + } + else + { + ulong signBitCheck = 1uL << (bitlength - 1); + + static if (type.min < 0) + { + enum long minVal = -(1uL << (bitlength - 1)); + enum ulong maxVal = (1uL << (bitlength - 1)) - 1; + alias UT = Unsigned!type; + enum UT extendSign = cast(UT)~((~0uL) >> (64 - bitlength)); + } + else + { + enum ulong minVal = 0; + enum ulong maxVal = (~0uL) >> (64 - bitlength); + enum extendSign = 0; + } + + mixinCode ~= + // getter + "@property " ~ typeString ~ " " ~ name ~ "() @safe pure nothrow @nogc const {\n" ~ + " auto result = (" ~ store ~ " & " ~ inverseMask ~ ") >> " ~ myToString(bitOffset) ~ ";\n" ~ + ((type.min >= 0) ? "" : " if (result >= " ~ myToString(signBitCheck) ~ + ") result |= " ~ myToString(extendSign) ~ ";") ~ + " return cast(" ~ typeString ~ ") result;\n}\n" ~ + // setter + "@property void " ~ name ~ "(" ~ typeString ~ " v) @safe pure nothrow @nogc {\n" ~ + " assert(v >= " ~ name ~ "_min, \"Value is smaller than the minimum value of bitfield '" ~ + name ~ "'\");\n" ~ + " assert(v <= " ~ name ~ "_max, \"Value is greater than the maximum value of bitfield '" ~ + name ~ "'\");\n" ~ + " " ~ store ~ " = cast(typeof(" ~ store ~ ")) ((" ~ store ~ " & ~cast(typeof(" ~ store ~ + ")) " ~ inverseMask ~ ")\n" ~ + " | ((cast(typeof(" ~ store ~ ")) v << " ~ myToString(bitOffset) ~ ") & " ~ inverseMask ~ + ")\n" ~ + " );\n}\n" ~ + // constants + "enum " ~ typeString ~ " " ~ name ~ "_min = cast(" ~ typeString ~ ") " ~ myToString(minVal) ~ + ";\n" ~ + "enum " ~ typeString ~ " " ~ name ~ "_max = cast(" ~ typeString ~ ") " ~ myToString(maxVal) ~ + ";\n"; + } + } + + bitOffset += bitlength; + } + } + + return mixinCode; + } } /** @@ -429,6 +610,73 @@ unittest t2b.e = -5; assert(t2b.e == -5); t4b.a = -5; assert(t4b.a == -5L); } +@safe pure nothrow @nogc +unittest +{ + // Degenerate bitfields (#8474 / #11160) tests mixed with range tests + struct Test1 + { + mixin bitfields2!(uint, "a", 32, + uint, "b", 4, + uint, "c", 4, + uint, "d", 8, + uint, "e", 16,); + + static assert(Test1.b_min == 0); + static assert(Test1.b_max == 15); + } + + struct Test2 + { + mixin bitfields2!(bool, "a", 0, + ulong, "b", 64); + + static assert(Test2.b_min == ulong.min); + static assert(Test2.b_max == ulong.max); + } + + struct Test1b + { + mixin bitfields2!(bool, "a", 0, + int, "b", 8); + } + + struct Test2b + { + mixin bitfields2!(int, "a", 32, + int, "b", 4, + int, "c", 4, + int, "d", 8, + int, "e", 16,); + + static assert(Test2b.b_min == -8); + static assert(Test2b.b_max == 7); + } + + struct Test3b + { + mixin bitfields2!(bool, "a", 0, + long, "b", 64); + + static assert(Test3b.b_min == long.min); + static assert(Test3b.b_max == long.max); + } + + struct Test4b + { + import std.bitmanip : bitfields; + mixin bitfields2!(long, "a", 32, + int, "b", 32); + } + + // Sign extension tests + Test2b t2b; + Test4b t4b; + t2b.b = -5; assert(t2b.b == -5); + t2b.d = -5; assert(t2b.d == -5); + t2b.e = -5; assert(t2b.e == -5); + t4b.a = -5; assert(t4b.a == -5L); +} @system unittest { @@ -510,6 +758,21 @@ unittest num.back = 1; assert(num.bits == 0xFFFF_FFFF_8000_0001uL); } +@safe unittest +{ + // Bug #6686 + union S { + ulong bits = ulong.max; + mixin bitfields2!( + ulong, "back", 31, + ulong, "front", 33); + } + S num; + + num.bits = ulong.max; + num.back = 1; + assert(num.bits == 0xFFFF_FFFF_8000_0001uL); +} @safe unittest { @@ -527,6 +790,22 @@ unittest data.a = 1; assert(data.b == 42); } +@safe unittest +{ + // Bug #5942 + struct S + { + mixin bitfields2!( + int, "a" , 32, + int, "b" , 32 + ); + } + + S data; + data.b = 42; + data.a = 1; + assert(data.b == 42); +} @safe unittest { @@ -552,6 +831,30 @@ unittest test(); } +@safe unittest +{ + struct Test + { + mixin bitfields2!(bool, "a", 1, + uint, "b", 3, + short, "c", 4); + } + + @safe void test() pure nothrow + { + Test t; + + t.a = true; + t.b = 5; + t.c = 2; + + assert(t.a); + assert(t.b == 5); + assert(t.c == 2); + } + + test(); +} @safe unittest { @@ -628,6 +931,82 @@ unittest f.b = true; assert(f.checkExpectations(true)); } +@safe unittest +{ + { + static struct Integrals { + bool checkExpectations(bool eb, int ei, short es) { return b == eb && i == ei && s == es; } + + mixin bitfields2!( + bool, "b", 1, + uint, "i", 3, + short, "s", 4); + } + Integrals i; + assert(i.checkExpectations(false, 0, 0)); + i.b = true; + assert(i.checkExpectations(true, 0, 0)); + i.i = 7; + assert(i.checkExpectations(true, 7, 0)); + i.s = -8; + assert(i.checkExpectations(true, 7, -8)); + i.s = 7; + assert(i.checkExpectations(true, 7, 7)); + } + + //Bug# 8876 + { + struct MoreIntegrals { + bool checkExpectations(uint eu, ushort es, uint ei) { return u == eu && s == es && i == ei; } + + mixin bitfields2!( + uint, "u", 24, + short, "s", 16, + int, "i", 24); + } + + MoreIntegrals i; + assert(i.checkExpectations(0, 0, 0)); + i.s = 20; + assert(i.checkExpectations(0, 20, 0)); + i.i = 72; + assert(i.checkExpectations(0, 20, 72)); + i.u = 8; + assert(i.checkExpectations(8, 20, 72)); + i.s = 7; + assert(i.checkExpectations(8, 7, 72)); + } + + enum A { True, False } + enum B { One, Two, Three, Four } + static struct Enums { + bool checkExpectations(A ea, B eb) { return a == ea && b == eb; } + + mixin bitfields2!( + A, "a", 1, + B, "b", 2, + uint, "", 5); + } + Enums e; + assert(e.checkExpectations(A.True, B.One)); + e.a = A.False; + assert(e.checkExpectations(A.False, B.One)); + e.b = B.Three; + assert(e.checkExpectations(A.False, B.Three)); + + static struct SingleMember { + bool checkExpectations(bool eb) { return b == eb; } + + mixin bitfields2!( + bool, "b", 1, + uint, "", 7); + } + SingleMember f; + assert(f.checkExpectations(false)); + f.b = true; + assert(f.checkExpectations(true)); +} + // Issue 12477 @system unittest @@ -653,6 +1032,105 @@ unittest catch (AssertError ae) { assert(ae.msg.canFind("Value is smaller than the minimum value of bitfield 'b'"), ae.msg); } } +// Issue 12477 +@system unittest +{ + import core.exception : AssertError; + import std.algorithm.searching : canFind; + + static struct S + { + mixin bitfields2!( + uint, "a", 6, + int, "b", 2); + } + + S s; + + try { s.a = uint.max; assert(0); } + catch (AssertError ae) + { assert(ae.msg.canFind("Value is greater than the maximum value of bitfield 'a'"), ae.msg); } + + try { s.b = int.min; assert(0); } + catch (AssertError ae) + { assert(ae.msg.canFind("Value is smaller than the minimum value of bitfield 'b'"), ae.msg); } +} + +@safe pure nothrow @nogc +unittest +{ + import std.typecons : Flag, Yes, No; + enum CustomFlag1 : bool + { + first = false, second = true + } + alias CustomFlag2 = Flag!"CustomFlag2"; + template CustomFlag3(string name) + { + enum CustomFlag3 : bool + { + false_ = false, + true_ = true, + } + } + struct Test + { + mixin bitfields2!( + Flag!"a", "a", 1, + CustomFlag1, "b", 1, + CustomFlag2, "c", 1, + CustomFlag3!"something", "d", 1, + ubyte, "", 4); + } + Test t; + + Flag!"a" a; + CustomFlag1 b; + CustomFlag2 c; + CustomFlag3!"something" d; + + void check() + { + assert(a == t.a); + assert(b == t.b); + assert(c == t.c); + assert(d == t.d); + } + + check(); + + a = Yes.a; + t.a = a; + check(); + + b = CustomFlag1.second; + t.b = b; + check(); + + c = Yes.CustomFlag2; + t.c = c; + check(); + + d = CustomFlag3!"something".true_; + t.d = d; + check(); + + a = No.a; + t.a = a; + check(); + + b = CustomFlag1.first; + t.b = b; + check(); + + c = No.CustomFlag2; + t.c = c; + check(); + + d = CustomFlag3!"something".false_; + t.d = d; + check(); +} /** Allows manipulating the fraction, exponent, and sign parts of a