The OpenD Programming Language

1 /++
2 Bit-level manipulation facilities.
3 
4 $(SCRIPT inhibitQuickIndex = 1;)
5 $(BOOKTABLE,
6 $(TR $(TH Category) $(TH Functions))
7 $(TR $(TD Bit constructs) $(TD
8     $(LREF bitfields)
9 ))
10 $(TR $(TD Tagging) $(TD
11     $(LREF taggedClassRef)
12     $(LREF taggedPointer)
13 ))
14 )
15 
16 License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
17 Authors:   $(HTTP digitalmars.com, Walter Bright),
18            $(HTTP erdani.org, Andrei Alexandrescu),
19            Amaury SECHET
20 +/
21 module mir.bitmanip;
22 
23 import std.traits;
24 
25 private string normString()(string str)
26 {
27     // if (str.length && (str[$-1] == 'U' || str[$-1] == 'u')) str = str[0 .. $-1];
28     // if (str.length && (str[$-1] == 'L' || str[$-1] == 'l')) str = str[0 .. $-1];
29     return str;
30 }
31 
32 private template createAccessors(
33     string store, T, string name, size_t len, size_t offset)
34 {
35     static if (!name.length)
36     {
37         // No need to create any accessor
38         enum result = "";
39     }
40     else static if (len == 0)
41     {
42         // Fields of length 0 are always zero
43         enum result = "enum "~T.stringof~" "~name~" = 0;\n";
44     }
45     else
46     {
47         enum ulong
48             maskAllElse = ((~0uL) >> (64 - len)) << offset,
49             signBitCheck = 1uL << (len - 1);
50 
51         static if (T.min < 0)
52         {
53             enum long minVal = -(1uL << (len - 1));
54             enum long maxVal = (1uL << (len - 1)) - 1;
55             alias UT = Unsigned!(T);
56             enum UT extendSign = cast(UT)~((~0uL) >> (64 - len));
57         }
58         else
59         {
60             enum ulong minVal = 0;
61             enum ulong maxVal = (~0uL) >> (64 - len);
62             enum extendSign = 0;
63         }
64 
65         static if (is(T == bool))
66         {
67             static assert(len == 1);
68             enum result =
69             // getter
70                 "@property bool " ~ name ~ "()() @safe pure nothrow @nogc const { return "
71                 ~"("~store~" & "~ maskAllElse.stringof ~") != 0;}\n"
72             // setter
73                 ~"@property void " ~ name ~ "()(bool v) @safe pure nothrow @nogc { "
74                 ~"if (v) "~store~" |= "~ maskAllElse.stringof ~";"
75                 ~"else "~store~" &= cast(typeof("~store~"))(-1-cast(typeof("~store~"))"~ maskAllElse.stringof ~");}\n";
76         }
77         else
78         {
79             // getter
80             enum result = "@property "~T.stringof~" "~name~"()() @safe pure nothrow @nogc const { ulong result = "
81                 ~"(ulong("~store~") & "
82                 ~  maskAllElse.stringof  ~ ") >>"
83                 ~  offset.stringof  ~ ";"
84                 ~ (T.min < 0
85                    ? "if (result >= " ~  signBitCheck.stringof 
86                    ~ ") result |= " ~  extendSign.stringof  ~ ";"
87                    : "")
88                 ~ " return cast("~T.stringof~") result;}\n"
89             // setter
90                 ~"@property void "~name~"()("~T.stringof~" v) @safe pure nothrow @nogc { "
91                 ~"assert(v >= "~name~`_min, "Value is smaller than the minimum value of bitfield '`~name~`'"); `
92                 ~"assert(v <= "~name~`_max, "Value is greater than the maximum value of bitfield '`~name~`'"); `
93                 ~store~" = cast(typeof("~store~"))"
94                 ~" (("~store~" & (-1-cast(typeof("~store~"))"~ maskAllElse.stringof ~"))"
95                 ~" | ((cast(typeof("~store~")) v << "~ offset.stringof ~")"
96                 ~" & "~ maskAllElse.stringof ~"));}\n"
97             // constants
98                 ~"enum "~T.stringof~" "~name~"_min = cast("~T.stringof~")"
99                 ~ (minVal == minVal.min && minVal.min < 0 ? "long.min" : minVal.stringof) ~"; "
100                 ~" enum "~T.stringof~" "~name~"_max = cast("~T.stringof~")"
101                 ~ maxVal.stringof ~"; ";
102         }
103     }
104 }
105 
106 private template createStoreName(Ts...)
107 {
108     static if (Ts.length < 2)
109         enum createStoreName = "";
110     else
111         enum createStoreName = "_" ~ Ts[1] ~ createStoreName!(Ts[3 .. $]);
112 }
113 
114 private template createStorageAndFields(Ts...)
115 {
116     enum Name = createStoreName!Ts;
117     enum Size = sizeOfBitField!Ts;
118     static if (Size == ubyte.sizeof * 8)
119         alias StoreType = ubyte;
120     else static if (Size == ushort.sizeof * 8)
121         alias StoreType = ushort;
122     else static if (Size == uint.sizeof * 8)
123         alias StoreType = uint;
124     else static if (Size == ulong.sizeof * 8)
125         alias StoreType = ulong;
126     else
127     {
128         static assert(false, "Field widths must sum to 8, 16, 32, or 64");
129         alias StoreType = ulong; // just to avoid another error msg
130     }
131     enum result
132         = "private " ~ StoreType.stringof ~ " " ~ Name ~ ";"
133         ~ createFields!(Name, 0, Ts).result;
134 }
135 
136 private template createFields(string store, size_t offset, Ts...)
137 {
138     static if (Ts.length > 0)
139         enum result
140             = createAccessors!(store, Ts[0], Ts[1], Ts[2], offset).result
141             ~ createFields!(store, offset + Ts[2], Ts[3 .. $]).result;
142     else
143         enum result = "";
144 }
145 
146 private ulong getBitsForAlign()(ulong a)
147 {
148     ulong bits = 0;
149     while ((a & 0x01) == 0)
150     {
151         bits++;
152         a >>= 1;
153     }
154     assert(a == 1, "alignment is not a power of 2");
155     return bits;
156 }
157 
158 private template createReferenceAccessor(string store, T, ulong bits, string name)
159 {
160     import std.traits : CopyTypeQualifiers, PointerTarget;
161 
162     static if (is(T == class))
163         alias Q = T;
164     else
165         alias Q = PointerTarget!T;
166 
167     enum storageType = (CopyTypeQualifiers!(Q, void)*).stringof;
168     enum storage = "private " ~ storageType ~ ' ' ~ store ~ "_ptr;\n";
169     enum storage_accessor = "@property ref size_t " ~ store ~ "()() return @trusted pure nothrow @nogc const { "
170         ~ "return *cast(size_t*) &" ~ store ~ "_ptr;}\n"
171         ~ "@property void " ~ store ~ "()(size_t v) @trusted pure nothrow @nogc { "
172         ~ "" ~ store ~ "_ptr = cast(" ~ storageType ~ ") v;}\n";
173 
174     enum mask = (1UL << bits) - 1;
175     enum maskInv = ~mask;
176     // getter
177     enum ref_accessor = "@property "~T.stringof~" "~name~"()() @trusted pure nothrow @nogc const { auto result = "
178         ~ "("~store~" & "~ maskInv.stringof ~"); "
179         ~ "return cast("~T.stringof~") cast(" ~ storageType ~ ") result;}\n"
180     // setter
181         ~"@property void "~name~"()("~T.stringof~" v) @trusted pure nothrow @nogc { "
182         ~"assert(((cast(typeof("~store~")) cast(" ~ storageType ~ ") v) & "~ mask.stringof
183         ~`) == 0, "Value not properly aligned for '`~name~`'"); `
184         ~store~" = cast(typeof("~store~"))"
185         ~" (("~store~" & (cast(typeof("~store~")) "~ mask.stringof ~"))"
186         ~" | ((cast(typeof("~store~")) cast(" ~ storageType ~ ") v) & (cast(typeof("~store~")) "~ maskInv.stringof ~")));}\n";
187 
188     enum result = storage ~ storage_accessor ~ ref_accessor;
189 }
190 
191 private template sizeOfBitField(T...)
192 {
193     static if (T.length < 2)
194         enum sizeOfBitField = 0;
195     else
196         enum sizeOfBitField = T[2] + sizeOfBitField!(T[3 .. $]);
197 }
198 
199 private template createTaggedReference(T, ulong a, string name, Ts...)
200 {
201     static assert(
202         sizeOfBitField!Ts <= getBitsForAlign(a),
203         "Fields must fit in the bits know to be zero because of alignment."
204     );
205     enum StoreName = createStoreName!(T, name, 0, Ts);
206     enum result
207         = createReferenceAccessor!(StoreName, T, sizeOfBitField!Ts, name).result
208         ~ createFields!(StoreName, 0, Ts, size_t, "", T.sizeof * 8 - sizeOfBitField!Ts).result;
209 }
210 
211 /**
212 Allows creating bit fields inside $(D_PARAM struct)s and $(D_PARAM
213 class)es.
214 
215 Example:
216 
217 ----
218 struct A
219 {
220     int a;
221     mixin(bitfields!(
222         uint, "x",    2,
223         int,  "y",    3,
224         uint, "z",    2,
225         bool, "flag", 1));
226 }
227 A obj;
228 obj.x = 2;
229 obj.z = obj.x;
230 ----
231 
232 The example above creates a bitfield pack of eight bits, which fit in
233 one $(D_PARAM ubyte). The bitfields are allocated starting from the
234 least significant bit, i.e. x occupies the two least significant bits
235 of the bitfields storage.
236 
237 The sum of all bit lengths in one $(D_PARAM bitfield) instantiation
238 must be exactly 8, 16, 32, or 64. If padding is needed, just allocate
239 one bitfield with an empty name.
240 
241 Example:
242 
243 ----
244 struct A
245 {
246     mixin(bitfields!(
247         bool, "flag1",    1,
248         bool, "flag2",    1,
249         uint, "",         6));
250 }
251 ----
252 
253 The type of a bit field can be any integral type or enumerated
254 type. The most efficient type to store in bitfields is $(D_PARAM
255 bool), followed by unsigned types, followed by signed types.
256 */
257 
258 template bitfields(T...)
259 {
260     enum { bitfields = createStorageAndFields!T.result }
261 }
262 
263 /**
264 This string mixin generator allows one to create tagged pointers inside $(D_PARAM struct)s and $(D_PARAM class)es.
265 
266 A tagged pointer uses the bits known to be zero in a normal pointer or class reference to store extra information.
267 For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero.
268 One can store a 2-bit integer there.
269 
270 The example above creates a tagged pointer in the struct A. The pointer is of type
271 $(D uint*) as specified by the first argument, and is named x, as specified by the second
272 argument.
273 
274 Following arguments works the same way as $(D bitfield)'s. The bitfield must fit into the
275 bits known to be zero because of the pointer alignment.
276 */
277 
278 template taggedPointer(T : T*, string name, Ts...) {
279     enum taggedPointer = createTaggedReference!(T*, T.alignof, name, Ts).result;
280 }
281 
282 ///
283 @safe version(mir_core_test) unittest
284 {
285     struct A
286     {
287         int a;
288         mixin(taggedPointer!(
289             uint*, "x",
290             bool, "b1", 1,
291             bool, "b2", 1));
292     }
293     A obj;
294     obj.x = new uint;
295     obj.b1 = true;
296     obj.b2 = false;
297 }
298 
299 /**
300 This string mixin generator allows one to create tagged class reference inside $(D_PARAM struct)s and $(D_PARAM class)es.
301 
302 A tagged class reference uses the bits known to be zero in a normal class reference to store extra information.
303 For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero.
304 One can store a 2-bit integer there.
305 
306 The example above creates a tagged reference to an Object in the struct A. This expects the same parameters
307 as $(D taggedPointer), except the first argument which must be a class type instead of a pointer type.
308 */
309 
310 template taggedClassRef(T, string name, Ts...)
311 if (is(T == class))
312 {
313     enum taggedClassRef = createTaggedReference!(T, 8, name, Ts).result;
314 }
315 
316 ///
317 @safe version(mir_core_test) unittest
318 {
319     struct A
320     {
321         int a;
322         mixin(taggedClassRef!(
323             Object, "o",
324             uint, "i", 2));
325     }
326     A obj;
327     obj.o = new Object();
328     obj.i = 3;
329 }
330 
331 @safe pure nothrow @nogc
332 version(mir_core_test) unittest
333 {
334     // Degenerate bitfields (#8474 / #11160) tests mixed with range tests
335     struct Test1
336     {
337         mixin(bitfields!(uint, "a", 32,
338                         uint, "b", 4,
339                         uint, "c", 4,
340                         uint, "d", 8,
341                         uint, "e", 16,));
342 
343         static assert(Test1.b_min == 0);
344         static assert(Test1.b_max == 15);
345     }
346 
347     struct Test2
348     {
349         mixin(bitfields!(bool, "a", 0,
350                         ulong, "b", 64));
351 
352         static assert(Test2.b_min == ulong.min);
353         static assert(Test2.b_max == ulong.max);
354     }
355 
356     struct Test1b
357     {
358         mixin(bitfields!(bool, "a", 0,
359                         int, "b", 8));
360     }
361 
362     struct Test2b
363     {
364         mixin(bitfields!(int, "a", 32,
365                         int, "b", 4,
366                         int, "c", 4,
367                         int, "d", 8,
368                         int, "e", 16,));
369 
370         static assert(Test2b.b_min == -8);
371         static assert(Test2b.b_max == 7);
372     }
373 
374     struct Test3b
375     {
376         mixin(bitfields!(bool, "a", 0,
377                         long, "b", 64));
378 
379         static assert(Test3b.b_min == long.min);
380         static assert(Test3b.b_max == long.max);
381     }
382 
383     struct Test4b
384     {
385         mixin(bitfields!(long, "a", 32,
386                         int, "b", 32));
387     }
388 
389     // Sign extension tests
390     Test2b t2b;
391     Test4b t4b;
392     t2b.b = -5; assert(t2b.b == -5);
393     t2b.d = -5; assert(t2b.d == -5);
394     t2b.e = -5; assert(t2b.e == -5);
395     t4b.a = -5; assert(t4b.a == -5L);
396 }
397 
398 @system version(mir_core_test) unittest
399 {
400     struct Test5
401     {
402         mixin(taggedPointer!(
403             int*, "a",
404             uint, "b", 2));
405     }
406 
407     Test5 t5;
408     t5.a = null;
409     t5.b = 3;
410     assert(t5.a is null);
411     assert(t5.b == 3);
412 
413     int myint = 42;
414     t5.a = &myint;
415     assert(t5.a is &myint);
416     assert(t5.b == 3);
417 
418     struct Test6
419     {
420         mixin(taggedClassRef!(
421             Object, "o",
422             bool, "b", 1));
423     }
424 
425     Test6 t6;
426     t6.o = null;
427     t6.b = false;
428     assert(t6.o is null);
429     assert(t6.b == false);
430 
431     auto o = new Object();
432     t6.o = o;
433     t6.b = true;
434     assert(t6.o is o);
435     assert(t6.b == true);
436 }
437 
438 @safe version(mir_core_test) unittest
439 {
440     static assert(!__traits(compiles,
441         taggedPointer!(
442             int*, "a",
443             uint, "b", 3)));
444 
445     static assert(!__traits(compiles,
446         taggedClassRef!(
447             Object, "a",
448             uint, "b", 4)));
449 
450     struct S {
451         mixin(taggedClassRef!(
452             Object, "a",
453             bool, "b", 1));
454     }
455 
456     const S s;
457     void bar(S s) {}
458 
459     static assert(!__traits(compiles, bar(s)));
460 }
461 
462 @safe version(mir_core_test) unittest
463 {
464     // Bug #6686
465     union  S {
466         ulong bits = ulong.max;
467         mixin (bitfields!(
468             ulong, "back",  31,
469             ulong, "front", 33)
470         );
471     }
472     S num;
473 
474     num.bits = ulong.max;
475     num.back = 1;
476     assert(num.bits == 0xFFFF_FFFF_8000_0001uL);
477 }
478 
479 @safe version(mir_core_test) unittest
480 {
481     // Bug #5942
482     struct S
483     {
484         mixin(bitfields!(
485             int, "a" , 32,
486             int, "b" , 32
487         ));
488     }
489 
490     S data;
491     data.b = 42;
492     data.a = 1;
493     assert(data.b == 42);
494 }
495 
496 @safe version(mir_core_test) unittest
497 {
498     struct Test
499     {
500         mixin(bitfields!(bool, "a", 1,
501                          uint, "b", 3,
502                          short, "c", 4));
503     }
504 
505     @safe void test() pure nothrow
506     {
507         Test t;
508 
509         t.a = true;
510         t.b = 5;
511         t.c = 2;
512 
513         assert(t.a);
514         assert(t.b == 5);
515         assert(t.c == 2);
516     }
517 
518     test();
519 }
520 
521 @safe version(mir_core_test) unittest
522 {
523     {
524         static struct Integrals {
525             bool checkExpectations(bool eb, int ei, short es) { return b == eb && i == ei && s == es; }
526 
527             mixin(bitfields!(
528                       bool, "b", 1,
529                       uint, "i", 3,
530                       short, "s", 4));
531         }
532         Integrals i;
533         assert(i.checkExpectations(false, 0, 0));
534         i.b = true;
535         assert(i.checkExpectations(true, 0, 0));
536         i.i = 7;
537         assert(i.checkExpectations(true, 7, 0));
538         i.s = -8;
539         assert(i.checkExpectations(true, 7, -8));
540         i.s = 7;
541         assert(i.checkExpectations(true, 7, 7));
542     }
543 
544     //Bug# 8876
545     {
546         struct MoreIntegrals {
547             bool checkExpectations(uint eu, ushort es, uint ei) { return u == eu && s == es && i == ei; }
548 
549             mixin(bitfields!(
550                   uint, "u", 24,
551                   short, "s", 16,
552                   int, "i", 24));
553         }
554 
555         MoreIntegrals i;
556         assert(i.checkExpectations(0, 0, 0));
557         i.s = 20;
558         assert(i.checkExpectations(0, 20, 0));
559         i.i = 72;
560         assert(i.checkExpectations(0, 20, 72));
561         i.u = 8;
562         assert(i.checkExpectations(8, 20, 72));
563         i.s = 7;
564         assert(i.checkExpectations(8, 7, 72));
565     }
566 
567     enum A { True, False }
568     enum B { One, Two, Three, Four }
569     static struct Enums {
570         bool checkExpectations(A ea, B eb) { return a == ea && b == eb; }
571 
572         mixin(bitfields!(
573                   A, "a", 1,
574                   B, "b", 2,
575                   uint, "", 5));
576     }
577     Enums e;
578     assert(e.checkExpectations(A.True, B.One));
579     e.a = A.False;
580     assert(e.checkExpectations(A.False, B.One));
581     e.b = B.Three;
582     assert(e.checkExpectations(A.False, B.Three));
583 
584     static struct SingleMember {
585         bool checkExpectations(bool eb) { return b == eb; }
586 
587         mixin(bitfields!(
588                   bool, "b", 1,
589                   uint, "", 7));
590     }
591     SingleMember f;
592     assert(f.checkExpectations(false));
593     f.b = true;
594     assert(f.checkExpectations(true));
595 }
596 
597 // Issue 12477
598 @system version(mir_core_test) unittest
599 {
600     import std.algorithm.searching : canFind;
601     import mir.bitmanip : bitfields;
602     import core.exception : AssertError;
603 
604     static struct S
605     {
606         mixin(bitfields!(
607             uint, "a", 6,
608             int, "b", 2));
609     }
610 
611     S s;
612 
613     try { s.a = uint.max; assert(0); }
614     catch (AssertError ae)
615     { assert(ae.msg.canFind("Value is greater than the maximum value of bitfield 'a'"), ae.msg); }
616 
617     try { s.b = int.min;  assert(0); }
618     catch (AssertError ae)
619     { assert(ae.msg.canFind("Value is smaller than the minimum value of bitfield 'b'"), ae.msg); }
620 }
621 
622 @system version(mir_core_test) unittest
623 {
624     import core.atomic : atomicStore, atomicLoad, MO = MemoryOrder;
625 
626     static struct S
627     {
628         mixin(taggedPointer!(
629             shared(int)*, "si",
630             bool, "f", 1));
631 
632         this(shared(int)* ptr, bool flag)
633         {
634             si = ptr;
635             f = flag;
636         }
637     }
638 
639     shared static S s;
640     shared static int i;
641 
642     s.atomicStore!(MO.raw)(S(&i, true));
643     assert(s.atomicLoad!(MO.raw) == S(&i, true));
644 }