1 module nxt.packed_string; 2 3 /** String packed into one word. 4 * 5 * Length is stored in upper `lengthBits` of pointer/word `_raw`. 6 * 7 * All length bits set means memory bits of `_raw` points to `string`. This 8 * makes `PackedString` `nothrow`. 9 * 10 * TODO: If D's GC doesn't ignore the upper `lengthBits` bits of pointer, make 11 * `core.memory.GC` be told that every `PackedString` should be bit-anded with 12 * `addressMask` before scanned for an address. Related functions: 13 * - isLarge. 14 * 15 * Proposed API for specifying this is: `__traits(adressBitMask, declaration, 16 * mask)` where `declaration.sizeof == size_t.sizeof`. 17 */ 18 @safe struct PackedString { 19 enum totalBits = 8 * _raw.sizeof; 20 21 /++ Number of bits used to store length. 22 2024-01-01: Address space of a single Linux user process is 47 bits. 23 +/ 24 enum lengthBits = 16; 25 26 /// Number of bits used to memory address. 27 enum addressBits = totalBits - lengthBits; 28 29 /// Capacity of small variant where `length` fits in `lengthBits`. 30 public enum smallCapacity = (2 ^^ lengthBits - 1) - 1; 31 32 /// Bit mask of length part. 33 enum size_t lengthMask = (cast(size_t)(2^^lengthBits - 1)) << addressBits; 34 35 /// Bit mask of address part. 36 enum size_t addressMask = ~lengthMask; 37 38 alias Large = immutable(char)[]; 39 40 pure nothrow @nogc: 41 42 this(in string x) @trusted in(!((cast(size_t)x.ptr) & lengthMask)) { 43 if (x.length <= smallCapacity) 44 _raw = cast(size_t)(x.ptr) | (x.length << addressBits); 45 else { 46 assert(0, "TODO: implement this"); 47 // string* y = new string; /+ TODO: how do I do this? +/ 48 // *y = x; 49 // _raw = cast(size_t)(x.ptr) | (x.length << addressBits); 50 } 51 } 52 53 /** Returns: `true` iff this is a large string, otherwise `false.` */ 54 @property bool isLarge() const scope @trusted 55 { 56 version (D_Coverage) {} else pragma(inline, true); 57 return (_raw & lengthMask) == (2^^lengthBits) - 1; 58 } 59 60 /// Get pointer to characters. 61 immutable(char)* ptr() const @property @trusted 62 => cast(typeof(return))(_raw & addressMask); 63 64 /// Get length. 65 size_t length() const @property @safe => (cast(size_t)_raw) >> addressBits; 66 67 /// Get slice. 68 string opSlice() const @property @trusted => ptr[0 .. length]; 69 70 void toString(Sink)(ref scope Sink sink) const @property scope => sink(opSlice()); 71 72 alias opSlice this; 73 74 private size_t _raw; 75 } 76 77 version (unittest) { 78 static assert(PackedString.sizeof == size_t.sizeof); 79 static assert(PackedString.totalBits == 64); 80 static assert(PackedString.addressBits == 48); 81 static assert(PackedString.smallCapacity == 65534); 82 static assert(PackedString.lengthMask == 0xffff_0000_0000_0000); 83 static assert(PackedString.addressMask == 0x0000_ffff_ffff_ffff); 84 } 85 86 /// 87 pure @safe unittest { 88 const s = "alpha"; 89 PackedString p = s; 90 assert(p.ptr == s.ptr); 91 assert(p.length == s.length); 92 assert(p[] == s); 93 assert(p == s); 94 } 95 96 /// 97 pure @safe unittest { 98 string s; 99 s.length = PackedString.smallCapacity; 100 PackedString p = s; 101 assert(p.ptr == s.ptr); 102 assert(p.length == s.length); 103 assert(p[] == s); 104 assert(p == s); 105 assert(p is s); 106 }