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 { 218 import std.meta : AliasSeq; 219 220 alias SubType = WordVariant!(byte*, short*); 221 alias SuperType = WordVariant!(bool*, byte*, short*, long*); 222 223 byte* byteValue = cast(byte*)(0x11); 224 short* shortValue = cast(short*)(0x22); 225 226 SubType sub = byteValue; 227 assert(sub.typeIndex == 1); 228 assert(sub.peek!(byte*)); 229 assert(*(sub.peek!(byte*)) == byteValue); 230 231 SuperType sup = sub; 232 assert(sup.typeIndex == 2); 233 assert(sup.peek!(byte*)); 234 assert(*(sup.peek!(byte*)) == byteValue); 235 236 sub = shortValue; 237 assert(sub.typeIndex == 2); 238 assert(sub.peek!(short*)); 239 assert(*(sub.peek!(short*)) == shortValue); 240 241 sup = sub; 242 assert(sup.typeIndex == 3); 243 assert(sup.peek!(short*)); 244 assert(*(sup.peek!(short*)) == shortValue); 245 } 246 247 /// 248 pure nothrow unittest 249 { 250 import std.meta : AliasSeq; 251 252 alias Types = AliasSeq!(byte*, short*, int*, long*, 253 ubyte*, ushort*, uint*, ulong*, 254 float*, double*, real*, 255 char*, wchar*, dchar*); 256 257 alias V = WordVariant!Types; 258 V v; 259 260 try 261 { 262 assert(v.toString == "null"); 263 } 264 catch (Exception e) {} 265 266 assert(v.isNull); 267 v = null; 268 assert(v.isNull); 269 assert(!v); 270 271 foreach (Tp; Types) 272 { 273 alias T = typeof(*Tp.init); 274 275 static assert(!__traits(compiles, { T[] a; v = &a; })); 276 static assert(!__traits(compiles, { v.peek!(T[]*); })); 277 278 // assignment from stack pointer 279 T a = 73; 280 T a_ = 73; 281 282 v = &a; 283 assert(v); 284 assert(!v.isNull); 285 assert(v.typeIndex != 0); 286 assert(v.ofType!Tp); 287 288 assert(v == &a); 289 assert(v != &a_); 290 assert(v); 291 292 foreach (Up; Types) 293 { 294 alias U = typeof(*Up.init); 295 static if (is(T == U)) 296 { 297 assert(v.peek!Up); 298 assert(*(v.peek!Up) == &a); 299 assert(v.as!Up == &a); 300 } 301 else 302 assert(!v.peek!Up); 303 } 304 305 // assignment from heap pointer 306 T* b = new T; 307 T* b_ = new T; 308 *b = 73; 309 *b_ = 73; 310 v = b; 311 assert(v == b); 312 assert(v != b_); 313 assert(v); 314 foreach (Up; Types) 315 { 316 alias U = typeof(*Up.init); 317 static if (is(T == U)) 318 { 319 assert(v.peek!Up); 320 assert(*(v.peek!Up) == b); 321 } 322 else 323 assert(!v.peek!Up); 324 } 325 326 } 327 } 328 329 /** A typed pointer to a variant of `Types`, packed into a word (`size_t`). 330 * 331 * See_Also: `std.bitmanip.taggedPointer`. 332 */ 333 struct VariantPointerTo(Types...) 334 { 335 static assert(this.sizeof == (void*).sizeof, "Size must be same as word (pointer)"); 336 337 alias S = size_t; // TODO: templatize? 338 339 import nxt.bit_traits : bitsNeeded; 340 enum typeBits = bitsNeeded!(Types.length); // number of bits needed to represent variant type 341 enum typeShift = 8*S.sizeof - typeBits; 342 enum typeMask = cast(S)(2^^typeBits - 1) << typeShift; 343 344 import std.meta : staticIndexOf; 345 enum indexOf(T) = staticIndexOf!(T, Types); // TODO: cast to ubyte if Types.length is <= 256 346 347 /// Is `true` iff a pointer to a `T` can be stored. 348 enum canStorePointerTo(T) = indexOf!T >= 0; 349 350 pure: 351 352 @property string toString() const @trusted // TODO: pure 353 { 354 import std.conv : to; 355 final switch (typeIndex) 356 { 357 foreach (const i, T; Types) 358 { 359 case i: return T.stringof ~ `@` ~ ptr.to!string; 360 } 361 } 362 } 363 364 nothrow: 365 366 extern (D) auto toHash() const 367 { 368 import core.internal.hash : hashOf; 369 return _raw.hashOf; 370 } 371 372 @nogc: 373 374 /// Construction from `value`. 375 this(T)(T* value) 376 if (canStorePointerTo!T) 377 { 378 init(value); 379 } 380 /// ditto 381 this(typeof(null) value) { /* null is the default */ } 382 383 /// Assignment from `that`. 384 auto ref opAssign(T)(T* that) 385 if (canStorePointerTo!T) 386 { 387 init(that); 388 return this; 389 } 390 /// ditto 391 auto ref opAssign(typeof(null) that) 392 { 393 _raw = S.init; 394 return this; 395 } 396 397 @property inout(void)* ptr() inout @trusted 398 { 399 return cast(void*)(_raw & ~typeMask); 400 } 401 402 @property inout(T)* peek(T)() inout @trusted if (canStorePointerTo!T) 403 { 404 static if (is(T == void)) 405 static assert(canStorePointerTo!T, `Cannot store a ` ~ T.stringof ~ ` in a ` ~ name); 406 if (!pointsTo!T) 407 return null; 408 return cast(inout T*)ptr; 409 } 410 411 bool opEquals(T)(T* that) const @trusted 412 { 413 auto x = peek!T; // if `this` contains pointer of to instance of type `T` 414 return (x && 415 x == that); // and is equal to it 416 } 417 418 bool isNull() const 419 { 420 return ptr is null; 421 } 422 423 bool opCast(T : bool)() const 424 { 425 return ptr !is null; 426 } 427 428 private void init(T)(T* that) 429 { 430 const thatRaw = cast(S)that; 431 assert(!(thatRaw & typeMask), `Top-most bits of pointer are already occupied`); // TODO: use enforce instead? 432 _raw = (thatRaw | // pointer in lower part 433 (cast(S)(indexOf!T) << typeShift)); // use higher bits for type information 434 } 435 436 /** Get zero-offset index of current variant type. */ 437 private auto typeIndex() const 438 { 439 return (_raw & typeMask) >> typeShift; 440 } 441 442 private bool pointsTo(T)() const if (canStorePointerTo!T) 443 { 444 return typeIndex == indexOf!T; 445 } 446 447 private S _raw; // raw untyped word 448 449 } 450 451 /// 452 pure nothrow unittest 453 { 454 import std.meta : AliasSeq; 455 456 alias Types = AliasSeq!(byte, short, int, long, 457 ubyte, ushort, uint, ulong, 458 float, double, real, 459 char, wchar, dchar); 460 461 alias V = VariantPointerTo!Types; 462 463 V v; 464 assert(v.isNull); 465 v = null; 466 assert(v.isNull); 467 assert(!v); 468 469 foreach (T; Types) 470 { 471 static assert(!__traits(compiles, { T[] a; v = &a; })); 472 static assert(!__traits(compiles, { v.peek!(T[]*); })); 473 474 // assignment from stack pointer 475 T a = 73; 476 T a_ = 73; 477 v = &a; 478 assert(v == &a); 479 assert(v != &a_); 480 assert(v); 481 foreach (U; Types) 482 { 483 static if (is(T == U)) 484 { 485 assert(v.peek!U); 486 assert(*(v.peek!U) == a); 487 } 488 else 489 assert(!v.peek!U); 490 } 491 492 // assignment from heap pointer 493 T* b = new T; 494 T* b_ = new T; 495 *b = 73; 496 *b_ = 73; 497 v = b; 498 assert(v == b); 499 assert(v != b_); 500 assert(v); 501 foreach (U; Types) 502 { 503 static if (is(T == U)) 504 { 505 assert(v.peek!U); 506 assert(*(v.peek!U) == *b); 507 } 508 else 509 assert(!v.peek!U); 510 } 511 512 } 513 }