1 module nxt.bit_traits; 2 3 static if (__VERSION__ >= 2083) 4 { 5 version(LDC) static assert(!__traits(compiles, { enum _ = __traits(isZeroInit, T); }), 6 "Remove checks for __traits(compiles, { enum _ = __traits(isZeroInit, T); }) now that it compiles with LDC"); 7 } 8 9 /** Get number of bits needed to represent the range (0 .. `length`-1). 10 */ 11 template bitsNeeded(size_t length) 12 { 13 // TODO: optimize by removing need for a linear search 14 static if (length <= 2) { enum bitsNeeded = 1; } 15 else static if (length <= 4) { enum bitsNeeded = 2; } 16 else static if (length <= 8) { enum bitsNeeded = 3; } 17 else static if (length <= 16) { enum bitsNeeded = 4; } 18 else static if (length <= 32) { enum bitsNeeded = 5; } 19 else static if (length <= 64) { enum bitsNeeded = 6; } 20 else static if (length <= 128) { enum bitsNeeded = 7; } 21 else static if (length <= 256) { enum bitsNeeded = 8; } 22 else static if (length <= 512) { enum bitsNeeded = 9; } 23 else { static assert(0, `Too large length`); } 24 } 25 26 /** Number of bits required to store a packed instance of `T`. 27 See_Also: http://forum.dlang.org/thread/okonqhnxzqlqtxijxsfg@forum.dlang.org 28 29 TODO: Extend to continuous version; use std.numeric.sumOfLog2s. Ask on 30 StackExchange Computer Science for the correct terminology. 31 32 See: http://dlang.org/phobos/std_numeric.html#.sumOfLog2s 33 34 TODO: merge with `UsageOf` 35 */ 36 template packedBitSizeOf(T) 37 { 38 static if (is(T == enum)) 39 { 40 static assert(T.min != T.max, "enum T must have at least two enumerators"); 41 import core.bitop : bsr; 42 enum range = T.max - T.min; // TODO: use uniqueEnumMembers.length instead? 43 enum packedBitSizeOf = range.bsr + 1; 44 } 45 // TODO 46 // else static if (isAggregate!T) 47 // { 48 // foreach (E; T.tupleof) 49 // { 50 // ....; 51 // } 52 // } 53 else 54 { 55 enum packedBitSizeOf = 8*T.sizeof; 56 } 57 } 58 59 @safe pure nothrow @nogc unittest 60 { 61 static assert(packedBitSizeOf!ubyte == 8); 62 static assert(!__traits(compiles, 63 { 64 enum E1 { x } static assert(packedBitSizeOf!E1 == 1); 65 })); 66 enum E2 { x, y } 67 static assert(packedBitSizeOf!E2 == 1); 68 enum E3 { x, y, z } 69 static assert(packedBitSizeOf!E3 == 2); 70 enum E4 { x, y, z, w } 71 static assert(packedBitSizeOf!E4 == 2); 72 enum E5 { a, b, c, d, e } 73 static assert(packedBitSizeOf!E5 == 3); 74 enum E6 { a, b, c, d, e, f } 75 static assert(packedBitSizeOf!E6 == 3); 76 enum E7 { a, b, c, d, e, f, g } 77 static assert(packedBitSizeOf!E7 == 3); 78 enum E8 { a, b, c, d, e, f, g, h } 79 static assert(packedBitSizeOf!E8 == 3); 80 enum E9 { a, b, c, d, e, f, g, h, i } 81 static assert(packedBitSizeOf!E9 == 4); 82 } 83 84 85 /+ Is the representation of `T.init` known at compile time to consist of nothing 86 + but zero bits? Padding between a struct's fields is not considered. 87 +/ 88 template isInitAllZeroBits(T) 89 { 90 static if (__traits(compiles, { enum _ = __traits(isZeroInit, T); })) 91 { 92 enum isInitAllZeroBits = __traits(isZeroInit, T); 93 // pragma(msg, "TODO: use `enum isInitAllZeroBits = __traits(isZeroInit, T);` here and in `isAllZeroBits` and remove the test of isInitAllZeroBits"); 94 } 95 else static if (T.sizeof == 0) 96 { 97 enum isInitAllZeroBits = true; 98 } 99 else 100 { 101 import std.traits : isStaticArray; 102 static if (isStaticArray!T && __traits(compiles, T.init[0])) 103 { 104 enum isInitAllZeroBits = __traits(compiles, { 105 static assert(isAllZeroBits!(typeof(T.init[0]), T.init[0])); 106 }); 107 } 108 else 109 { 110 enum isInitAllZeroBits = __traits(compiles, { 111 static assert(isAllZeroBits!(T, T.init)); 112 }); 113 } 114 } 115 } 116 117 @nogc nothrow pure @safe unittest 118 { 119 static assert(isInitAllZeroBits!int); 120 static assert(isInitAllZeroBits!(Object)); 121 static assert(isInitAllZeroBits!(void*)); 122 static assert(isInitAllZeroBits!uint); 123 static assert(isInitAllZeroBits!(uint[2])); 124 static assert(isInitAllZeroBits!(string)); 125 static assert(isInitAllZeroBits!(wstring)); 126 static assert(isInitAllZeroBits!(dstring)); 127 128 static assert(!isInitAllZeroBits!float); 129 // static assert(isInitAllZeroBits!(float[0])); 130 static assert(!isInitAllZeroBits!(float[2])); 131 132 static struct S1 133 { 134 int a; 135 } 136 static assert(isInitAllZeroBits!S1); 137 138 static struct S2 139 { 140 int a = 1; 141 } 142 static assert(!isInitAllZeroBits!S2); 143 144 static struct S3 145 { 146 S1 a; 147 int b; 148 } 149 static assert(isInitAllZeroBits!S3); 150 static assert(isInitAllZeroBits!(S3[2])); 151 152 static struct S4 153 { 154 S1 a; 155 S2 b; 156 } 157 static assert(!isInitAllZeroBits!S4); 158 159 static struct S5 160 { 161 real r = 0; 162 } 163 static assert(isInitAllZeroBits!S5); 164 165 static struct S6 166 { 167 168 } 169 static assert(isInitAllZeroBits!S6); 170 171 static struct S7 172 { 173 float[0] a; 174 } 175 // TODO: static assert(isInitAllZeroBits!S7); 176 177 static class C1 178 { 179 int a = 1; 180 } 181 static assert(isInitAllZeroBits!C1); 182 183 // Ensure Tuple can be read. 184 import std.typecons : Tuple; 185 static assert(isInitAllZeroBits!(Tuple!(int, int))); 186 static assert(!isInitAllZeroBits!(Tuple!(float, float))); 187 188 // Ensure private fields of structs from other modules 189 // are taken into account. 190 import std.random : Mt19937; 191 static assert(!isInitAllZeroBits!Mt19937); 192 // Check that it works with const. 193 static assert(isInitAllZeroBits!(const(Mt19937)) == isInitAllZeroBits!Mt19937); 194 static assert(isInitAllZeroBits!(const(S5)) == isInitAllZeroBits!S5); 195 } 196 197 /+ Can the representation be determined at compile time to consist of nothing 198 + but zero bits? Padding between a struct's fields is not considered. 199 +/ 200 template isAllZeroBits(T, T value) 201 { 202 static if ((is(T == class) || is(T == typeof(null))) && // need this special case 203 value is null) // because pointer must be compared with `is` instead of `==` for `SSOString` case below 204 { 205 enum isAllZeroBits = true; 206 } 207 else static if (value == T.init && // NOTE `value is T.init` crashes compiler for `SSOString` 208 __traits(compiles, { enum _ = __traits(isZeroInit, T); })) 209 { 210 enum isAllZeroBits = __traits(isZeroInit, T); 211 } 212 else 213 { 214 // pragma(msg, "T: ", T.stringof, " value:", value); 215 import std.traits : isDynamicArray, isStaticArray; 216 static if (isDynamicArray!(T)) 217 { 218 enum isAllZeroBits = value is null && value.length is 0; 219 } 220 else static if (is(typeof(value is null))) 221 { 222 enum isAllZeroBits = value is null; 223 } 224 else static if (is(typeof(value is 0))) 225 { 226 enum isAllZeroBits = value is 0; 227 } 228 else static if (isStaticArray!(T)) 229 { 230 enum isAllZeroBits = () 231 { 232 // Use index so this works when T.length is 0. 233 static foreach (i; 0 .. T.length) 234 { 235 if (!isAllZeroBits!(typeof(value[i]), value[i])) 236 return false; 237 } 238 return true; 239 }(); 240 } 241 else static if (is(T == struct) || 242 is(T == union)) 243 { 244 enum isAllZeroBits = () 245 { 246 static foreach (e; value.tupleof) 247 { 248 if (!isAllZeroBits!(typeof(e), e)) 249 return false; 250 } 251 return true; 252 }(); 253 } 254 else 255 enum isAllZeroBits = false; 256 } 257 } 258 259 @nogc nothrow pure @safe unittest 260 { 261 static assert(isAllZeroBits!(int, 0)); 262 static assert(!isAllZeroBits!(int, 1)); 263 264 import std.meta : AliasSeq; 265 foreach (Float; AliasSeq!(float, double, real)) 266 { 267 assert(isAllZeroBits!(Float, 0.0)); 268 assert(!isAllZeroBits!(Float, -0.0)); 269 assert(!isAllZeroBits!(Float, Float.nan)); 270 } 271 272 static assert(isAllZeroBits!(void*, null)); 273 static assert(isAllZeroBits!(int*, null)); 274 static assert(isAllZeroBits!(Object, null)); 275 } 276 277 /+ Can the representation be determined at compile time to consist of nothing 278 but 1 bits? This is reported as $(B false) for structs with padding between 279 their fields because `opEquals` and hashing may rely on those bits being zero. 280 281 Note: 282 A bool occupies 8 bits so `isAllOneBits!(bool, true) == false` 283 284 See_Also: 285 https://forum.dlang.org/post/hn11oh$1usk$1@digitalmars.com 286 https://github.com/dlang/phobos/pull/6024 287 +/ 288 template isAllOneBits(T, T value) 289 { 290 import std.traits : isIntegral, isSomeChar, Unsigned; 291 static if (isIntegral!T || isSomeChar!T) 292 { 293 import core.bitop : popcnt; 294 static if (T.min < T(0)) 295 enum isAllOneBits = popcnt(cast(Unsigned!T) value) == T.sizeof * 8; 296 else 297 enum isAllOneBits = popcnt(value) == T.sizeof * 8; 298 } 299 else static if (__traits(isStaticArray, typeof(value))) 300 { 301 enum isAllOneBits = () 302 { 303 bool b = true; 304 // Use index so this works when T.length is 0. 305 static foreach (i; 0 .. T.length) 306 { 307 b &= isAllOneBits!(typeof(value[i]), value[i]); 308 if (b == false) 309 return b; 310 } 311 312 return b; 313 }(); 314 } 315 else static if (is(typeof(value) == struct)) 316 { 317 enum isAllOneBits = () 318 { 319 bool b = true; 320 size_t fieldSizeSum = 0; 321 static foreach (e; value.tupleof) 322 { 323 b &= isAllOneBits!(typeof(e), e); 324 if (b == false) 325 return b; 326 fieldSizeSum += typeof(e).sizeof; 327 } 328 // If fieldSizeSum == T.sizeof then there can be no gaps 329 // between fields. 330 return b && fieldSizeSum == T.sizeof; 331 }(); 332 } 333 else 334 { 335 enum isAllOneBits = false; 336 } 337 } 338 339 @nogc nothrow pure @safe unittest 340 { 341 static assert(isAllOneBits!(char, 0xff)); 342 static assert(isAllOneBits!(wchar, 0xffff)); 343 static assert(isAllOneBits!(byte, cast(byte) 0xff)); 344 static assert(isAllOneBits!(int, 0xffff_ffff)); 345 static assert(isAllOneBits!(char[4], [0xff, 0xff, 0xff, 0xff])); 346 347 static assert(!isAllOneBits!(bool, true)); 348 static assert(!isAllOneBits!(wchar, 0xff)); 349 static assert(!isAllOneBits!(Object, Object.init)); 350 } 351 352 /+ Can the representation be determined at compile time to consist of nothing 353 but 1 bits? This is reported as $(B false) for structs with padding between 354 their fields because `opEquals` and hashing may rely on those bits being zero. 355 356 See_Also: 357 https://forum.dlang.org/post/hn11oh$1usk$1@digitalmars.com 358 https://github.com/dlang/phobos/pull/6024 359 +/ 360 template isInitAllOneBits(T) 361 { 362 static if (__traits(isStaticArray, T) && __traits(compiles, T.init[0])) 363 enum isInitAllOneBits = __traits(compiles, { 364 static assert(isAllOneBits!(typeof(T.init[0]), T.init[0])); 365 }); 366 else 367 enum isInitAllOneBits = __traits(compiles, { 368 static assert(isAllOneBits!(T, T.init)); 369 }); 370 } 371 372 @nogc nothrow pure @safe unittest 373 { 374 static assert(isInitAllOneBits!char); 375 static assert(isInitAllOneBits!wchar); 376 static assert(!isInitAllOneBits!dchar); 377 378 static assert(isInitAllOneBits!(char[4])); 379 static assert(!isInitAllOneBits!(int[4])); 380 static assert(!isInitAllOneBits!Object); 381 382 static struct S1 383 { 384 char a; 385 char b; 386 } 387 static assert(isInitAllOneBits!S1); 388 389 static struct S2 390 { 391 char a = 1; 392 } 393 static assert(!isInitAllOneBits!S2); 394 395 static struct S3 396 { 397 S1 a; 398 char b; 399 } 400 static assert(isInitAllOneBits!S3); 401 static assert(isInitAllOneBits!(S3[2])); 402 403 static struct S4 404 { 405 S1 a; 406 S2 b; 407 } 408 static assert(!isInitAllOneBits!S4); 409 410 static struct Sshort 411 { 412 short r = cast(short)0xffff; 413 } 414 static assert(isInitAllOneBits!Sshort); 415 416 static struct Sint 417 { 418 int r = 0xffff_ffff; 419 } 420 static assert(isInitAllOneBits!Sint); 421 422 static struct Slong 423 { 424 long r = 0xffff_ffff_ffff_ffff; 425 } 426 static assert(isInitAllOneBits!Slong); 427 428 // Verify that when there is padding between fields isInitAllOneBits is false. 429 static struct S10 430 { 431 align(4) char a; 432 align(4) char b; 433 } 434 static assert(!isInitAllOneBits!S10); 435 436 static class C1 437 { 438 char c; 439 } 440 static assert(!isInitAllOneBits!C1); 441 }