1 /** 2 * Reference-counted objects for handling large amounts of raw _data. 3 * 4 * Using the `Data` type will only place a small object in managed 5 * memory, keeping the actual bytes in unmanaged memory. 6 * 7 * A proxy class (`Memory`) is used to safely allow multiple 8 * references to the same block of unmanaged memory. 9 * 10 * When the `Memory` object is destroyed, the unmanaged memory is 11 * deallocated. 12 * 13 * This has the following advantage over using managed memory (regular 14 * D arrays): 15 * 16 * - Faster allocation and deallocation, since memory is requested from 17 * the OS directly as whole pages. 18 * 19 * - Greatly reduced chance of memory leaks (on 32-bit platforms) due to 20 * stray pointers. 21 * 22 * - Overall improved GC performance due to reduced size of managed heap. 23 * 24 * - Memory is immediately returned to the OS when no more references 25 * remain. 26 * 27 * - Unlike D arrays, `Data` objects know their reference count, 28 * enabling things like copy-on-write or safely casting away constness. 29 * 30 * License: 31 * This Source Code Form is subject to the terms of 32 * the Mozilla Public License, v. 2.0. If a copy of 33 * the MPL was not distributed with this file, You 34 * can obtain one at http://mozilla.org/MPL/2.0/. 35 * 36 * Authors: 37 * Vladimir Panteleev <ae@cy.md> 38 */ 39 40 module ae.sys.data; 41 42 import core.exception : OutOfMemoryError, onOutOfMemoryError; 43 44 import std.algorithm.mutation : move; 45 import std.traits : hasIndirections, Unqual, isSomeChar; 46 47 // https://issues.dlang.org/show_bug.cgi?id=23961 48 //import ae.utils.array : emptySlice, sliceIndex, bytes, fromBytes; 49 50 debug(DATA) import core.stdc.stdio; 51 52 version (ae_data_nogc) 53 private enum useGC = false; 54 else 55 private enum useGC = true; 56 57 static if (useGC) 58 import core.memory : GC; 59 60 /** 61 * A reference to a reference-counted block of memory. 62 * Represents a slice of data, which may be backed by managed memory, 63 * unmanaged memory, memory-mapped files, etc. 64 * 65 * Params: 66 * T = the element type. "void" has a special meaning in that memory 67 * will not be default-initialized. 68 */ 69 struct TData(T) 70 if (!hasIndirections!T) 71 { 72 private: 73 // https://issues.dlang.org/show_bug.cgi?id=23961 74 import ae.utils.array : emptySlice, sliceIndex, as, asBytes; 75 76 /// Wrapped data 77 T[] data; 78 79 /// Reference to the memory of the actual data - may be null to 80 /// indicate wrapped data in managed memory. 81 /// Used to maintain the reference count to unmanaged data, and 82 /// for in-place expands (for appends). 83 Memory memory; 84 85 // --- Typed Memory construction helpers 86 87 // No pure due to https://issues.dlang.org/show_bug.cgi?id=23959 88 static T[] allocateMemory(U)(out Memory memory, size_t initialSize, size_t capacity, scope void delegate(Unqual!U[] contents) /*pure*/ @safe nothrow @nogc fill) 89 { 90 if (capacity * T.sizeof < OSAllocator.pageSize) 91 memory = unmanagedNew!CMemory(initialSize * T.sizeof, capacity * T.sizeof); 92 else 93 memory = unmanagedNew!OSMemory(initialSize * T.sizeof, capacity * T.sizeof); 94 fill(cast(Unqual!U[])memory.contents); 95 return cast(T[])memory.contents; 96 } 97 98 static T[] allocateMemory(U)(out Memory memory, U[] initialData) 99 { 100 assert(initialData.length * U.sizeof % T.sizeof == 0, "Unaligned allocation size"); 101 auto initialSize = initialData.length * U.sizeof / T.sizeof; 102 // @trusted to allow copying void[] to void[] 103 return allocateMemory!U(memory, initialSize, initialSize, (contents) @trusted { contents[] = initialData[]; }); 104 } 105 106 static T[] allocateMemory(out Memory memory, size_t initialSize, size_t capacity) 107 { 108 return allocateMemory!T(memory, initialSize, capacity, (contents) { 109 static if (!is(Unqual!T == void)) 110 contents[] = T.init; 111 }); 112 } 113 114 // --- Concatenation / appending helpers 115 116 // No pure due to https://issues.dlang.org/show_bug.cgi?id=23959 117 void reallocate(size_t size, size_t capacity, scope void delegate(Unqual!T[] contents) /*pure*/ @safe nothrow @nogc fill) 118 { 119 Memory newMemory; 120 // @trusted to allow copying void[] to void[] 121 auto newData = allocateMemory!T(newMemory, size, capacity, (contents) @trusted { 122 contents[0 .. this.data.length] = this.data; 123 fill(contents[this.data.length .. $]); 124 }); 125 126 clear(); 127 this.memory = newMemory; 128 this.memory.referenceCount++; 129 this.data = newData; 130 } 131 132 // No pure due to https://issues.dlang.org/show_bug.cgi?id=23959 133 void expand(size_t newSize, size_t newCapacity, scope void delegate(Unqual!T[] contents) /*pure*/ @safe nothrow @nogc fill) 134 @trusted // Allow slicing data.ptr 135 in 136 { 137 assert(length < newSize); 138 assert(newSize <= newCapacity); 139 } 140 out 141 { 142 assert(length == newSize); 143 } 144 do 145 { 146 if (newCapacity <= capacity) 147 { 148 auto dataBytes = this.data.asBytes; 149 auto pos = memory.contents.sliceIndex(dataBytes); // start position in memory data in bytes 150 memory.setSize(pos + newSize * T.sizeof); 151 auto oldSize = data.length; 152 data = data.ptr[0..newSize]; 153 fill(cast(Unqual!T[])data[oldSize .. $]); 154 } 155 else 156 reallocate(newSize, newCapacity, fill); 157 } 158 159 // Maximum preallocation for append operations. 160 enum maxPrealloc = 4*1024*1024; // must be power of 2 161 162 alias Appendable = const(Unqual!T)[]; 163 static assert(is(typeof((T[]).init ~ Appendable.init))); 164 165 static TData createConcatenation(Appendable left, Appendable right) 166 { 167 auto newSize = left.length + right.length; 168 Memory newMemory; 169 // @trusted to allow copying void[] to void[] 170 allocateMemory!T(newMemory, newSize, newSize, (contents) @trusted { 171 contents[0 .. left.length] = left[]; 172 contents[left.length .. $] = right[]; 173 }); 174 return TData(newMemory); 175 } 176 177 TData concat(Appendable right) 178 { 179 if (right.length == 0) 180 return this; 181 return createConcatenation(this.data, right); 182 } 183 184 TData prepend(Appendable left) 185 { 186 if (left.length == 0) 187 return this; 188 return createConcatenation(left, this.data); 189 } 190 191 static size_t getPreallocSize(size_t length) 192 { 193 import ae.utils.math : isPowerOfTwo, nextPowerOfTwo; 194 static assert(isPowerOfTwo(maxPrealloc)); 195 196 if (length < maxPrealloc) 197 return nextPowerOfTwo(length); 198 else 199 return ((length-1) | (maxPrealloc-1)) + 1; 200 } 201 202 TData append(Appendable right) 203 { 204 if (right.length == 0) 205 return this; 206 size_t newLength = length + right.length; 207 // @trusted to allow copying void[] to void[] 208 expand(newLength, getPreallocSize(newLength), (contents) @trusted { 209 contents[] = right[]; 210 }); 211 return this; 212 } 213 214 // --- Conversion helpers 215 216 void assertUnique() const 217 { 218 assert( 219 (data is null) 220 || 221 (memory && memory.referenceCount == 1) 222 ); 223 } 224 225 void becomeUnique() 226 out 227 { 228 assertUnique(); 229 } 230 do 231 { 232 if (!memory) // null, empty, or GC-owned data 233 this = TData(data); 234 else 235 if (memory.referenceCount > 1) 236 this = TData(data); 237 } 238 239 debug invariant 240 { 241 if (data.length == 0) 242 assert(memory is null, "Zero-length Data pinning Memory"); 243 if (memory) 244 assert(memory.referenceCount > 0, "Data referencing Memory with bad reference count"); 245 } 246 247 public: 248 // --- Lifetime - construction 249 250 // TODO: overload the constructor on scope/non-scope to detect when to reallocate? 251 // https://issues.dlang.org/show_bug.cgi?id=23941 252 253 /** 254 * DWIM constructor for creating a new instance wrapping the given data. 255 * 256 * In the current implementation, `data` is always copied into the 257 * new instance, however past and future implementations may not 258 * guarantee this (`wrapGC`-like construction may be used 259 * opportunistically instead). 260 */ 261 this(U)(U[] data) 262 if (is(typeof({ U[] u; T[] t = u.dup; }))) 263 { 264 if (data is null) 265 this.data = null; 266 else 267 if (data.length == 0) 268 this.data = emptySlice!T; 269 else 270 // if (forceReallocation || GC.addrOf(data.ptr) is null) 271 { 272 // copy (emplace) into unmanaged memory 273 this.data = allocateMemory(this.memory, data); 274 this.memory.referenceCount++; 275 } 276 // else 277 // { 278 // // just save a reference 279 // this.data = data; 280 // } 281 282 assert(this.length * T.sizeof == data.length * U.sizeof); 283 } 284 285 /// Create a new null-like instance. 286 this(typeof(null) n) 287 { 288 } 289 290 deprecated this(U)(U[] data) 291 if (is(T == ubyte) && 292 !is(typeof({ U[] u; T[] t = u.dup; })) && 293 is(typeof({ U[] u; void[] t = u.dup; }))) 294 { 295 const(void)[] v = data; 296 this(cast(const(ubyte)[])v); 297 } 298 299 /// Create a new instance with given size/capacity. Capacity defaults to size. 300 this(size_t size, size_t capacity = 0) 301 in 302 { 303 assert(capacity == 0 || size <= capacity); 304 } 305 do 306 { 307 if (!capacity) 308 capacity = size; 309 310 if (capacity) 311 { 312 this.data = allocateMemory(this.memory, size, capacity); 313 this.memory.referenceCount++; 314 } 315 else 316 { 317 memory = null; 318 this.data = null; 319 } 320 321 assert(this.length == size); 322 } 323 324 /// Allow assigning null to clear. 325 void opAssign(typeof(null)) 326 { 327 clear(); 328 } 329 330 /// Create a new instance which slices some range of managed (GC-owned) memory. 331 /// Does not copy the data. 332 static if (useGC) 333 static TData wrapGC(T[] data) 334 { 335 assert(data.length == 0 || GC.addrOf(data.ptr) !is null, "wrapGC data must be GC-owned"); 336 TData result; 337 result.data = data; 338 return result; 339 } 340 341 /// Create a new instance slicing all of the given memory's contents. 342 this(Memory memory) 343 { 344 this.memory = memory; 345 this.memory.referenceCount++; 346 this.data = cast(T[])memory.contents; 347 } 348 349 this(this) @safe 350 { 351 if (memory) 352 { 353 memory.referenceCount++; 354 debug (DATA_REFCOUNT) debugLog("%p -> %p: Incrementing refcount to %d", cast(void*)&this, cast(void*)memory, memory.referenceCount); 355 } 356 else 357 debug (DATA_REFCOUNT) debugLog("%p -> %p: this(this) with no memory", cast(void*)&this, cast(void*)memory); 358 } 359 360 // --- Lifetime - destruction 361 362 // No pure due to https://issues.dlang.org/show_bug.cgi?id=23959 363 ~this() /*pure*/ @trusted nothrow @nogc 364 { 365 clear(); 366 // https://issues.dlang.org/show_bug.cgi?id=13809 367 // (cast(void delegate() pure nothrow @nogc)&clear)(); 368 } 369 370 /// Unreference contents, freeing it if this was the last reference. 371 void clear() nothrow @nogc 372 { 373 if (memory) 374 { 375 assert(memory.referenceCount > 0, "Dangling pointer to Memory"); 376 memory.referenceCount--; 377 debug (DATA_REFCOUNT) debugLog("%p -> %p: Decrementing refcount to %d", cast(void*)&this, cast(void*)memory, memory.referenceCount); 378 if (memory.referenceCount == 0) 379 unmanagedDelete(memory); 380 381 memory = null; 382 } 383 384 this.data = null; 385 } 386 387 // This used to be an unsafe method which deleted the wrapped data. 388 // Now that Data is refcounted, this simply calls clear() and 389 // additionally asserts that this Data is the only Data holding 390 // a reference to the memory. 391 deprecated void deleteContents() 392 out 393 { 394 assert(memory is null); 395 } 396 do 397 { 398 if (memory) 399 { 400 assert(memory.referenceCount == 1, "Attempting to call deleteContents with more than one reference"); 401 clear(); 402 } 403 } 404 405 // --- Lifetime - conversion 406 407 /// Returns an instance with the same data as the current instance, and a reference count of 1. 408 /// The current instance is cleared. 409 /// If the current instance already has a reference count of 1, no copying is done. 410 TData ensureUnique() 411 { 412 becomeUnique(); 413 return move(this); 414 } 415 416 /// Soft (memory-safe) cast: 417 /// Cast contents to another type, and returns an instance with that contents. 418 /// Constness is preserved. No copying is done. 419 auto asDataOf(U)() 420 if (is(typeof(this.data.asBytes.as!(U[])) == U[])) 421 { 422 TData!U result; 423 result.data = this.data.asBytes.as!(U[]); 424 result.memory = this.memory; 425 if (result.memory) 426 result.memory.referenceCount++; 427 return result; 428 } 429 430 /// Hard (normally memory-unsafe) cast: 431 /// Cast contents to another type, and returns an instance with that contents. 432 /// The current instance is cleared. U may have an incompatible constness. 433 /// To enforce memory safety, the current instance must be the only one 434 /// holding a reference to the data (call `ensureUnique` first). 435 /// No copying is done. 436 TData!U castTo(U)() 437 if (!hasIndirections!U) 438 { 439 assertUnique(); 440 441 TData!U result; 442 result.data = cast(U[])data; 443 result.memory = this.memory; 444 this.data = null; 445 this.memory = null; 446 return result; 447 } 448 449 // --- Contents access 450 451 private enum enterImpl = q{ 452 // We must make a copy of ourselves to ensure that, should 453 // `fn` overwrite the `this` instance, the passed contents 454 // slice remains valid. 455 auto self = this; 456 scope data = self.data; // Add `scope` attribute 457 return fn(data); 458 }; 459 460 /// Get temporary access to the data referenced by this Data instance. 461 // This non-templated overload set exists to allow 462 // lambda functions (anonymous function templates). 463 void enter(scope void delegate(scope T[]) fn) { mixin(enterImpl); } 464 void enter(scope void delegate(scope T[]) @safe fn) @safe { mixin(enterImpl); } /// ditto 465 // No pure due to https://issues.dlang.org/show_bug.cgi?id=23959 466 // void enter(scope void delegate(scope T[]) pure fn) pure { mixin(enterImpl); } /// ditto 467 // void enter(scope void delegate(scope T[]) @safe pure fn) @safe pure { mixin(enterImpl); } /// ditto 468 void enter(scope void delegate(scope T[]) nothrow fn) nothrow { mixin(enterImpl); } /// ditto 469 void enter(scope void delegate(scope T[]) @safe nothrow fn) @safe nothrow { mixin(enterImpl); } /// ditto 470 // void enter(scope void delegate(scope T[]) pure nothrow fn) pure nothrow { mixin(enterImpl); } /// ditto 471 // void enter(scope void delegate(scope T[]) @safe pure nothrow fn) @safe pure nothrow { mixin(enterImpl); } /// ditto 472 void enter(scope void delegate(scope T[]) @nogc fn) @nogc { mixin(enterImpl); } /// ditto 473 void enter(scope void delegate(scope T[]) @safe @nogc fn) @safe @nogc { mixin(enterImpl); } /// ditto 474 // void enter(scope void delegate(scope T[]) pure @nogc fn) pure @nogc { mixin(enterImpl); } /// ditto 475 // void enter(scope void delegate(scope T[]) @safe pure @nogc fn) @safe pure @nogc { mixin(enterImpl); } /// ditto 476 void enter(scope void delegate(scope T[]) nothrow @nogc fn) nothrow @nogc { mixin(enterImpl); } /// ditto 477 void enter(scope void delegate(scope T[]) @safe nothrow @nogc fn) @safe nothrow @nogc { mixin(enterImpl); } /// ditto 478 // void enter(scope void delegate(scope T[]) pure nothrow @nogc fn) pure nothrow @nogc { mixin(enterImpl); } /// ditto 479 // void enter(scope void delegate(scope T[]) @safe pure nothrow @nogc fn) @safe pure nothrow @nogc { mixin(enterImpl); } /// ditto 480 481 // https://issues.dlang.org/show_bug.cgi?id=23956 482 // void enter(scope void delegate(scope const(T)[]) fn) const { mixin(enterImpl); } 483 // void enter(scope void delegate(scope const(T)[]) @safe fn) const @safe { mixin(enterImpl); } 484 // void enter(scope void delegate(scope const(T)[]) pure fn) const pure { mixin(enterImpl); } 485 // void enter(scope void delegate(scope const(T)[]) @safe pure fn) const @safe pure { mixin(enterImpl); } 486 // void enter(scope void delegate(scope const(T)[]) nothrow fn) const nothrow { mixin(enterImpl); } 487 // void enter(scope void delegate(scope const(T)[]) @safe nothrow fn) const @safe nothrow { mixin(enterImpl); } 488 // void enter(scope void delegate(scope const(T)[]) pure nothrow fn) const pure nothrow { mixin(enterImpl); } 489 // void enter(scope void delegate(scope const(T)[]) @safe pure nothrow fn) const @safe pure nothrow { mixin(enterImpl); } 490 // void enter(scope void delegate(scope const(T)[]) @nogc fn) const @nogc { mixin(enterImpl); } 491 // void enter(scope void delegate(scope const(T)[]) @safe @nogc fn) const @safe @nogc { mixin(enterImpl); } 492 // void enter(scope void delegate(scope const(T)[]) pure @nogc fn) const pure @nogc { mixin(enterImpl); } 493 // void enter(scope void delegate(scope const(T)[]) @safe pure @nogc fn) const @safe pure @nogc { mixin(enterImpl); } 494 // void enter(scope void delegate(scope const(T)[]) nothrow @nogc fn) const nothrow @nogc { mixin(enterImpl); } 495 // void enter(scope void delegate(scope const(T)[]) @safe nothrow @nogc fn) const @safe nothrow @nogc { mixin(enterImpl); } 496 // void enter(scope void delegate(scope const(T)[]) pure nothrow @nogc fn) const pure nothrow @nogc { mixin(enterImpl); } 497 // void enter(scope void delegate(scope const(T)[]) @safe pure nothrow @nogc fn) const @safe pure nothrow @nogc { mixin(enterImpl); } 498 499 // For everything else, there is a template overload. 500 // Note: Dg is a IFTI-inferred parameter due to 501 // https://issues.dlang.org/show_bug.cgi?id=23955 502 auto enter(this This, Dg)(scope Dg fn) { mixin(enterImpl); } 503 504 /// Put a copy of the data on D's managed heap, and return it. 505 T[] toGC() const 506 { 507 return data.dup; 508 } 509 510 // deprecated alias toHeap = toGC; 511 // https://issues.dlang.org/show_bug.cgi?id=23954 512 deprecated T[] toHeap() const { return toGC(); } 513 514 /** 515 Get the referenced data. Unsafe! 516 517 All operations on the returned contents must be accompanied by 518 a live reference to the `Data` object, in order to keep a 519 reference towards the Memory owning the contents. 520 521 Be sure not to lose `Data` references while using their contents! 522 For example, avoid code like this: 523 ---- 524 getSomeData() // returns Data 525 .unsafeContents // returns ubyte[] 526 .useContents(); // uses the ubyte[] ... while there is no Data to reference it 527 ---- 528 The `Data` return value may be unreachable once `.unsafeContents` is evaluated. 529 Use `.toGC` instead of `.unsafeContents` in such cases to safely get a GC-owned copy, 530 or use `.enter(contents => ...)` to safely get a temporary reference. 531 */ 532 @property inout(T)[] unsafeContents() inout @system { return this.data; } 533 534 // deprecated alias contents = unsafeContents; 535 // https://issues.dlang.org/show_bug.cgi?id=23954 536 deprecated @property inout(T)[] contents() inout @system { return this.data; } 537 538 deprecated @property Unqual!T[] mcontents() @system 539 { 540 becomeUnique(); 541 return cast(Unqual!T[])data; 542 } 543 544 // --- Array-like operations 545 546 /// 547 @property size_t length() const 548 { 549 return data.length; 550 } 551 alias opDollar = length; /// ditto 552 553 deprecated @property inout(T)* ptr() inout { return unsafeContents.ptr; } 554 555 deprecated @property Unqual!T* mptr() @system { return mcontents.ptr; } 556 557 bool opCast(T)() const 558 if (is(T == bool)) 559 { 560 return data !is null; 561 } /// 562 563 /// Return the maximum value that can be set to `length` without causing a reallocation 564 @property size_t capacity() const 565 { 566 if (memory is null) 567 return length; 568 // We can only safely expand if the memory slice is at the end of the used unmanaged memory block, 569 // or, if we are the only reference. 570 auto dataBytes = this.data.asBytes; 571 auto pos = memory.contents.sliceIndex(dataBytes); // start position in memory data in bytes 572 auto end = pos + dataBytes.length; // end position in memory data in bytes 573 assert(end <= memory.size); 574 if ((end == memory.size || memory.referenceCount == 1) && end < memory.capacity) 575 return (memory.capacity - pos) / T.sizeof; // integer division truncating towards zero 576 else 577 return length; 578 } 579 580 /// Resize contents 581 @property void length(size_t newLength) 582 { 583 if (newLength == length) // no change 584 return; 585 if (newLength < length) // shorten 586 { 587 if (!newLength) 588 this = TData(emptySlice!T); 589 else 590 data = data[0..newLength]; 591 } 592 else // lengthen 593 expand(newLength, newLength, (contents) { 594 static if (!is(Unqual!T == void)) 595 contents[] = T.init; 596 }); 597 } 598 599 /// Create a copy of the data 600 @property This dup(this This)() 601 { 602 return This(this.data); 603 } 604 605 /// Create a new `Data` containing the concatenation of `this` and `data`. 606 /// Does not preallocate for successive appends. 607 template opBinary(string op) if (op == "~") 608 { 609 TData opBinary(Appendable data) 610 { 611 return concat(data); 612 } /// 613 614 TData opBinary(TData data) 615 { 616 return concat(data.data); 617 } /// 618 619 static if (!is(Unqual!T == void)) 620 TData opBinary(T value) 621 { 622 return concat((&value)[0..1]); 623 } /// 624 625 static if (is(T == ubyte)) 626 deprecated TData opBinary(const(void)[] data) 627 { 628 return concat(cast(Appendable)data); 629 } 630 } 631 632 /// Create a new `Data` containing the concatenation of `data` and `this`. 633 /// Does not preallocate for successive appends. 634 template opBinaryRight(string op) if (op == "~") 635 { 636 TData opBinaryRight(Appendable data) 637 { 638 return prepend(data); 639 } /// 640 641 static if (!is(Unqual!T == void)) 642 TData opBinaryRight(T value) 643 { 644 return prepend((&value)[0..1]); 645 } /// 646 647 static if (is(T == ubyte)) 648 deprecated TData opBinaryRight(const(void)[] data) 649 { 650 return prepend(cast(Appendable)data); 651 } 652 } 653 654 /// Append data to this `Data`. 655 /// Unlike concatenation (`a ~ b`), appending (`a ~= b`) will preallocate. 656 template opOpAssign(string op) if (op == "~") 657 { 658 TData opOpAssign(Appendable data) 659 { 660 return append(data); 661 } /// 662 663 TData opOpAssign(TData data) 664 { 665 return append(data.data); 666 } /// 667 668 static if (!is(Unqual!T == void)) 669 TData opOpAssign(T value) 670 { 671 return append((&value)[0..1]); 672 } /// 673 674 static if (is(T == ubyte)) 675 deprecated TData opOpAssign(const(void)[] data) 676 { 677 return append(cast(Appendable)data); 678 } 679 } 680 681 /// Access an individual item. 682 static if (!is(Unqual!T == void)) 683 T opIndex(size_t index) 684 { 685 return data[index]; 686 } 687 688 /// Write an individual item. 689 static if (is(typeof(data[0] = T.init))) 690 T opIndexAssign(T value, size_t index) 691 { 692 return data[index] = value; 693 } 694 695 /// Returns a `Data` pointing at a slice of this `Data`'s contents. 696 TData opSlice() 697 { 698 return this; 699 } 700 701 /// ditto 702 TData opSlice(size_t x, size_t y) 703 in 704 { 705 assert(x <= y); 706 assert(y <= length); 707 } 708 out(result) 709 { 710 assert(result.length == y-x); 711 } 712 do 713 { 714 if (x == y) 715 return TData(emptySlice!T); 716 else 717 { 718 TData result = this; 719 result.data = result.data[x .. y]; 720 return result; 721 } 722 } 723 724 package(ae) // TODO: is this a good API? 725 sizediff_t indexOf(const(T)[] needle) const 726 { 727 static if (is(typeof(imported!q{ae.utils.array}.indexOf(this.data, needle)))) 728 return imported!q{ae.utils.array}.indexOf(this.data, needle); 729 else 730 static if (is(typeof(imported!q{std..string}.indexOf(this.data, needle)))) 731 return imported!q{std..string}.indexOf(this.data, needle); 732 else 733 { 734 if (this.data.length >= needle.length) 735 foreach (i; 0 .. this.data.length - needle.length + 1) 736 if (this.data[i .. i + needle.length] == needle) 737 return i; 738 return -1; 739 } 740 } 741 742 package(ae) // ditto 743 static if (is(T == ubyte)) 744 deprecated sizediff_t indexOf(const(void)[] needle) const 745 { 746 return indexOf(cast(const(ubyte)[])needle); 747 } 748 749 // --- Range operations 750 751 /// Range primitive. 752 @property bool empty() const { return length == 0; } 753 static if (!is(Unqual!T == void)) 754 T front() { return data[0]; } /// 755 void popFront() { data = data[1 .. $]; } /// 756 757 // /// Return a new `Data` for the first `size` bytes, and slice this instance from size to end. 758 // Data popFront(size_t size) 759 // in 760 // { 761 // assert(size <= length); 762 // } 763 // do 764 // { 765 // Data result = this; 766 // result.contents = contents[0..size]; 767 // this .contents = contents[size..$]; 768 // return result; 769 // } 770 } 771 772 unittest 773 { 774 import core.exception : AssertError; 775 import core.memory : GC; 776 import std.exception : assertThrown; 777 import ae.utils.array : emptySlice; 778 779 alias AliasSeq(TList...) = TList; 780 foreach (B; AliasSeq!(ubyte, uint, char, void)) 781 { 782 alias Ts = AliasSeq!(B, const(B), immutable(B)); 783 foreach (T; Ts) 784 { 785 // Template instantiation 786 { 787 TData!T d; 788 cast(void) d; 789 } 790 // .enter type 791 { 792 TData!T d; 793 d.enter((scope contents) { T[] _ = contents; }); 794 } 795 // .enter with functors 796 { 797 import ae.utils.functor.primitives : functor; 798 TData!T d; 799 d.enter(functor!((contents) { 800 assert(contents == d.unsafeContents); 801 })); 802 } 803 // // .enter with const 804 // { 805 // const TData!T d; 806 // d.enter((scope contents) { const T[] _ = contents; }); 807 // } 808 // .enter with return value 809 { 810 TData!T d; 811 auto l = d.enter((T[] contents) => contents.length); 812 assert(l == d.length); 813 } 814 // Construction from typeof(null) 815 { 816 auto d = TData!T(null); 817 assert(d.length == 0); 818 assert(d.unsafeContents.ptr is null); 819 } 820 // Construction from null slice 821 { 822 T[] arr = null; 823 auto d = TData!T(arr); 824 assert(d.length == 0); 825 assert(d.unsafeContents.ptr is null); 826 } 827 // Construction from non-null empty 828 { 829 auto d = TData!T(emptySlice!T()); 830 assert(d.length == 0); 831 assert(d.unsafeContents.ptr !is null); 832 } 833 // Construction from non-empty non-GC slice 834 { 835 T[5] arr = void; 836 assert(GC.addrOf(arr.ptr) is null); 837 auto d = TData!T(arr[]); 838 assert(d.length == 5); 839 assert(d.unsafeContents.ptr !is null); 840 assert(GC.addrOf(d.unsafeContents.ptr) is null); 841 } 842 // Construction from non-empty GC slice 843 { 844 T[] arr = new T[5]; 845 assert(GC.addrOf(arr.ptr) !is null); 846 auto d = TData!T(arr); 847 assert(d.length == 5); 848 assert(d.unsafeContents.ptr !is null); 849 assert(GC.addrOf(d.unsafeContents.ptr) is null); 850 } 851 // wrapGC from GC slice 852 static if (useGC) 853 {{ 854 T[] arr = new T[5]; 855 auto d = TData!T.wrapGC(arr); 856 assert(d.length == 5); 857 assert(d.unsafeContents.ptr is arr.ptr); 858 }} 859 // wrapGC from non-GC slice 860 static if (useGC) 861 {{ 862 static T[5] arr = void; 863 assertThrown!AssertError(TData!T.wrapGC(arr[])); 864 }} 865 // .capacity 866 { 867 T[] arr = new T[5]; 868 auto d = TData!T(arr); 869 assert(d.capacity >= 5); 870 auto d2 = d[0 .. 3]; 871 assert(d2.capacity == 3); 872 d = null; 873 assert(d2.capacity >= 5); // Sole reference; safe to expand over old data 874 } 875 876 // Try a bunch of operations with different kinds of instances 877 static T[5] arr = void; 878 TData!T delegate()[] generators = [ 879 delegate () => TData!T(), 880 delegate () => TData!T(null), 881 delegate () => TData!T(T[].init), 882 delegate () => TData!T(arr[]), 883 delegate () => TData!T(arr[0 .. 0]), 884 delegate () => TData!T(arr[].dup), 885 delegate () => TData!T(arr[].dup[0 .. 0]), 886 ]; 887 static if (useGC) 888 generators ~= [ 889 delegate () => TData!T.wrapGC(arr[].dup), 890 delegate () => TData!T.wrapGC(arr[].dup[0 .. 0]), 891 ]; 892 static if (is(B == void)) 893 foreach (B2; AliasSeq!(ubyte, uint, char, void)) 894 foreach (T2; AliasSeq!(B2, const(B2), immutable(B2))) 895 static if (is(typeof({ T2[] u; T[] t = u; }))) 896 { 897 static T2[5] arr2 = void; 898 generators ~= [ 899 delegate () => TData!T(T2[].init), 900 delegate () => TData!T(arr2[]), 901 delegate () => TData!T(arr2[0 .. 0]), 902 delegate () => TData!T(arr2[].dup), 903 delegate () => TData!T(arr2[].dup[0 .. 0]), 904 ]; 905 static if (useGC) 906 generators ~= [ 907 delegate () => TData!T.wrapGC(arr2[].dup), 908 // delegate () => TData!T.wrapGC(arr2[].dup[0 .. 0]), // TODO: why not? 909 ]; 910 } 911 foreach (generator; generators) 912 { 913 // General coherency 914 { 915 auto d = generator(); 916 auto length = d.length; 917 auto contents = d.unsafeContents; 918 assert(contents.length == length); 919 size_t entered; 920 d.enter((enteredContents) { 921 assert(enteredContents is contents); 922 entered++; 923 }); 924 assert(entered == 1); 925 assert(d.dup.unsafeContents == contents); 926 } 927 // Lifetime with .enter 928 { 929 auto d = generator(); 930 d.enter((contents) { 931 d = typeof(d)(null); 932 (cast(ubyte[])contents)[] = 42; 933 }); 934 } 935 // toGC 936 { 937 auto d = generator(); 938 auto contents = d.unsafeContents; 939 assert(d.toGC() == contents); 940 } 941 // In-place expansion (resize to capacity) 942 { 943 auto d = generator(); 944 auto oldContents = d.unsafeContents; 945 d.length = d.capacity; 946 assert(d.unsafeContents.ptr == oldContents.ptr); 947 } 948 // Copying expansion (resize past capacity) 949 { 950 auto d = generator(); 951 auto oldContents = d.unsafeContents; 952 d.length = d.capacity + 1; 953 assert(d.unsafeContents.ptr != oldContents.ptr); 954 } 955 // Concatenation 956 { 957 void test(L, R)(L left, R right) 958 { 959 { 960 auto result = left ~ right; 961 assert(result.length == left.length + right.length); 962 // TODO: test contents, need opEquals? 963 } 964 static if (!is(Unqual!T == void)) 965 { 966 if (left.length) 967 { 968 auto result = left[0] ~ right; 969 assert(result.length == 1 + right.length); 970 } 971 if (right.length) 972 { 973 auto result = left ~ right[0]; 974 assert(result.length == left.length + 1); 975 } 976 } 977 } 978 979 foreach (generator2; generators) 980 { 981 test(generator(), generator2()); 982 test(generator().toGC, generator2()); 983 test(generator(), generator2().toGC); 984 } 985 } 986 // Appending 987 { 988 void test(L, R)(L left, R right) 989 { 990 { 991 auto result = left; 992 result ~= right; 993 assert(result.length == left.length + right.length); 994 // TODO: test contents, need opEquals? 995 } 996 static if (!is(Unqual!T == void)) 997 { 998 if (right.length) 999 { 1000 auto result = left; 1001 result ~= right[0]; 1002 assert(result.length == left.length + 1); 1003 } 1004 } 1005 } 1006 1007 foreach (generator2; generators) 1008 { 1009 test(generator(), generator2()); 1010 // test(generator().toGC, generator2()); 1011 test(generator(), generator2().toGC); 1012 } 1013 } 1014 // Slicing to zero 1015 { 1016 auto l = generator().length; 1017 foreach (n; [0, l/2, l]) 1018 { 1019 auto d = generator(); 1020 d = d[n .. n]; 1021 assert(d == d); 1022 } 1023 } 1024 // Shrinking to zero 1025 { 1026 auto d = generator(); 1027 d.length = 0; 1028 assert(d == d); 1029 } 1030 // Reference count 1031 { 1032 auto d = generator(); 1033 if (d.memory) 1034 { 1035 assert(d.memory.referenceCount == 1); 1036 { 1037 auto s = d[1..4]; 1038 assert(d.memory.referenceCount == 2); 1039 cast(void) s; 1040 } 1041 assert(d.memory.referenceCount == 1); 1042 } 1043 } 1044 } 1045 1046 // Test effects of construction from various sources 1047 { 1048 void testSource(S)(S s) 1049 { 1050 void testData(TData!T d) 1051 { 1052 // Test true-ish-ness 1053 assert(!! s == !! d); 1054 // Test length 1055 static if (is(typeof(s.length))) 1056 assert(s.length * s[0].sizeof == d.length * T.sizeof); 1057 // Test content 1058 assert(s == d.unsafeContents); 1059 } 1060 // Construction 1061 testData(TData!T(s)); 1062 // wrapGC 1063 static if (useGC) 1064 static if (is(typeof(*s.ptr) == T)) 1065 if (GC.addrOf(s.ptr)) 1066 testData(TData!T.wrapGC(s)); 1067 // Appending 1068 { 1069 TData!T d; 1070 d ~= s; 1071 } 1072 } 1073 testSource(null); 1074 testSource(T[].init); 1075 testSource(arr[]); 1076 testSource(arr[0 .. 0]); 1077 testSource(arr[].dup); 1078 testSource(arr[].dup[0 .. 0]); 1079 testSource(arr[].idup); 1080 testSource(arr[].idup[0 .. 0]); 1081 static if (is(B == void)) 1082 foreach (B2; AliasSeq!(ubyte, uint, char, void)) 1083 foreach (T2; AliasSeq!(B2, const(B2), immutable(B2))) 1084 static if (is(typeof({ T2[] u; T[] t = u; }))) 1085 { 1086 static T2[5] arr2 = void; 1087 testSource(T2[].init); 1088 testSource(arr2[]); 1089 testSource(arr2[0 .. 0]); 1090 testSource(arr2[].dup); 1091 testSource(arr2[].dup[0 .. 0]); 1092 } 1093 } 1094 1095 foreach (U; Ts) 1096 { 1097 // Construction from compatible slice 1098 { 1099 U[] u; 1100 TData!T(u); 1101 static if (useGC) 1102 static if (is(typeof({ T[] t = u; }))) 1103 cast(void) TData!T.wrapGC(u); 1104 } 1105 } 1106 } 1107 } 1108 } 1109 1110 // pure/@safe/nothrow/@nogc compilation test 1111 // No pure due to https://issues.dlang.org/show_bug.cgi?id=23959 1112 /*pure*/ @safe nothrow @nogc unittest 1113 { 1114 TData!ubyte d; 1115 d.enter((scope contents) { ubyte[] _ = contents; }); 1116 d = TData!ubyte(null); 1117 assert(d.length == 0); 1118 1119 ubyte[] arr1 = null; 1120 d = TData!ubyte(arr1); 1121 1122 ubyte[0] arr2; 1123 d = TData!ubyte(arr2[]); 1124 1125 ubyte[5] arr3 = void; 1126 d = TData!ubyte(arr3[]); 1127 1128 d.enter((contents) { 1129 d = typeof(d)(null); 1130 (cast(ubyte[])contents)[] = 42; 1131 }); 1132 1133 d.length = d.length + 1; 1134 d.length = d.capacity; 1135 1136 d = d ~ d; 1137 d ~= d; 1138 d.enter((contents) { 1139 d = d ~ contents; 1140 d = contents ~ d; 1141 d ~= contents; 1142 }); 1143 } 1144 1145 /// Get the underlying type of a `TData`. 1146 /// (For `Data`, this will be `ubyte`.) 1147 template DataElementType(D) 1148 if (is(D == TData!T, T)) 1149 { 1150 static if (is(D == TData!T, T)) 1151 alias DataElementType = T; 1152 } 1153 static assert(is(DataElementType!Data == ubyte)); 1154 1155 /// The most common use case of manipulating unmanaged memory is 1156 /// working with raw bytes, whether they're received from the network, 1157 /// read from a file, or elsewhere. 1158 alias Data = TData!ubyte; 1159 1160 // ************************************************************************ 1161 1162 deprecated("legacy transitive import - please `import ae.sys.dataset;`.") 1163 public import ae.sys.dataset : copyTo, joinData, joinToHeap, DataVec, shift, bytes, DataSetBytes; 1164 1165 deprecated alias DataWrapper = Memory; 1166 1167 package(ae) // TODO: is this a good API? 1168 T as(T)(Data data) 1169 { 1170 assert(data.length == T.sizeof, "Incorrect data size"); 1171 return data.asDataOf!T.front; 1172 } 1173 1174 package(ae) // TODO: is this a good API? 1175 T pop(T)(ref Data data) 1176 { 1177 assert(data.length >= T.sizeof, "Insufficient bytes in data"); 1178 auto result = data[0 .. T.sizeof].asDataOf!T.front; 1179 data = data[T.sizeof .. $]; 1180 return result; 1181 } 1182 1183 // ************************************************************************ 1184 1185 /// Base abstract class which owns a block of memory. 1186 abstract class Memory 1187 { 1188 sizediff_t referenceCount = 0; /// Reference count. 1189 abstract @property inout(ubyte)[] contents() inout pure @safe nothrow @nogc; /// The owned memory 1190 abstract @property size_t size() const pure @safe nothrow @nogc; /// Length of `contents`. 1191 abstract void setSize(size_t newSize) pure @safe nothrow @nogc; /// Resize `contents` up to `capacity`. 1192 abstract @property size_t capacity() const pure @safe nothrow @nogc; /// Maximum possible size. 1193 1194 debug ~this() nothrow @nogc 1195 { 1196 debug(DATA_REFCOUNT) debugLog("%.*s.~this, referenceCount==%d", this.classinfo.name.length, this.classinfo.name.ptr, referenceCount); 1197 assert(referenceCount == 0, "Deleting Memory with non-zero reference count"); 1198 } 1199 } 1200 1201 // ************************************************************************ 1202 1203 package: 1204 1205 /// How many bytes are currently in `Data`-owned memory. 1206 static /*thread-local*/ size_t dataMemory, dataMemoryPeak; 1207 /// How many `Memory` instances there are live currently. 1208 static /*thread-local*/ uint dataCount; 1209 /// How many allocations have been done so far. 1210 static /*thread-local*/ uint allocCount; 1211 1212 /// Set threshold of allocated memory to trigger a garbage collection. 1213 static if (useGC) 1214 void setGCThreshold(size_t value) { collectThreshold = value; } 1215 1216 /// Allocate and construct a new class in `malloc`'d memory. 1217 C unmanagedNew(C, Args...)(auto ref Args args) @trusted 1218 if (is(C == class)) 1219 { 1220 import std.conv : emplace; 1221 enum size = __traits(classInstanceSize, C); 1222 auto p = unmanagedAlloc(size); 1223 emplace!C(p[0..size], args); 1224 return cast(C)p; 1225 } 1226 1227 /// Delete a class instance created with `unmanagedNew`. 1228 void unmanagedDelete(C)(C c) nothrow @nogc 1229 if (is(C == class)) 1230 { 1231 // Add attributes to object.destroy by cast: 1232 // - Add @nogc. 1233 // Object.~this is not @nogc, but allocating in a destructor crashes the GC anyway, 1234 // so all class destructors are already effectively @nogc. 1235 // - Add pure as well. 1236 // Memory implementations may have impure destructors, 1237 // such as closing file descriptors for memory-mapped files. 1238 // However, implementations SHOULD be pure as far as the program's state is concerned. 1239 static void callDestroy(C c) nothrow { c.destroy(); } 1240 // No pure due to: https://issues.dlang.org/show_bug.cgi?id=23959 1241 (cast(void function(C) /*pure*/ nothrow @nogc) &callDestroy)(c); 1242 1243 unmanagedFree(cast(void*)c); 1244 } 1245 1246 void* unmanagedAlloc(size_t sz) pure nothrow @nogc 1247 { 1248 import core.stdc.stdlib : malloc; 1249 1250 // Cast to add `pure` to malloc. 1251 // Allocating with `new` is pure, and so should be malloc. 1252 alias PureMalloc = extern (C) void* function(size_t) pure nothrow @nogc @system; 1253 auto p = (cast(PureMalloc) &malloc)(sz); 1254 1255 debug(DATA_REFCOUNT) debugLog("? -> %p: Allocating via malloc (%d bytes)", p, cast(uint)sz); 1256 1257 if (!p) 1258 //throw new OutOfMemoryError(); 1259 onOutOfMemoryError(); // @nogc 1260 1261 //GC.addRange(p, sz); 1262 return p; 1263 } 1264 1265 void unmanagedFree(void* p) pure nothrow @nogc 1266 { 1267 import core.stdc.stdlib : free; 1268 1269 if (p) 1270 { 1271 debug(DATA_REFCOUNT) debugLog("? -> %p: Deleting via free", p); 1272 1273 //GC.removeRange(p); 1274 1275 // Cast to add `pure` to free. 1276 // Same rationale as for malloc. 1277 alias PureFree = extern (C) void function(void* ptr) pure nothrow @nogc @system; 1278 (cast(PureFree) &free)(p); 1279 } 1280 } 1281 1282 version (Windows) 1283 import core.sys.windows.windows; 1284 else 1285 { 1286 import core.sys.posix.unistd; 1287 import core.sys.posix.sys.mman; 1288 } 1289 1290 static if (useGC) 1291 { 1292 /// Threshold of allocated memory to trigger a collect. 1293 __gshared size_t collectThreshold = 8*1024*1024; // 8MB 1294 /// Counter towards the threshold. 1295 /*thread-local*/ size_t allocatedThreshold; 1296 } 1297 1298 /// Some form of dynamically-allocated memory. 1299 /// Implementation is provided by the Allocator parameter. 1300 /*private*/ class DynamicMemory(Allocator) : Memory 1301 { 1302 /// Pointer to actual data. 1303 ubyte* data; 1304 /// Used size. Needed for safe appends. 1305 size_t _size; 1306 /// Allocated capacity. 1307 size_t _capacity; 1308 1309 static if (useGC) 1310 { 1311 deprecated alias collectThreshold = .collectThreshold; 1312 deprecated alias allocatedThreshold = .allocatedThreshold; 1313 } 1314 1315 /// Create a new instance with given capacity. 1316 this(size_t size, size_t capacity) pure @trusted nothrow @nogc 1317 { 1318 // Add attributes to the implementation by cast: 1319 // - Add pure. 1320 // - The implementation is "pure" in the same way that the D 1321 // garbage collector is "pure", even though it has global state. 1322 // - Add @nogc. 1323 // - There are a few common use cases for the @nogc attribute: 1324 // 1. The entire program does not use the GC (and probably does not even link to one). 1325 // 2. The program does use the GC, but some sections should not 1326 // (e.g. they perform only performance-sensitive computations 1327 // and accidental GC allocations should be caught and avoided). 1328 // 3. The code is a library which wants to be usable by either GC or @nogc programs. 1329 // - The second case is the most common in my personal experience, 1330 // so by default we assume that a GC is present but still offer a @nogc interface. 1331 // - We do this to offer better memory usage and reclaim memory faster 1332 // when Data instances are on the D GC heap, but are unreferenced. 1333 // - To actually use this module without the D GC, compile with -version=ae_data_nogc. 1334 // - (Ideally, we would offer both a @nogc and non-@nogc interface, 1335 // and let the caller's @nogc-ness select which one is used, 1336 // in the same way that the compiler can choose between a @nogc and non-@nogc overload, 1337 // however this is not currently feasible to implement.) 1338 (cast(void delegate(size_t size, size_t capacity) pure @trusted nothrow @nogc)&thisImpl)(size, capacity); 1339 } 1340 1341 private final void thisImpl(size_t size, size_t capacity) @trusted nothrow 1342 { 1343 data = cast(ubyte*)Allocator.allocate(/*ref*/ capacity); 1344 static if (useGC) 1345 if (data is null) 1346 { 1347 debug(DATA) fprintf(stderr, "Garbage collect triggered by failed Data allocation of %llu bytes... ", cast(ulong)capacity); 1348 GC.collect(); 1349 debug(DATA) fprintf(stderr, "Done\n"); 1350 data = cast(ubyte*)Allocator.allocate(/*ref*/ capacity); 1351 .allocatedThreshold = 0; 1352 } 1353 if (data is null) 1354 onOutOfMemoryError(); 1355 1356 dataMemory += capacity; 1357 if (dataMemoryPeak < dataMemory) 1358 dataMemoryPeak = dataMemory; 1359 dataCount ++; 1360 allocCount ++; 1361 1362 this._size = size; 1363 this._capacity = capacity; 1364 1365 static if (useGC) 1366 { 1367 // also collect 1368 .allocatedThreshold += capacity; 1369 if (.allocatedThreshold > .collectThreshold) 1370 { 1371 debug(DATA) fprintf(stderr, "Garbage collect triggered by total allocated Data exceeding threshold... "); 1372 GC.collect(); 1373 debug(DATA) fprintf(stderr, "Done\n"); 1374 .allocatedThreshold = 0; 1375 } 1376 } 1377 } 1378 1379 /// Destructor - destroys the wrapped data. 1380 ~this() @nogc 1381 { 1382 Allocator.deallocate(data, capacity); 1383 data = null; 1384 // If Memory is created and manually deleted, there is no need to cause frequent collections 1385 static if (useGC) 1386 { 1387 if (.allocatedThreshold > capacity) 1388 .allocatedThreshold -= capacity; 1389 else 1390 .allocatedThreshold = 0; 1391 } 1392 1393 dataMemory -= capacity; 1394 dataCount --; 1395 } 1396 1397 @property override 1398 size_t size() const pure @safe nothrow @nogc { return _size; } 1399 1400 @property override 1401 size_t capacity() const pure @safe nothrow @nogc { return _capacity; } 1402 1403 override void setSize(size_t newSize) pure @safe nothrow @nogc 1404 { 1405 assert(newSize <= capacity); 1406 _size = newSize; 1407 } 1408 1409 @property override 1410 inout(ubyte)[] contents() inout pure @trusted nothrow @nogc 1411 { 1412 return data[0 .. _size]; 1413 } 1414 } 1415 1416 // TODO: Maybe use std.experimental.allocator, one day. 1417 // One blocker is that it needs to stop pretending the page size is 4096 everywhere. 1418 1419 private struct OSAllocator 1420 { 1421 static immutable size_t pageSize; 1422 1423 shared static this() 1424 { 1425 version (Windows) 1426 { 1427 import core.sys.windows.winbase : GetSystemInfo, SYSTEM_INFO; 1428 1429 SYSTEM_INFO si; 1430 GetSystemInfo(&si); 1431 pageSize = si.dwPageSize; 1432 } 1433 else 1434 { 1435 pageSize = sysconf(_SC_PAGE_SIZE); 1436 } 1437 } 1438 1439 static void* allocate(ref size_t size) /*pure*/ nothrow @nogc 1440 { 1441 if (is(typeof(pageSize))) 1442 size = ((size-1) | (pageSize-1))+1; 1443 1444 version(Windows) 1445 { 1446 return VirtualAlloc(null, size, MEM_COMMIT, PAGE_READWRITE); 1447 } 1448 else 1449 version(Posix) 1450 { 1451 version(linux) 1452 import core.sys.linux.sys.mman : MAP_ANON; 1453 auto p = mmap(null, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 1454 return (p == MAP_FAILED) ? null : p; 1455 } 1456 else 1457 return core.stdc.malloc(size); 1458 } 1459 1460 static void deallocate(void* p, size_t size) @nogc 1461 { 1462 debug 1463 { 1464 (cast(ubyte*)p)[0..size] = 0xDB; 1465 } 1466 version(Windows) 1467 VirtualFree(p, 0, MEM_RELEASE); 1468 else 1469 version(Posix) 1470 munmap(p, size); 1471 else 1472 core.stdc.free(size); 1473 } 1474 } 1475 1476 /// Wrapper for data in RAM, allocated from the OS. 1477 alias OSMemory = DynamicMemory!OSAllocator; 1478 1479 private struct CAllocator 1480 { 1481 static void* allocate(ref size_t size) /*pure*/ nothrow @nogc 1482 { 1483 import core.stdc.stdlib : malloc; 1484 return malloc(size); 1485 } 1486 1487 static void deallocate(void* p, size_t size) @nogc 1488 { 1489 import core.stdc.stdlib : free; 1490 free(p); 1491 } 1492 } 1493 1494 /// Wrapper for data in RAM, allocated from the C standard library. 1495 /// Used for small objects. 1496 alias CMemory = DynamicMemory!CAllocator; 1497 1498 // ************************************************************************ 1499 1500 debug(DATA_REFCOUNT) import ae.utils.exception, ae.sys.memory, core.stdc.stdio; 1501 1502 debug(DATA_REFCOUNT) void debugLog(Args...)(const char* s, Args args) @nogc 1503 { 1504 fprintf(stderr, s, args); 1505 fprintf(stderr, "\n"); 1506 if (inCollect()) 1507 fprintf(stderr, "\t(in GC collect)\n"); 1508 else 1509 (cast(void function() @nogc)&debugStackTrace)(); 1510 fflush(core.stdc.stdio.stderr); 1511 } 1512 1513 debug(DATA_REFCOUNT) void debugStackTrace() 1514 { 1515 try 1516 foreach (line; getStackTrace()) 1517 fprintf(stderr, "\t%.*s\n", cast(int)line.length, line.ptr); 1518 catch (Throwable e) 1519 fprintf(stderr, "\t(stacktrace error: %.*s)", cast(int)e.msg.length, e.msg.ptr); 1520 }