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 }