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 bool opEquals(ref TData other) const { return data == other.data; } 743 bool opEquals(TData other) const { return data == other.data; } 744 bool opEquals(scope T[] other) const { return data == other; } 745 746 package(ae) // ditto 747 static if (is(T == ubyte)) 748 deprecated sizediff_t indexOf(const(void)[] needle) const 749 { 750 return indexOf(cast(const(ubyte)[])needle); 751 } 752 753 // --- Range operations 754 755 /// Range primitive. 756 @property bool empty() const { return length == 0; } 757 static if (!is(Unqual!T == void)) 758 T front() { return data[0]; } /// 759 void popFront() { this = this[1 .. $]; } /// 760 761 // /// Return a new `Data` for the first `size` bytes, and slice this instance from size to end. 762 // Data popFront(size_t size) 763 // in 764 // { 765 // assert(size <= length); 766 // } 767 // do 768 // { 769 // Data result = this; 770 // result.contents = contents[0..size]; 771 // this .contents = contents[size..$]; 772 // return result; 773 // } 774 } 775 776 debug(ae_unittest) unittest 777 { 778 import core.exception : AssertError; 779 import core.memory : GC; 780 import std.exception : assertThrown; 781 import ae.utils.array : emptySlice; 782 783 alias AliasSeq(TList...) = TList; 784 foreach (B; AliasSeq!(ubyte, uint, char, void)) 785 { 786 alias Ts = AliasSeq!(B, const(B), immutable(B)); 787 foreach (T; Ts) 788 { 789 // Template instantiation 790 { 791 TData!T d; 792 cast(void) d; 793 } 794 // .enter type 795 { 796 TData!T d; 797 d.enter((scope contents) { T[] _ = contents; }); 798 } 799 // .enter with functors 800 { 801 import ae.utils.functor.primitives : functor; 802 TData!T d; 803 d.enter(functor!((contents) { 804 assert(contents == d.unsafeContents); 805 })); 806 } 807 // // .enter with const 808 // { 809 // const TData!T d; 810 // d.enter((scope contents) { const T[] _ = contents; }); 811 // } 812 // .enter with return value 813 { 814 TData!T d; 815 auto l = d.enter((T[] contents) => contents.length); 816 assert(l == d.length); 817 } 818 // Construction from typeof(null) 819 { 820 auto d = TData!T(null); 821 assert(d.length == 0); 822 assert(d.unsafeContents.ptr is null); 823 } 824 // Construction from null slice 825 { 826 T[] arr = null; 827 auto d = TData!T(arr); 828 assert(d.length == 0); 829 assert(d.unsafeContents.ptr is null); 830 } 831 // Construction from non-null empty 832 { 833 auto d = TData!T(emptySlice!T()); 834 assert(d.length == 0); 835 assert(d.unsafeContents.ptr !is null); 836 } 837 // Construction from non-empty non-GC slice 838 { 839 T[5] arr = void; 840 assert(GC.addrOf(arr.ptr) is null); 841 auto d = TData!T(arr[]); 842 assert(d.length == 5); 843 assert(d.unsafeContents.ptr !is null); 844 assert(GC.addrOf(d.unsafeContents.ptr) is null); 845 } 846 // Construction from non-empty GC slice 847 { 848 T[] arr = new T[5]; 849 assert(GC.addrOf(arr.ptr) !is null); 850 auto d = TData!T(arr); 851 assert(d.length == 5); 852 assert(d.unsafeContents.ptr !is null); 853 assert(GC.addrOf(d.unsafeContents.ptr) is null); 854 } 855 // wrapGC from GC slice 856 static if (useGC) 857 {{ 858 T[] arr = new T[5]; 859 auto d = TData!T.wrapGC(arr); 860 assert(d.length == 5); 861 assert(d.unsafeContents.ptr is arr.ptr); 862 }} 863 // wrapGC from non-GC slice 864 static if (useGC) 865 {{ 866 static T[5] arr = void; 867 assertThrown!AssertError(TData!T.wrapGC(arr[])); 868 }} 869 // .capacity 870 { 871 T[] arr = new T[5]; 872 auto d = TData!T(arr); 873 assert(d.capacity >= 5); 874 auto d2 = d[0 .. 3]; 875 assert(d2.capacity == 3); 876 d = null; 877 assert(d2.capacity >= 5); // Sole reference; safe to expand over old data 878 } 879 880 // Try a bunch of operations with different kinds of instances 881 static T[5] arr = void; 882 TData!T delegate()[] generators = [ 883 delegate () => TData!T(), 884 delegate () => TData!T(null), 885 delegate () => TData!T(T[].init), 886 delegate () => TData!T(arr[]), 887 delegate () => TData!T(arr[0 .. 0]), 888 delegate () => TData!T(arr[].dup), 889 delegate () => TData!T(arr[].dup[0 .. 0]), 890 ]; 891 static if (useGC) 892 generators ~= [ 893 delegate () => TData!T.wrapGC(arr[].dup), 894 delegate () => TData!T.wrapGC(arr[].dup[0 .. 0]), 895 ]; 896 static if (is(B == void)) 897 foreach (B2; AliasSeq!(ubyte, uint, char, void)) 898 foreach (T2; AliasSeq!(B2, const(B2), immutable(B2))) 899 static if (is(typeof({ T2[] u; T[] t = u; }))) 900 { 901 static T2[5] arr2 = void; 902 generators ~= [ 903 delegate () => TData!T(T2[].init), 904 delegate () => TData!T(arr2[]), 905 delegate () => TData!T(arr2[0 .. 0]), 906 delegate () => TData!T(arr2[].dup), 907 delegate () => TData!T(arr2[].dup[0 .. 0]), 908 ]; 909 static if (useGC) 910 generators ~= [ 911 delegate () => TData!T.wrapGC(arr2[].dup), 912 // delegate () => TData!T.wrapGC(arr2[].dup[0 .. 0]), // TODO: why not? 913 ]; 914 } 915 foreach (generator; generators) 916 { 917 // General coherency 918 { 919 auto d = generator(); 920 auto length = d.length; 921 auto contents = d.unsafeContents; 922 assert(contents.length == length); 923 size_t entered; 924 d.enter((enteredContents) { 925 assert(enteredContents is contents); 926 entered++; 927 }); 928 assert(entered == 1); 929 assert(d.dup.unsafeContents == contents); 930 } 931 // Lifetime with .enter 932 { 933 auto d = generator(); 934 d.enter((contents) { 935 d = typeof(d)(null); 936 (cast(ubyte[])contents)[] = 42; 937 }); 938 } 939 // toGC 940 { 941 auto d = generator(); 942 auto contents = d.unsafeContents; 943 assert(d.toGC() == contents); 944 } 945 // In-place expansion (resize to capacity) 946 { 947 auto d = generator(); 948 auto oldContents = d.unsafeContents; 949 d.length = d.capacity; 950 assert(d.unsafeContents.ptr == oldContents.ptr); 951 } 952 // Copying expansion (resize past capacity) 953 { 954 auto d = generator(); 955 auto oldContents = d.unsafeContents; 956 d.length = d.capacity + 1; 957 assert(d.unsafeContents.ptr != oldContents.ptr); 958 } 959 // Equality 960 { 961 auto d1 = generator(); 962 auto d2 = generator(); 963 assert(d1 == d2); 964 } 965 // Concatenation 966 { 967 void test(L, R)(L left, R right) 968 { 969 { 970 auto result = left ~ right; 971 assert(result.length == left.length + right.length); 972 // TODO: test contents, need opEquals? 973 } 974 static if (!is(Unqual!T == void)) 975 { 976 if (left.length) 977 { 978 auto result = left[0] ~ right; 979 assert(result.length == 1 + right.length); 980 } 981 if (right.length) 982 { 983 auto result = left ~ right[0]; 984 assert(result.length == left.length + 1); 985 } 986 } 987 } 988 989 foreach (generator2; generators) 990 { 991 test(generator(), generator2()); 992 test(generator().toGC, generator2()); 993 test(generator(), generator2().toGC); 994 } 995 } 996 // Appending 997 { 998 void test(L, R)(L left, R right) 999 { 1000 { 1001 auto result = left; 1002 result ~= right; 1003 assert(result.length == left.length + right.length); 1004 // TODO: test contents, need opEquals? 1005 } 1006 static if (!is(Unqual!T == void)) 1007 { 1008 if (right.length) 1009 { 1010 auto result = left; 1011 result ~= right[0]; 1012 assert(result.length == left.length + 1); 1013 } 1014 } 1015 } 1016 1017 foreach (generator2; generators) 1018 { 1019 test(generator(), generator2()); 1020 // test(generator().toGC, generator2()); 1021 test(generator(), generator2().toGC); 1022 } 1023 } 1024 // Slicing to zero 1025 { 1026 auto l = generator().length; 1027 foreach (n; [0, l/2, l]) 1028 { 1029 auto d = generator(); 1030 d = d[n .. n]; 1031 assert(d == d); 1032 } 1033 } 1034 // Shrinking to zero 1035 { 1036 auto d = generator(); 1037 d.length = 0; 1038 assert(d == d); 1039 } 1040 // Reference count 1041 { 1042 auto d = generator(); 1043 if (d.memory) 1044 { 1045 assert(d.memory.referenceCount == 1); 1046 { 1047 auto s = d[1..4]; 1048 assert(d.memory.referenceCount == 2); 1049 cast(void) s; 1050 } 1051 assert(d.memory.referenceCount == 1); 1052 } 1053 } 1054 } 1055 1056 // Test effects of construction from various sources 1057 { 1058 void testSource(S)(S s) 1059 { 1060 void testData(TData!T d) 1061 { 1062 // Test true-ish-ness 1063 assert(!! s == !! d); 1064 // Test length 1065 static if (is(typeof(s.length))) 1066 assert(s.length * s[0].sizeof == d.length * T.sizeof); 1067 // Test content 1068 assert(s == d.unsafeContents); 1069 } 1070 // Construction 1071 testData(TData!T(s)); 1072 // wrapGC 1073 static if (useGC) 1074 static if (is(typeof(*s.ptr) == T)) 1075 if (GC.addrOf(s.ptr)) 1076 testData(TData!T.wrapGC(s)); 1077 // Appending 1078 { 1079 TData!T d; 1080 d ~= s; 1081 } 1082 } 1083 testSource(null); 1084 testSource(T[].init); 1085 testSource(arr[]); 1086 testSource(arr[0 .. 0]); 1087 testSource(arr[].dup); 1088 testSource(arr[].dup[0 .. 0]); 1089 testSource(arr[].idup); 1090 testSource(arr[].idup[0 .. 0]); 1091 static if (is(B == void)) 1092 foreach (B2; AliasSeq!(ubyte, uint, char, void)) 1093 foreach (T2; AliasSeq!(B2, const(B2), immutable(B2))) 1094 static if (is(typeof({ T2[] u; T[] t = u; }))) 1095 { 1096 static T2[5] arr2 = void; 1097 testSource(T2[].init); 1098 testSource(arr2[]); 1099 testSource(arr2[0 .. 0]); 1100 testSource(arr2[].dup); 1101 testSource(arr2[].dup[0 .. 0]); 1102 } 1103 } 1104 1105 foreach (U; Ts) 1106 { 1107 // Construction from compatible slice 1108 { 1109 U[] u; 1110 TData!T(u); 1111 static if (useGC) 1112 static if (is(typeof({ T[] t = u; }))) 1113 cast(void) TData!T.wrapGC(u); 1114 } 1115 } 1116 } 1117 } 1118 } 1119 1120 // pure/@safe/nothrow/@nogc compilation test 1121 // No pure due to https://issues.dlang.org/show_bug.cgi?id=23959 1122 debug(ae_unittest) /*pure*/ @safe nothrow @nogc unittest 1123 { 1124 TData!ubyte d; 1125 d.enter((scope contents) { ubyte[] _ = contents; }); 1126 d = TData!ubyte(null); 1127 assert(d.length == 0); 1128 1129 ubyte[] arr1 = null; 1130 d = TData!ubyte(arr1); 1131 1132 ubyte[0] arr2; 1133 d = TData!ubyte(arr2[]); 1134 1135 ubyte[5] arr3 = void; 1136 d = TData!ubyte(arr3[]); 1137 1138 d.enter((contents) { 1139 d = typeof(d)(null); 1140 (cast(ubyte[])contents)[] = 42; 1141 }); 1142 1143 d.length = d.length + 1; 1144 d.length = d.capacity; 1145 1146 d = d ~ d; 1147 d ~= d; 1148 d.enter((contents) { 1149 d = d ~ contents; 1150 d = contents ~ d; 1151 d ~= contents; 1152 }); 1153 } 1154 1155 debug(ae_unittest) unittest 1156 { 1157 import std.format : format; 1158 assert(format!"%s"(TData!char("hello")) == "hello"); 1159 } 1160 1161 /// Get the underlying type of a `TData`. 1162 /// (For `Data`, this will be `ubyte`.) 1163 template DataElementType(D) 1164 if (is(D == TData!T, T)) 1165 { 1166 static if (is(D == TData!T, T)) 1167 alias DataElementType = T; 1168 } 1169 static assert(is(DataElementType!Data == ubyte)); 1170 1171 /// The most common use case of manipulating unmanaged memory is 1172 /// working with raw bytes, whether they're received from the network, 1173 /// read from a file, or elsewhere. 1174 alias Data = TData!ubyte; 1175 1176 // ************************************************************************ 1177 1178 deprecated("legacy transitive import - please `import ae.sys.dataset;`.") 1179 public import ae.sys.dataset : copyTo, joinData, joinToHeap, DataVec, shift, bytes, DataSetBytes; 1180 1181 deprecated alias DataWrapper = Memory; 1182 1183 package(ae) // TODO: is this a good API? 1184 T as(T)(Data data) 1185 { 1186 assert(data.length == T.sizeof, "Incorrect data size"); 1187 return data.asDataOf!T.front; 1188 } 1189 1190 package(ae) // TODO: is this a good API? 1191 T pop(T)(ref Data data) 1192 { 1193 assert(data.length >= T.sizeof, "Insufficient bytes in data"); 1194 auto result = data[0 .. T.sizeof].asDataOf!T.front; 1195 data = data[T.sizeof .. $]; 1196 return result; 1197 } 1198 1199 // ************************************************************************ 1200 1201 /// Base abstract class which owns a block of memory. 1202 abstract class Memory 1203 { 1204 sizediff_t referenceCount = 0; /// Reference count. 1205 abstract @property inout(ubyte)[] contents() inout pure @safe nothrow @nogc; /// The owned memory 1206 abstract @property size_t size() const pure @safe nothrow @nogc; /// Length of `contents`. 1207 abstract void setSize(size_t newSize) pure @safe nothrow @nogc; /// Resize `contents` up to `capacity`. 1208 abstract @property size_t capacity() const pure @safe nothrow @nogc; /// Maximum possible size. 1209 1210 debug ~this() nothrow @nogc 1211 { 1212 debug(DATA_REFCOUNT) debugLog("%.*s.~this, referenceCount==%d", this.classinfo.name.length, this.classinfo.name.ptr, referenceCount); 1213 assert(referenceCount == 0, "Deleting Memory with non-zero reference count"); 1214 } 1215 } 1216 1217 // ************************************************************************ 1218 1219 package: 1220 1221 /// How many bytes are currently in `Data`-owned memory. 1222 static /*thread-local*/ size_t dataMemory, dataMemoryPeak; 1223 /// How many `Memory` instances there are live currently. 1224 static /*thread-local*/ uint dataCount; 1225 /// How many allocations have been done so far. 1226 static /*thread-local*/ uint allocCount; 1227 1228 /// Set threshold of allocated memory to trigger a garbage collection. 1229 static if (useGC) 1230 void setGCThreshold(size_t value) { collectThreshold = value; } 1231 1232 /// Allocate and construct a new class in `malloc`'d memory. 1233 C unmanagedNew(C, Args...)(auto ref Args args) @trusted 1234 if (is(C == class)) 1235 { 1236 import std.conv : emplace; 1237 enum size = __traits(classInstanceSize, C); 1238 auto p = unmanagedAlloc(size); 1239 emplace!C(p[0..size], args); 1240 return cast(C)p; 1241 } 1242 1243 /// Delete a class instance created with `unmanagedNew`. 1244 void unmanagedDelete(C)(C c) nothrow @nogc 1245 if (is(C == class)) 1246 { 1247 // Add attributes to object.destroy by cast: 1248 // - Add @nogc. 1249 // Object.~this is not @nogc, but allocating in a destructor crashes the GC anyway, 1250 // so all class destructors are already effectively @nogc. 1251 // - Add pure as well. 1252 // Memory implementations may have impure destructors, 1253 // such as closing file descriptors for memory-mapped files. 1254 // However, implementations SHOULD be pure as far as the program's state is concerned. 1255 static void callDestroy(C c) nothrow { c.destroy(); } 1256 // No pure due to: https://issues.dlang.org/show_bug.cgi?id=23959 1257 (cast(void function(C) /*pure*/ nothrow @nogc) &callDestroy)(c); 1258 1259 unmanagedFree(cast(void*)c); 1260 } 1261 1262 void* unmanagedAlloc(size_t sz) pure nothrow @nogc 1263 { 1264 import core.stdc.stdlib : malloc; 1265 1266 // Cast to add `pure` to malloc. 1267 // Allocating with `new` is pure, and so should be malloc. 1268 alias PureMalloc = extern (C) void* function(size_t) pure nothrow @nogc @system; 1269 auto p = (cast(PureMalloc) &malloc)(sz); 1270 1271 debug(DATA_REFCOUNT) debugLog("? -> %p: Allocating via malloc (%d bytes)", p, cast(uint)sz); 1272 1273 if (!p) 1274 //throw new OutOfMemoryError(); 1275 onOutOfMemoryError(); // @nogc 1276 1277 //GC.addRange(p, sz); 1278 return p; 1279 } 1280 1281 void unmanagedFree(void* p) pure nothrow @nogc 1282 { 1283 import core.stdc.stdlib : free; 1284 1285 if (p) 1286 { 1287 debug(DATA_REFCOUNT) debugLog("? -> %p: Deleting via free", p); 1288 1289 //GC.removeRange(p); 1290 1291 // Cast to add `pure` to free. 1292 // Same rationale as for malloc. 1293 alias PureFree = extern (C) void function(void* ptr) pure nothrow @nogc @system; 1294 (cast(PureFree) &free)(p); 1295 } 1296 } 1297 1298 version (Windows) 1299 import core.sys.windows.windows; 1300 else 1301 { 1302 import core.sys.posix.unistd; 1303 import core.sys.posix.sys.mman; 1304 } 1305 1306 static if (useGC) 1307 { 1308 /// Threshold of allocated memory to trigger a collect. 1309 __gshared size_t collectThreshold = 8*1024*1024; // 8MB 1310 /// Counter towards the threshold. 1311 /*thread-local*/ size_t allocatedThreshold; 1312 } 1313 1314 /// Some form of dynamically-allocated memory. 1315 /// Implementation is provided by the Allocator parameter. 1316 /*private*/ class DynamicMemory(Allocator) : Memory 1317 { 1318 /// Pointer to actual data. 1319 ubyte* data; 1320 /// Used size. Needed for safe appends. 1321 size_t _size; 1322 /// Allocated capacity. 1323 size_t _capacity; 1324 1325 static if (useGC) 1326 { 1327 deprecated alias collectThreshold = .collectThreshold; 1328 deprecated alias allocatedThreshold = .allocatedThreshold; 1329 } 1330 1331 /// Create a new instance with given capacity. 1332 this(size_t size, size_t capacity) pure @trusted nothrow @nogc 1333 { 1334 // Add attributes to the implementation by cast: 1335 // - Add pure. 1336 // - The implementation is "pure" in the same way that the D 1337 // garbage collector is "pure", even though it has global state. 1338 // - Add @nogc. 1339 // - There are a few common use cases for the @nogc attribute: 1340 // 1. The entire program does not use the GC (and probably does not even link to one). 1341 // 2. The program does use the GC, but some sections should not 1342 // (e.g. they perform only performance-sensitive computations 1343 // and accidental GC allocations should be caught and avoided). 1344 // 3. The code is a library which wants to be usable by either GC or @nogc programs. 1345 // - The second case is the most common in my personal experience, 1346 // so by default we assume that a GC is present but still offer a @nogc interface. 1347 // - We do this to offer better memory usage and reclaim memory faster 1348 // when Data instances are on the D GC heap, but are unreferenced. 1349 // - To actually use this module without the D GC, compile with -version=ae_data_nogc. 1350 // - (Ideally, we would offer both a @nogc and non-@nogc interface, 1351 // and let the caller's @nogc-ness select which one is used, 1352 // in the same way that the compiler can choose between a @nogc and non-@nogc overload, 1353 // however this is not currently feasible to implement.) 1354 (cast(void delegate(size_t size, size_t capacity) pure @trusted nothrow @nogc)&thisImpl)(size, capacity); 1355 } 1356 1357 private final void thisImpl(size_t size, size_t capacity) @trusted nothrow 1358 { 1359 data = cast(ubyte*)Allocator.allocate(/*ref*/ capacity); 1360 static if (useGC) 1361 if (data is null) 1362 { 1363 debug(DATA) fprintf(stderr, "Garbage collect triggered by failed Data allocation of %llu bytes... ", cast(ulong)capacity); 1364 GC.collect(); 1365 debug(DATA) fprintf(stderr, "Done\n"); 1366 data = cast(ubyte*)Allocator.allocate(/*ref*/ capacity); 1367 .allocatedThreshold = 0; 1368 } 1369 if (data is null) 1370 onOutOfMemoryError(); 1371 1372 dataMemory += capacity; 1373 if (dataMemoryPeak < dataMemory) 1374 dataMemoryPeak = dataMemory; 1375 dataCount ++; 1376 allocCount ++; 1377 1378 this._size = size; 1379 this._capacity = capacity; 1380 1381 static if (useGC) 1382 { 1383 // also collect 1384 .allocatedThreshold += capacity; 1385 if (.allocatedThreshold > .collectThreshold) 1386 { 1387 debug(DATA) fprintf(stderr, "Garbage collect triggered by total allocated Data exceeding threshold... "); 1388 GC.collect(); 1389 debug(DATA) fprintf(stderr, "Done\n"); 1390 .allocatedThreshold = 0; 1391 } 1392 } 1393 } 1394 1395 /// Destructor - destroys the wrapped data. 1396 ~this() @nogc 1397 { 1398 Allocator.deallocate(data, capacity); 1399 data = null; 1400 // If Memory is created and manually deleted, there is no need to cause frequent collections 1401 static if (useGC) 1402 { 1403 if (.allocatedThreshold > capacity) 1404 .allocatedThreshold -= capacity; 1405 else 1406 .allocatedThreshold = 0; 1407 } 1408 1409 dataMemory -= capacity; 1410 dataCount --; 1411 } 1412 1413 @property override 1414 size_t size() const pure @safe nothrow @nogc { return _size; } 1415 1416 @property override 1417 size_t capacity() const pure @safe nothrow @nogc { return _capacity; } 1418 1419 override void setSize(size_t newSize) pure @safe nothrow @nogc 1420 { 1421 assert(newSize <= capacity); 1422 _size = newSize; 1423 } 1424 1425 @property override 1426 inout(ubyte)[] contents() inout pure @trusted nothrow @nogc 1427 { 1428 return data[0 .. _size]; 1429 } 1430 } 1431 1432 // TODO: Maybe use std.experimental.allocator, one day. 1433 // One blocker is that it needs to stop pretending the page size is 4096 everywhere. 1434 1435 private struct OSAllocator 1436 { 1437 static immutable size_t pageSize; 1438 1439 shared static this() 1440 { 1441 version (Windows) 1442 { 1443 import core.sys.windows.winbase : GetSystemInfo, SYSTEM_INFO; 1444 1445 SYSTEM_INFO si; 1446 GetSystemInfo(&si); 1447 pageSize = si.dwPageSize; 1448 } 1449 else 1450 { 1451 pageSize = sysconf(_SC_PAGE_SIZE); 1452 } 1453 } 1454 1455 static void* allocate(ref size_t size) /*pure*/ nothrow @nogc 1456 { 1457 if (is(typeof(pageSize))) 1458 size = ((size-1) | (pageSize-1))+1; 1459 1460 version(Windows) 1461 { 1462 return VirtualAlloc(null, size, MEM_COMMIT, PAGE_READWRITE); 1463 } 1464 else 1465 version(Posix) 1466 { 1467 version(linux) 1468 import core.sys.linux.sys.mman : MAP_ANON; 1469 auto p = mmap(null, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 1470 return (p == MAP_FAILED) ? null : p; 1471 } 1472 else 1473 return core.stdc.malloc(size); 1474 } 1475 1476 static void deallocate(void* p, size_t size) @nogc 1477 { 1478 debug 1479 { 1480 (cast(ubyte*)p)[0..size] = 0xDB; 1481 } 1482 version(Windows) 1483 VirtualFree(p, 0, MEM_RELEASE); 1484 else 1485 version(Posix) 1486 munmap(p, size); 1487 else 1488 core.stdc.free(size); 1489 } 1490 } 1491 1492 /// Wrapper for data in RAM, allocated from the OS. 1493 alias OSMemory = DynamicMemory!OSAllocator; 1494 1495 private struct CAllocator 1496 { 1497 static void* allocate(ref size_t size) /*pure*/ nothrow @nogc 1498 { 1499 import core.stdc.stdlib : malloc; 1500 return malloc(size); 1501 } 1502 1503 static void deallocate(void* p, size_t size) @nogc 1504 { 1505 import core.stdc.stdlib : free; 1506 free(p); 1507 } 1508 } 1509 1510 /// Wrapper for data in RAM, allocated from the C standard library. 1511 /// Used for small objects. 1512 alias CMemory = DynamicMemory!CAllocator; 1513 1514 // ************************************************************************ 1515 1516 debug(DATA_REFCOUNT) import ae.utils.exception, ae.sys.memory, core.stdc.stdio; 1517 1518 debug(DATA_REFCOUNT) void debugLog(Args...)(const char* s, Args args) @nogc 1519 { 1520 fprintf(stderr, s, args); 1521 fprintf(stderr, "\n"); 1522 if (inCollect()) 1523 fprintf(stderr, "\t(in GC collect)\n"); 1524 else 1525 (cast(void function() @nogc)&debugStackTrace)(); 1526 fflush(core.stdc.stdio.stderr); 1527 } 1528 1529 debug(DATA_REFCOUNT) void debugStackTrace() 1530 { 1531 try 1532 foreach (line; getStackTrace()) 1533 fprintf(stderr, "\t%.*s\n", cast(int)line.length, line.ptr); 1534 catch (Throwable e) 1535 fprintf(stderr, "\t(stacktrace error: %.*s)", cast(int)e.msg.length, e.msg.ptr); 1536 }