| /** |
| * A library bitfields utility |
| * |
| * Copyright: Copyright (C) 1999-2025 by The D Language Foundation, All Rights Reserved |
| * Authors: Dennis Korpel |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/common/bitfields.d, common/bitfields.d) |
| * Documentation: https://dlang.org/phobos/dmd_common_bitfields.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/common/bitfields.d |
| */ |
| module dmd.common.bitfields; |
| |
| nothrow: |
| @safe: |
| |
| //version = Has_Bitfields; // does not work (yet) because hashOf doesn't work on bitfields |
| version(Has_Bitfields) |
| version = Debugger_friendly; // without Has_Bitfields, this uses more space by using S |
| |
| /** |
| * Generate code for bit fields inside a struct/class body |
| * Params: |
| * S = type of a struct with only boolean fields, which should become bit fields |
| * T = type of bit fields variable, must have enough bits to store all booleans |
| * field = if provided, assume it is declared and initialized elsewhere |
| * bitOff = start using bits at the given offset |
| * Returns: D code with a bit fields variable and getter / setter functions |
| */ |
| extern (D) string generateBitFields(S, T, string field = "", int bitOff = 0, int ID = __LINE__)() |
| if (__traits(isUnsigned, T)) |
| { |
| import core.bitop: bsr; |
| // if _fieldName provided, assume it declared and initialized elsewhere |
| enum fieldName = field.length == 0 ? "bitFields" : field; |
| string result = "extern (C++) pure nothrow @nogc @safe final {"; |
| |
| struct BitInfo |
| { |
| int[] offset; |
| int[] size; |
| T initialValue; |
| int totalSize; |
| } |
| |
| // Iterate over members to compute bit offset and bit size for each of them |
| enum BitInfo bitInfo = () { |
| BitInfo result; |
| int bitOffset = bitOff; |
| foreach (size_t i, mem; __traits(allMembers, S)) |
| { |
| alias memType = typeof(__traits(getMember, S, mem)); |
| enum int bitSize = bsr(memType.max | 1) + 1; |
| result.offset ~= bitOffset; |
| result.size ~= bitSize; |
| result.initialValue |= cast(T) __traits(getMember, S.init, mem) << bitOffset; |
| bitOffset += bitSize; |
| } |
| result.totalSize = bitOffset; |
| return result; |
| } (); |
| |
| alias TP = typeof(T.init + 0u); // type that `T` gets promoted to, uint or ulong |
| enum string toString(TP i) = i.stringof; // compile time 'integer to string' |
| |
| static assert(bitInfo.totalSize <= T.sizeof * 8, |
| "sum of bit field size "~toString!(bitInfo.totalSize)~" exceeds storage type `"~T.stringof~"`"); |
| |
| version(Debugger_friendly) |
| { |
| // unique name needed to allow same name as in base class using `alias`, but without overloading |
| string bitfieldsName = fieldName ~ toString!(ID); |
| string bitfieldsRead = T.stringof~" "~bitfieldsName~"() const pure { return 0"; |
| string bitfieldsWrite = "void "~bitfieldsName~"("~T.stringof~" v) {\n"; |
| } |
| |
| foreach (size_t i, mem; __traits(allMembers, S)) |
| { |
| enum typeName = typeof(__traits(getMember, S, mem)).stringof; |
| enum shift = toString!(bitInfo.offset[i]); |
| enum sizeMask = toString!((1 << bitInfo.size[i]) - 1); // 0x01 for bool, 0xFF for ubyte etc. |
| version(Debugger_friendly) |
| { |
| string memacc = mem; |
| bitfieldsRead ~= "\n| (cast("~T.stringof~")("~memacc~" & "~sizeMask~") << "~shift~")"; |
| bitfieldsWrite ~= memacc~" = cast("~typeName~")((v >> "~shift~") & "~sizeMask~");\n"; |
| result ~= typeName~" "~mem; |
| version(Has_Bitfields) |
| result ~= " : "~toString!(bitInfo.size[i]); |
| enum meminit = __traits(getMember, S.init, mem); |
| result ~= " = "~meminit.stringof~";\n"; |
| } |
| else |
| { |
| result ~= " |
| "~typeName~" "~mem~"() const scope { return cast("~typeName~") (("~fieldName~" >>> "~shift~") & "~sizeMask~"); } |
| "~typeName~" "~mem~"("~typeName~" v) scope |
| { |
| "~fieldName~" &= ~("~sizeMask~" << "~shift~"); |
| "~fieldName~" |= v << "~shift~"; |
| return v; |
| }"; |
| } |
| } |
| version(Debugger_friendly) |
| { |
| bitfieldsRead ~= ";\n}\n"; |
| bitfieldsWrite ~= "}\n"; |
| if (field.length == 0) |
| result ~= "alias "~fieldName~" = "~bitfieldsName~";\n"; |
| result ~= bitfieldsRead ~ bitfieldsWrite; |
| result ~= "\n}\n"; |
| return result; |
| } |
| else |
| { |
| result ~= "\n}\n"; |
| enum TP initVal = bitInfo.initialValue; |
| if (field.length == 0) |
| result ~= " private "~T.stringof~" "~fieldName~" = " ~ toString!(initVal) ~ ";\n"; |
| return result; |
| } |
| } |
| |
| /// |
| unittest |
| { |
| enum E |
| { |
| a, b, c, |
| } |
| |
| static struct B |
| { |
| bool x; |
| bool y; |
| E e = E.c; |
| bool z = 1; |
| private ubyte w = 77; |
| } |
| |
| static struct S |
| { |
| mixin(generateBitFields!(B, ushort)); |
| } |
| |
| S s; |
| assert(!s.x); |
| s.x = true; |
| assert(s.x); |
| s.x = false; |
| assert(!s.x); |
| |
| s.y = true; |
| assert(s.y); |
| assert(!s.x); |
| |
| assert(s.e == E.c); |
| s.e = E.a; |
| assert(s.e == E.a); |
| |
| assert(s.z); |
| assert(s.w == 77); |
| s.w = 3; |
| assert(s.w == 3); |
| } |