1 module eventcore.internal.utils; 2 3 import core.memory : GC; 4 import std.traits : hasIndirections; 5 import taggedalgebraic; 6 7 8 void print(ARGS...)(string str, ARGS args) 9 @trusted @nogc nothrow { 10 import std.format : formattedWrite; 11 StdoutRange r; 12 scope cb = () { 13 try (&r).formattedWrite(str, args); 14 catch (Exception e) assert(false, e.msg); 15 }; 16 (cast(void delegate() @nogc @safe nothrow)cb)(); 17 r.put('\n'); 18 } 19 20 T mallocT(T, ARGS...)(ARGS args) 21 { 22 import core.stdc.stdlib : malloc; 23 import std.conv : emplace; 24 25 T ret = () @trusted { 26 enum size = __traits(classInstanceSize, T); 27 T r = cast(T)malloc(size); 28 static if (hasIndirections!T) 29 GC.addRange(cast(void*)r, size); 30 return r; 31 }(); 32 return emplace!T(ret, args); 33 } 34 35 void freeT(T)(ref T inst) @nogc 36 if (is(T == class)) 37 { 38 import core.stdc.stdlib : free; 39 40 if (!inst) return; 41 42 noGCDestroy(inst); 43 static if (hasIndirections!T) 44 GC.removeRange(cast(void*)inst); 45 free(cast(void*)inst); 46 inst = null; 47 } 48 49 T[] mallocNT(T)(size_t cnt) 50 @trusted { 51 import core.stdc.stdlib : malloc; 52 import std.conv : emplace; 53 54 auto ret = (cast(T*)malloc(T.sizeof * cnt))[0 .. cnt]; 55 static if (hasIndirections!T) 56 GC.addRange(cast(void*)ret, T.sizeof * cnt); 57 foreach (ref v; ret) 58 static if (!is(T == class)) 59 emplace!T(&v); 60 else v = null; 61 return ret; 62 } 63 64 void freeNT(T)(ref T[] arr) 65 { 66 import core.stdc.stdlib : free; 67 68 foreach (ref v; arr) 69 static if (!is(T == class)) 70 destroy(v); 71 static if (hasIndirections!T) 72 GC.removeRange(arr.ptr); 73 free(arr.ptr); 74 arr = null; 75 } 76 77 private void noGCDestroy(T)(ref T t) 78 @trusted { 79 // FIXME: only do this if the destructor chain is actually nogc 80 scope doit = { destroy(t); }; 81 (cast(void delegate() @nogc)doit)(); 82 } 83 84 private extern(C) Throwable.TraceInfo _d_traceContext(void* ptr = null); 85 86 void nogc_assert(bool cond, string message, string file = __FILE__, int line = __LINE__) 87 @trusted nothrow @nogc { 88 import core.stdc.stdlib : abort; 89 import std.stdio : stderr; 90 91 if (!cond) { 92 scope (exit) { 93 abort(); 94 assert(false); 95 } 96 97 scope doit = { 98 stderr.writefln("Assertion failure @%s(%s): %s", file, line, message); 99 stderr.writeln("------------------------"); 100 if (auto info = _d_traceContext(null)) { 101 foreach (s; info) 102 stderr.writeln(s); 103 } else stderr.writeln("no stack trace available"); 104 }; 105 (cast(void delegate() @nogc)doit)(); // write and _d_traceContext are not nogc 106 } 107 } 108 109 struct StdoutRange { 110 @safe: @nogc: nothrow: 111 import core.stdc.stdio; 112 113 void put(string str) 114 { 115 () @trusted { fwrite(str.ptr, str.length, 1, stderr); } (); 116 } 117 118 void put(char ch) 119 { 120 () @trusted { fputc(ch, stderr); } (); 121 } 122 } 123 124 struct ChoppedVector(T, size_t CHUNK_SIZE = 16*64*1024/nextPOT(T.sizeof)) { 125 static assert(nextPOT(CHUNK_SIZE) == CHUNK_SIZE, 126 "CHUNK_SIZE must be a power of two for performance reasons."); 127 128 @safe: nothrow: 129 import core.stdc.stdlib : calloc, free, malloc, realloc; 130 131 alias chunkSize = CHUNK_SIZE; 132 133 private { 134 alias Chunk = T[chunkSize]; 135 alias ChunkPtr = Chunk*; 136 ChunkPtr[] m_chunks; 137 size_t m_chunkCount; 138 size_t m_length; 139 } 140 141 @disable this(this); 142 143 ~this() 144 @nogc { 145 clear(); 146 } 147 148 @property size_t length() const @nogc { return m_length; } 149 150 void clear() 151 @nogc { 152 () @trusted { 153 foreach (i; 0 .. m_chunkCount) { 154 destroy(*m_chunks[i]); 155 static if (hasIndirections!T) 156 GC.removeRange(m_chunks[i]); 157 free(m_chunks[i]); 158 } 159 free(m_chunks.ptr); 160 m_chunks = null; 161 } (); 162 m_chunkCount = 0; 163 m_length = 0; 164 } 165 166 ref T opIndex(size_t index) 167 @nogc { 168 auto chunk = index / chunkSize; 169 auto subidx = index % chunkSize; 170 if (index >= m_length) m_length = index+1; 171 reserveChunk(chunk); 172 return (*m_chunks[chunk])[subidx]; 173 } 174 175 ref const(T) opIndex(size_t index) 176 const @nogc { 177 static immutable T emptySlot; 178 179 auto chunk = index / chunkSize; 180 auto subidx = index % chunkSize; 181 if (index >= m_length) return emptySlot; 182 auto c = m_chunks[chunk]; 183 if (!c) return emptySlot; 184 return (*c)[subidx]; 185 } 186 187 int opApply(scope int delegate(size_t idx, ref T) @safe nothrow del) 188 { 189 size_t idx = 0; 190 outer: 191 foreach (c; m_chunks) { 192 if (c) { 193 foreach (i, ref t; *c) { 194 if (auto ret = del(idx+i, t)) 195 return ret; 196 if (i + idx >= length) 197 break outer; 198 } 199 } 200 idx += chunkSize; 201 } 202 return 0; 203 } 204 205 int opApply(scope int delegate(size_t idx, ref const(T)) @safe nothrow del) 206 const { 207 size_t idx = 0; 208 outer: 209 foreach (c; m_chunks) { 210 if (c) { 211 foreach (i, ref t; *c) { 212 if (auto ret = del(idx+i, t)) 213 return ret; 214 if (i + idx >= length) 215 break outer; 216 } 217 } 218 idx += chunkSize; 219 } 220 return 0; 221 } 222 223 private void reserveChunk(size_t chunkidx) 224 @nogc { 225 if (m_chunks.length <= chunkidx) { 226 auto l = m_chunks.length == 0 ? 64 : m_chunks.length; 227 while (l <= chunkidx) l *= 2; 228 () @trusted { 229 auto newptr = cast(ChunkPtr*)realloc(m_chunks.ptr, l * ChunkPtr.length); 230 assert(newptr !is null, "Failed to allocate chunk index!"); 231 newptr[m_chunks.length .. l] = ChunkPtr.init; 232 m_chunks = newptr[0 .. l]; 233 } (); 234 } 235 236 while (m_chunkCount <= chunkidx) { 237 () @trusted { 238 auto ptr = cast(ChunkPtr)calloc(chunkSize, T.sizeof); 239 assert(ptr !is null, "Failed to allocate chunk!"); 240 // FIXME: initialize with T.init instead of 0 241 static if (hasIndirections!T) 242 GC.addRange(ptr, chunkSize * T.sizeof); 243 m_chunks[m_chunkCount++] = ptr; 244 } (); 245 } 246 } 247 } 248 249 struct AlgebraicChoppedVector(TCommon, TSpecific...) 250 { 251 import std.conv : to; 252 import std.meta : AliasSeq; 253 254 union U { 255 typeof(null) none; 256 mixin fields!0; 257 } 258 alias FieldType = TaggedAlgebraic!U; 259 static struct FullField { 260 TCommon common; 261 FieldType specific; 262 mixin(accessors()); 263 } 264 265 ChoppedVector!(FullField) items; 266 267 alias items this; 268 269 private static string accessors() 270 { 271 import std.format : format; 272 string ret; 273 foreach (i, U; TSpecific) 274 ret ~= "@property ref TSpecific[%s] %s() nothrow @safe @nogc { return this.specific.get!(TSpecific[%s]); }\n" 275 .format(i, U.Handle.name, i); 276 return ret; 277 } 278 279 private mixin template fields(size_t i) { 280 static if (i < TSpecific.length) { 281 mixin("TSpecific["~i.to!string~"] "~TSpecific[i].Handle.name~";"); 282 mixin fields!(i+1); 283 } 284 } 285 } 286 287 288 289 /** Efficient bit set of dynamic size. 290 */ 291 struct SmallIntegerSet(V : size_t) 292 { 293 private { 294 uint[][4] m_bits; 295 size_t m_count; 296 } 297 298 @disable this(this); 299 300 @property bool empty() const { return m_count == 0; } 301 302 void insert(V i) 303 { 304 assert(i >= 0); 305 foreach (j; 0 .. m_bits.length) { 306 uint b = 1u << (i%32); 307 i /= 32; 308 if (i >= m_bits[j].length) 309 m_bits[j].length = nextPOT(i+1); 310 if (j == 0 && !(m_bits[j][i] & b)) m_count++; 311 m_bits[j][i] |= b; 312 } 313 } 314 315 void remove(V i) 316 { 317 assert(i >= 0); 318 if (i >= m_bits[0].length * 32) return; 319 320 foreach (j; 0 .. m_bits.length) { 321 uint b = 1u << (i%32); 322 i /= 32; 323 if (!m_bits[j][i]) break; 324 if (j == 0 && m_bits[j][i] & b) m_count--; 325 m_bits[j][i] &= ~b; 326 if (m_bits[j][i]) break; 327 } 328 } 329 330 bool contains(V i) const { return i/32 < m_bits[0].length && m_bits[0][i/32] & (1u<<(i%32)); } 331 332 int opApply(scope int delegate(V) @safe nothrow del) 333 const @safe { 334 int rec(size_t depth, uint bi) 335 { 336 auto b = m_bits[depth][bi]; 337 foreach (i; 0 .. 32) 338 if (b & (1u << i)) { 339 uint sbi = bi*32 + i; 340 if (depth == 0) { 341 if (auto ret = del(V(sbi))) 342 return ret; 343 } else rec(depth-1, sbi); 344 } 345 return 0; 346 } 347 348 foreach (i, b; m_bits[$-1]) 349 if (b) { 350 if (auto ret = rec(m_bits.length-1, cast(uint)i)) 351 return ret; 352 } 353 354 return 0; 355 } 356 } 357 358 unittest { 359 uint[] ints = [0, 16, 31, 128, 4096, 65536]; 360 361 SmallIntegerSet!uint set; 362 bool[uint] controlset; 363 364 assert(set.empty); 365 foreach (i; ints) { 366 set.insert(i); 367 controlset[i] = true; 368 } 369 assert(!set.empty); 370 371 foreach (jidx, j; ints) { 372 size_t cnt = 0; 373 bool[int] seen; 374 foreach (i; set) { 375 assert(i in controlset); 376 assert(i !in seen); 377 seen[i] = true; 378 cnt++; 379 } 380 assert(cnt == ints.length - jidx); 381 382 set.remove(j); 383 controlset.remove(j); 384 } 385 assert(set.empty); 386 387 foreach (i; set) assert(false); 388 } 389 390 @safe nothrow unittest { 391 SmallIntegerSet!uint s; 392 393 void testIter(scope uint[] seq...) nothrow { 394 size_t cnt = 0; 395 foreach (v; s) { 396 assert(v == seq[cnt]); 397 cnt++; 398 } 399 assert(cnt == seq.length); 400 } 401 402 testIter(); 403 s.insert(1); 404 assert(s.contains(1)); 405 assert(!s.contains(2)); 406 testIter(1); 407 s.insert(3467); 408 assert(s.contains(3467)); 409 assert(!s.contains(300)); 410 testIter(1, 3467); 411 s.insert(2); 412 testIter(1, 2, 3467); 413 s.remove(1); 414 testIter(2, 3467); 415 s.remove(2); 416 testIter(3467); 417 s.remove(3467); 418 testIter(); 419 } 420 421 private size_t nextPOT(size_t n) @safe nothrow @nogc 422 { 423 foreach_reverse (i; 0 .. size_t.sizeof*8) { 424 size_t ni = cast(size_t)1 << i; 425 if (n & ni) { 426 return n & (ni-1) ? ni << 1 : ni; 427 } 428 } 429 return 1; 430 } 431 432 unittest { 433 assert(nextPOT(1) == 1); 434 assert(nextPOT(2) == 2); 435 assert(nextPOT(3) == 4); 436 assert(nextPOT(4) == 4); 437 assert(nextPOT(5) == 8); 438 }