1 /** Lightweight versions of polymorphism packed inside one single 2 word/pointer. 3 4 Most significant bits are used to store type information. 5 6 These higher bits are normally unused on 64-bit systems (tested on 7 Linux). 16 higher bits are, on Linux, either 1 (kernel-space) or 0 (user-space). 8 9 See_Also: http://forum.dlang.org/post/sybuoliqhhefcovxjfjv@forum.dlang.org 10 11 TODO: Ask forum.dlang.org: Is it safe to assume that `typeBits` most significant bits of a 12 pointer are zero? If not put them in least significant part. 13 14 TODO: What todo with the fact that the GC will fail to scan WordVariant? 15 Can the GC be tweaked to mask out the type bits before scanning? 16 17 TODO: Enable support for `is null` instead of `isNull`? 18 19 TODO: Use `enforce()` instead of `assert()` in WordVariant:initialize() 20 21 TODO: Move to Phobos std.variant 22 */ 23 module nxt.variant_ex; 24 25 /** A variant of `Types` packed into a word (`size_t`). 26 * 27 * Suitable for use in tree-data containers, such as radix trees (tries), where 28 * hybrid value (sparsely packed sub-tree) and pointer (to dense sub-tree) 29 * packing of sub-nodes is needed. 30 */ 31 struct WordVariant(Types...) 32 { 33 import nxt.traits_ex : allSame, sizesOf; 34 static assert(allSame!(sizesOf!(Types)), "Types must have equal size"); 35 static assert(this.sizeof == (void*).sizeof, "Size must be same as word (pointer)"); 36 37 alias S = size_t; /+ TODO: templatize? +/ 38 39 import nxt.typecons_ex : makeEnumFromSymbolNames; 40 alias Ix = makeEnumFromSymbolNames!(`ix_`, ``, true, false, Types); 41 static assert(Ix.undefined == 0); 42 43 import nxt.bit_traits : bitsNeeded; 44 enum typeBits = bitsNeeded!(1 + Types.length); // number of bits needed to represent variant type, plus one for undefined state 45 enum typeShift = 8*S.sizeof - typeBits; 46 enum typeMask = cast(S)(2^^typeBits - 1) << typeShift; 47 48 import std.meta : staticIndexOf; 49 enum indexOf(T) = staticIndexOf!(T, Types); /+ TODO: cast to ubyte if Types.length is <= 256 +/ 50 51 /// Is `true` iff a `T` can be stored. 52 enum canStore(T) = indexOf!T >= 0; 53 54 pure: 55 56 @property string toString() const @trusted /+ TODO: pure nothrow +/ 57 { 58 import std.traits : isPointer; 59 import std.conv : to; 60 final switch (typeIndex) // typeIndex starts at 0 (undefined) 61 { 62 case 0: return "null"; 63 foreach (const i, T; Types) 64 { 65 case i + 1: 66 /+ TODO: improve this to look more like D-code: +/ 67 static if (isPointer!T) 68 return T.stringof ~ `@` ~ as!T.to!string; /+ TODO: ~ `:` ~ (*as!T).to!string; +/ 69 else 70 return T.stringof ~ `@` ~ as!T.to!string; 71 } 72 } 73 } 74 75 nothrow: 76 77 extern (D) auto toHash() const 78 { 79 import core.internal.hash : hashOf; 80 return _raw.hashOf; 81 } 82 83 @nogc: 84 85 /// Construction from `value`. 86 this(T)(T value) if (canStore!T) { initialize(value); } 87 /// ditto 88 this(typeof(null) value) { _raw = S.init; } 89 /// Construction from sub-variant `value`. 90 this(SubTypes...)(WordVariant!(SubTypes) value) { initializeFromSub(value); } 91 92 /// Assignment from `that`. 93 auto ref opAssign(typeof(this) value) { _raw = value._raw; return this; } 94 /// ditto 95 auto ref opAssign(T)(T that) if (canStore!T) { initialize(that); return this; } 96 /// ditto 97 auto ref opAssign(typeof(null) that) { _raw = S.init; return this; } 98 /// Assignment from sub-variant `value`. 99 auto ref opAssign(SubTypes...)(WordVariant!(SubTypes) value) { initializeFromSub(value); return this; } 100 101 pragma(inline): 102 103 /** Reference to peek of type `T`. */ 104 static private struct Ref(T) 105 { 106 S _raw; 107 const bool defined; 108 pragma(inline): 109 bool opCast(T : bool)() const { return defined; } 110 inout(T) opUnary(string op : `*`)() @trusted inout 111 { 112 auto data = (_raw & ~typeMask); // mask away type bits 113 return *(cast(typeof(return)*)&data); 114 } 115 } 116 117 @property inout(Ref!T) peek(T)() inout @trusted if (canStore!T) 118 { 119 if (!ofType!T) 120 return typeof(return).init; 121 return typeof(return)(_raw, true); 122 } 123 124 bool opEquals(WordVariant that) const 125 { 126 return _raw == that._raw; 127 } 128 129 bool opEquals(T)(T that) const 130 { 131 auto x = peek!T; // if `this` contains pointer of to instance of type `T` 132 return x && *x == that; // and is equal to it 133 } 134 135 bool isNull() const 136 { 137 return _raw == S.init; 138 } 139 140 bool opCast(T : bool)() const 141 { 142 return !isNull; 143 } 144 145 private void initialize(T)(T that) @trusted 146 { 147 const thatRaw = (*(cast(S*)(&that))); 148 assert(!(thatRaw & typeMask), `Top-most bits of parameter is already occupied`); /+ TODO: use enforce instead? +/ 149 _raw = (thatRaw | // data in lower part 150 (cast(S)(indexOf!T + 1) << typeShift)); // use higher bits for type information 151 } 152 153 private void initializeFromSub(SubTypes...)(WordVariant!(SubTypes) that) @trusted 154 { 155 import std.meta : staticIndexOf; 156 final switch (that.typeIndex) 157 { 158 case 0: 159 this._raw = 0; // propagate `undefined`/`null` 160 foreach (const i, SubType; SubTypes) 161 { 162 static assert(staticIndexOf!(SubType, Types) != -1, 163 "Subtype " ~ SubType.stringof ~ " must be part of the supertypes " ~ Types.stringof); 164 case i + 1: 165 this._raw = (that.rawValue | // raw value 166 (cast(S)(indexOf!SubType + 1) << typeShift)); // super type index 167 break; 168 } 169 } 170 } 171 172 private bool ofType(T)() const if (canStore!T) 173 { 174 return (!isNull && 175 typeIndex == indexOf!T + 1); 176 } 177 178 inout(T) as(T)() inout @trusted if (canStore!T) 179 { 180 // only in debug mode because it's meant to be called in conjunction with `typeIndex` 181 assert(ofType!T); 182 inout x = rawValue; 183 return *(cast(typeof(return)*)(cast(void*)&x)); // reinterpret 184 } 185 186 /** Get zero-offset index as `Ix` of current variant type. */ 187 Ix typeIx() const 188 { 189 return cast(typeof(return))typeIndex; 190 } 191 192 /** Get zero-offset index of current variant type. */ 193 private auto typeIndex() const 194 { 195 return ((_raw & typeMask) >> typeShift); 196 } 197 198 private S rawValue() const 199 { 200 return _raw & ~typeMask; 201 } 202 203 private S _raw; // raw untyped word 204 205 } 206 207 /* TODO: activate to figure out what makes Sample instance consume RAM */ 208 version (debugMemoryUsage) 209 { 210 struct S {} 211 struct T {} 212 alias Sample = WordVariant!(S, T); 213 } 214 215 /// 216 pure nothrow unittest { 217 import std.meta : AliasSeq; 218 219 alias SubType = WordVariant!(byte*, short*); 220 alias SuperType = WordVariant!(bool*, byte*, short*, long*); 221 222 byte* byteValue = cast(byte*)(0x11); 223 short* shortValue = cast(short*)(0x22); 224 225 SubType sub = byteValue; 226 assert(sub.typeIndex == 1); 227 assert(sub.peek!(byte*)); 228 assert(*(sub.peek!(byte*)) == byteValue); 229 230 SuperType sup = sub; 231 assert(sup.typeIndex == 2); 232 assert(sup.peek!(byte*)); 233 assert(*(sup.peek!(byte*)) == byteValue); 234 235 sub = shortValue; 236 assert(sub.typeIndex == 2); 237 assert(sub.peek!(short*)); 238 assert(*(sub.peek!(short*)) == shortValue); 239 240 sup = sub; 241 assert(sup.typeIndex == 3); 242 assert(sup.peek!(short*)); 243 assert(*(sup.peek!(short*)) == shortValue); 244 } 245 246 /// 247 pure nothrow unittest { 248 import std.meta : AliasSeq; 249 250 alias Types = AliasSeq!(byte*, short*, int*, long*, 251 ubyte*, ushort*, uint*, ulong*, 252 float*, double*, real*, 253 char*, wchar*, dchar*); 254 255 alias V = WordVariant!Types; 256 V v; 257 258 try 259 { 260 assert(v.toString == "null"); 261 } 262 catch (Exception e) {} 263 264 assert(v.isNull); 265 v = null; 266 assert(v.isNull); 267 assert(!v); 268 269 foreach (Tp; Types) 270 { 271 alias T = typeof(*Tp.init); 272 273 static assert(!__traits(compiles, { T[] a; v = &a; })); 274 static assert(!__traits(compiles, { v.peek!(T[]*); })); 275 276 // assignment from stack pointer 277 T a = 73; 278 T a_ = 73; 279 280 v = &a; 281 assert(v); 282 assert(!v.isNull); 283 assert(v.typeIndex != 0); 284 assert(v.ofType!Tp); 285 286 assert(v == &a); 287 assert(v != &a_); 288 assert(v); 289 290 foreach (Up; Types) 291 { 292 alias U = typeof(*Up.init); 293 static if (is(T == U)) 294 { 295 assert(v.peek!Up); 296 assert(*(v.peek!Up) == &a); 297 assert(v.as!Up == &a); 298 } 299 else 300 assert(!v.peek!Up); 301 } 302 303 // assignment from heap pointer 304 T* b = new T; 305 T* b_ = new T; 306 *b = 73; 307 *b_ = 73; 308 v = b; 309 assert(v == b); 310 assert(v != b_); 311 assert(v); 312 foreach (Up; Types) 313 { 314 alias U = typeof(*Up.init); 315 static if (is(T == U)) 316 { 317 assert(v.peek!Up); 318 assert(*(v.peek!Up) == b); 319 } 320 else 321 assert(!v.peek!Up); 322 } 323 324 } 325 } 326 327 /** A typed pointer to a variant of `Types`, packed into a word (`size_t`). 328 * 329 * See_Also: `std.bitmanip.taggedPointer`. 330 */ 331 struct VariantPointerTo(Types...) 332 { 333 static assert(this.sizeof == (void*).sizeof, "Size must be same as word (pointer)"); 334 335 alias S = size_t; /+ TODO: templatize? +/ 336 337 import nxt.bit_traits : bitsNeeded; 338 enum typeBits = bitsNeeded!(Types.length); // number of bits needed to represent variant type 339 enum typeShift = 8*S.sizeof - typeBits; 340 enum typeMask = cast(S)(2^^typeBits - 1) << typeShift; 341 342 import std.meta : staticIndexOf; 343 enum indexOf(T) = staticIndexOf!(T, Types); /+ TODO: cast to ubyte if Types.length is <= 256 +/ 344 345 /// Is `true` iff a pointer to a `T` can be stored. 346 enum canStorePointerTo(T) = indexOf!T >= 0; 347 348 pure: 349 350 @property string toString() const @trusted /+ TODO: pure +/ 351 { 352 import std.conv : to; 353 final switch (typeIndex) 354 { 355 foreach (const i, T; Types) 356 { 357 case i: return T.stringof ~ `@` ~ ptr.to!string; 358 } 359 } 360 } 361 362 nothrow: 363 364 extern (D) auto toHash() const 365 { 366 import core.internal.hash : hashOf; 367 return _raw.hashOf; 368 } 369 370 @nogc: 371 372 /// Construction from `value`. 373 this(T)(T* value) 374 if (canStorePointerTo!T) 375 { 376 init(value); 377 } 378 /// ditto 379 this(typeof(null) value) { /* null is the default */ } 380 381 /// Assignment from `that`. 382 auto ref opAssign(T)(T* that) 383 if (canStorePointerTo!T) 384 { 385 init(that); 386 return this; 387 } 388 /// ditto 389 auto ref opAssign(typeof(null) that) 390 { 391 _raw = S.init; 392 return this; 393 } 394 395 @property inout(void)* ptr() inout @trusted 396 { 397 return cast(void*)(_raw & ~typeMask); 398 } 399 400 @property inout(T)* peek(T)() inout @trusted if (canStorePointerTo!T) 401 { 402 static if (is(T == void)) 403 static assert(canStorePointerTo!T, `Cannot store a ` ~ T.stringof ~ ` in a ` ~ name); 404 if (!pointsTo!T) 405 return null; 406 return cast(inout T*)ptr; 407 } 408 409 bool opEquals(T)(T* that) const @trusted 410 { 411 auto x = peek!T; // if `this` contains pointer of to instance of type `T` 412 return (x && 413 x == that); // and is equal to it 414 } 415 416 bool isNull() const 417 { 418 return ptr is null; 419 } 420 421 bool opCast(T : bool)() const 422 { 423 return ptr !is null; 424 } 425 426 private void init(T)(T* that) 427 { 428 const thatRaw = cast(S)that; 429 assert(!(thatRaw & typeMask), `Top-most bits of pointer are already occupied`); /+ TODO: use enforce instead? +/ 430 _raw = (thatRaw | // pointer in lower part 431 (cast(S)(indexOf!T) << typeShift)); // use higher bits for type information 432 } 433 434 /** Get zero-offset index of current variant type. */ 435 private auto typeIndex() const 436 { 437 return (_raw & typeMask) >> typeShift; 438 } 439 440 private bool pointsTo(T)() const if (canStorePointerTo!T) 441 { 442 return typeIndex == indexOf!T; 443 } 444 445 private S _raw; // raw untyped word 446 447 } 448 449 /// 450 pure nothrow unittest { 451 import std.meta : AliasSeq; 452 453 alias Types = AliasSeq!(byte, short, int, long, 454 ubyte, ushort, uint, ulong, 455 float, double, real, 456 char, wchar, dchar); 457 458 alias V = VariantPointerTo!Types; 459 460 V v; 461 assert(v.isNull); 462 v = null; 463 assert(v.isNull); 464 assert(!v); 465 466 foreach (T; Types) 467 { 468 static assert(!__traits(compiles, { T[] a; v = &a; })); 469 static assert(!__traits(compiles, { v.peek!(T[]*); })); 470 471 // assignment from stack pointer 472 T a = 73; 473 T a_ = 73; 474 v = &a; 475 assert(v == &a); 476 assert(v != &a_); 477 assert(v); 478 foreach (U; Types) 479 { 480 static if (is(T == U)) 481 { 482 assert(v.peek!U); 483 assert(*(v.peek!U) == a); 484 } 485 else 486 assert(!v.peek!U); 487 } 488 489 // assignment from heap pointer 490 T* b = new T; 491 T* b_ = new T; 492 *b = 73; 493 *b_ = 73; 494 v = b; 495 assert(v == b); 496 assert(v != b_); 497 assert(v); 498 foreach (U; Types) 499 { 500 static if (is(T == U)) 501 { 502 assert(v.peek!U); 503 assert(*(v.peek!U) == *b); 504 } 505 else 506 assert(!v.peek!U); 507 } 508 509 } 510 }