1 /** 2 * Wrappers for raw _data located in unmanaged memory. 3 * 4 * Using the Data type will only place a small object in managed memory, 5 * keeping the actual _data in unmanaged memory. 6 * A proxy class (DataWrapper) is used to safely allow multiple references to 7 * the same block of unmanaged memory. 8 * When the DataWrapper object is destroyed (either manually or by the garbage 9 * collector when there are no remaining Data references), the unmanaged 10 * memory is deallocated. 11 * 12 * This has the following advantage over using managed memory: 13 * $(UL 14 * $(LI Faster allocation and deallocation, since memory is requested from 15 * the OS directly as whole pages) 16 * $(LI Greatly reduced chance of memory leaks (on 32-bit platforms) due to 17 * stray pointers) 18 * $(LI Overall improved GC performance due to reduced size of managed heap) 19 * $(LI Memory is immediately returned to the OS when _data is deallocated) 20 * ) 21 * On the other hand, using Data has the following disadvantages: 22 * $(UL 23 * $(LI This module is designed to store raw _data which does not have any 24 * pointers. Storing objects containing pointers to managed memory is 25 * unsupported, and may result in memory corruption.) 26 * $(LI Small objects may be stored inefficiently, as the module requests 27 * entire pages of memory from the OS. Considering allocating one large 28 * object and use slices (Data instances) for individual objects.) 29 * $(LI Incorrect usage (i.e. retaining/escaping references to wrapped memory 30 * without keeping a reference to its corresponding DataWrapper) can 31 * result in dangling pointers and hard-to-debug memory corruption.) 32 * ) 33 * 34 * License: 35 * This Source Code Form is subject to the terms of 36 * the Mozilla Public License, v. 2.0. If a copy of 37 * the MPL was not distributed with this file, You 38 * can obtain one at http://mozilla.org/MPL/2.0/. 39 * 40 * Authors: 41 * Vladimir Panteleev <ae@cy.md> 42 */ 43 44 module ae.sys.data; 45 46 static import core.stdc.stdlib; 47 import core.stdc.string : memmove; 48 import std.traits; 49 import core.memory; 50 import core.exception; 51 debug import std.string; 52 public import ae.sys.dataset; 53 import ae.utils.math; 54 55 debug(DATA) import core.stdc.stdio; 56 57 // ideas/todo: 58 // * templatize (and forbid using aliased types)? 59 // * use heap (malloc/Windows heap API) for small objects? 60 // * reference counting? 61 // * "immutable" support? 62 63 /** 64 * Wrapper for data located in external memory, to prevent faux references. 65 * Represents a slice of data, which may or may not be in unmanaged memory. 66 * Data in unmanaged memory is bound to a DataWrapper class instance. 67 * 68 * All operations on this class should be safe, except for accessing contents directly. 69 * All operations on contents must be accompanied by a live reference to the Data object, 70 * to keep a GC anchor towards the unmanaged data. 71 * 72 * Concatenations and appends to Data contents will cause reallocations on the heap, consider using Data instead. 73 * 74 * Be sure not to lose Data references while using their contents! 75 * For example, avoid code like this: 76 * ---- 77 * fun(cast(string)transformSomeData(someData).contents); 78 * ---- 79 * The Data return value may be unreachable once .contents is evaluated. 80 * Use .toHeap instead of .contents in such cases to get a safe heap copy. 81 */ 82 struct Data 83 { 84 private: 85 /// Wrapped data 86 const(void)[] _contents; 87 /// Reference to the wrapper of the actual data - may be null to indicate wrapped data in managed memory. 88 /// Used as a GC anchor to unmanaged data, and for in-place expands (for appends). 89 DataWrapper wrapper; 90 /// Indicates whether we're allowed to modify the data contents. 91 bool mutable; 92 93 /// Maximum preallocation for append operations. 94 enum { MAX_PREALLOC = 4*1024*1024 } // must be power of 2 95 96 public: 97 /** 98 * Create new instance wrapping the given data. 99 * Params: 100 * data = initial data 101 * forceReallocation = when false, the contents will be duplicated to 102 * unmanaged memory only when it's not on the managed heap; when true, 103 * the contents will be reallocated always. 104 */ 105 this(const(void)[] data, bool forceReallocation = false) 106 { 107 if (data is null) 108 contents = null; 109 else 110 if (data.length == 0) 111 { 112 wrapper = emptyDataWrapper; 113 wrapper.references++; 114 contents = data; 115 } 116 else 117 if (forceReallocation || GC.addrOf(data.ptr) is null) 118 { 119 // copy to unmanaged memory 120 auto wrapper = unmanagedNew!MemoryDataWrapper(data.length, data.length); 121 this.wrapper = wrapper; 122 wrapper.contents[] = data[]; 123 contents = wrapper.contents; 124 mutable = true; 125 } 126 else 127 { 128 // just save a reference 129 contents = data; 130 mutable = false; 131 } 132 133 assert(this.length == data.length); 134 } 135 136 /// ditto 137 this(void[] data, bool forceReallocation = false) 138 { 139 const(void)[] cdata = data; 140 this(cdata, forceReallocation); 141 mutable = true; 142 } 143 144 /// Create a new instance with given size/capacity. Capacity defaults to size. 145 this(size_t size, size_t capacity = 0) 146 in 147 { 148 assert(capacity == 0 || size <= capacity); 149 } 150 do 151 { 152 if (!capacity) 153 capacity = size; 154 155 if (capacity) 156 { 157 auto wrapper = unmanagedNew!MemoryDataWrapper(size, capacity); 158 this.wrapper = wrapper; 159 contents = wrapper.contents; 160 mutable = true; 161 } 162 else 163 { 164 wrapper = null; 165 contents = null; 166 } 167 168 assert(this.length == size); 169 } 170 171 /// Create a new instance slicing all of the given wrapper's contents. 172 this(DataWrapper wrapper, bool mutable) 173 { 174 this.wrapper = wrapper; 175 this.mutable = mutable; 176 this.contents = wrapper.contents; 177 } 178 179 this(this) 180 { 181 if (wrapper) 182 { 183 wrapper.references++; 184 debug (DATA_REFCOUNT) debugLog("%p -> %p: Incrementing refcount to %d", cast(void*)&this, cast(void*)wrapper, wrapper.references); 185 } 186 else 187 debug (DATA_REFCOUNT) debugLog("%p -> %p: this(this) with no wrapper", cast(void*)&this, cast(void*)wrapper); 188 } 189 190 ~this() pure 191 { 192 //clear(); 193 // https://issues.dlang.org/show_bug.cgi?id=13809 194 (cast(void delegate() pure)&clear)(); 195 } 196 197 debug(DATA) invariant 198 { 199 if (wrapper) 200 assert(wrapper.references > 0, "Data referencing DataWrapper with bad reference count"); 201 } 202 203 /* 204 /// Create new instance as a slice over an existing DataWrapper. 205 private this(DataWrapper wrapper, size_t start = 0, size_t end = size_t.max) 206 { 207 this.wrapper = wrapper; 208 this.start = start; 209 this.end = end==size_t.max ? wrapper.capacity : end; 210 } 211 */ 212 213 /// Get contents 214 @property const(void)[] contents() const 215 { 216 return _contents; 217 } 218 219 @property private const(void)[] contents(const(void)[] data) 220 { 221 return _contents = data; 222 } 223 224 /// Get mutable contents 225 @property void[] mcontents() 226 { 227 if (!mutable && length) 228 { 229 reallocate(length, length); 230 assert(mutable); 231 } 232 return cast(void[])_contents; 233 } 234 235 /// Get pointer to contents 236 @property const(void)* ptr() const 237 { 238 return contents.ptr; 239 } 240 241 /// Get pointer to mutable contents 242 @property void* mptr() 243 { 244 return mcontents.ptr; 245 } 246 247 /// Size in bytes of contents 248 @property size_t length() const 249 { 250 return contents.length; 251 } 252 alias opDollar = length; /// ditto 253 254 /// True if contents is unset 255 @property bool empty() const 256 { 257 return contents is null; 258 } 259 260 bool opCast(T)() const 261 if (is(T == bool)) 262 { 263 return !empty; 264 } /// 265 266 /// Return maximum value that can be set to `length` without causing a reallocation 267 @property size_t capacity() const 268 { 269 if (wrapper is null) 270 return length; 271 // We can only safely expand if the memory slice is at the end of the used unmanaged memory block. 272 auto pos = ptr - wrapper.contents.ptr; // start position in wrapper data 273 auto end = pos + length; // end position in wrapper data 274 assert(end <= wrapper.size); 275 if (end == wrapper.size && end < wrapper.capacity) 276 return wrapper.capacity - pos; 277 else 278 return length; 279 } 280 281 /// Put a copy of the data on D's managed heap, and return it. 282 @property 283 void[] toHeap() const 284 { 285 return _contents.dup; 286 } 287 288 private void reallocate(size_t size, size_t capacity) 289 { 290 auto wrapper = unmanagedNew!MemoryDataWrapper(size, capacity); 291 wrapper.contents[0..this.length] = contents[]; 292 //(cast(ubyte[])newWrapper.contents)[this.length..value] = 0; 293 294 clear(); 295 this.wrapper = wrapper; 296 this.contents = wrapper.contents; 297 mutable = true; 298 } 299 300 private void expand(size_t newSize, size_t newCapacity) 301 in 302 { 303 assert(length < newSize); 304 assert(newSize <= newCapacity); 305 } 306 out 307 { 308 assert(length == newSize); 309 } 310 do 311 { 312 if (newCapacity <= capacity) 313 { 314 auto pos = ptr - wrapper.contents.ptr; // start position in wrapper data 315 wrapper.setSize(pos + newSize); 316 contents = ptr[0..newSize]; 317 } 318 else 319 reallocate(newSize, newCapacity); 320 } 321 322 /// Resize contents 323 @property void length(size_t value) 324 { 325 if (value == length) // no change 326 return; 327 if (value < length) // shorten 328 _contents = _contents[0..value]; 329 else // lengthen 330 expand(value, value); 331 } 332 333 /// Create a copy of the data 334 @property Data dup() const 335 { 336 return Data(contents, true); 337 } 338 339 /// This used to be an unsafe method which deleted the wrapped data. 340 /// Now that Data is refcounted, this simply calls clear() and 341 /// additionally asserts that this Data is the only Data holding 342 /// a reference to the wrapper. 343 void deleteContents() 344 out 345 { 346 assert(wrapper is null); 347 } 348 do 349 { 350 if (wrapper) 351 { 352 assert(wrapper.references == 1, "Attempting to call deleteContents with "); 353 clear(); 354 } 355 } 356 357 /// Unreference contents, freeing it if this was the last reference. 358 void clear() 359 { 360 if (wrapper) 361 { 362 assert(wrapper.references > 0, "Dangling pointer to wrapper"); 363 wrapper.references--; 364 debug (DATA_REFCOUNT) debugLog("%p -> %p: Decrementing refcount to %d", cast(void*)&this, cast(void*)wrapper, wrapper.references); 365 if (wrapper.references == 0) 366 wrapper.destroy(); 367 368 wrapper = null; 369 } 370 371 contents = null; 372 } 373 374 /// Create a new `Data` containing the concatenation of `this` and `data`. 375 /// Does not preallocate for successive appends. 376 Data concat(const(void)[] data) 377 { 378 if (data.length==0) 379 return this; 380 Data result = Data(length + data.length); 381 result.mcontents[0..this.length] = contents[]; 382 result.mcontents[this.length..$] = data[]; 383 return result; 384 } 385 386 /// ditto 387 template opBinary(string op) if (op == "~") 388 { 389 Data opBinary(T)(const(T)[] data) 390 if (!hasIndirections!T) 391 { 392 return concat(data); 393 } /// 394 395 Data opBinary()(Data data) 396 { 397 return concat(data.contents); 398 } /// 399 } 400 401 /// Create a new `Data` containing the concatenation of `data` and `this`. 402 /// Does not preallocate for successive appends. 403 Data prepend(const(void)[] data) 404 { 405 Data result = Data(data.length + length); 406 result.mcontents[0..data.length] = data[]; 407 result.mcontents[data.length..$] = contents[]; 408 return result; 409 } 410 411 /// ditto 412 template opBinaryRight(string op) if (op == "~") 413 { 414 Data opBinaryRight(T)(const(T)[] data) 415 if (!hasIndirections!T) 416 { 417 return prepend(data); 418 } /// 419 } 420 421 private static size_t getPreallocSize(size_t length) 422 { 423 if (length < MAX_PREALLOC) 424 return nextPowerOfTwo(length); 425 else 426 return ((length-1) | (MAX_PREALLOC-1)) + 1; 427 } 428 429 /// Append data to this `Data`. 430 /// Unlike concatenation (`a ~ b`), appending (`a ~= b`) will preallocate. 431 Data append(const(void)[] data) 432 { 433 if (data.length==0) 434 return this; 435 size_t oldLength = length; 436 size_t newLength = length + data.length; 437 expand(newLength, getPreallocSize(newLength)); 438 auto newContents = cast(void[])_contents[oldLength..$]; 439 newContents[] = (cast(void[])data)[]; 440 return this; 441 } 442 443 /// ditto 444 template opOpAssign(string op) if (op == "~") 445 { 446 Data opOpAssign(T)(const(T)[] data) 447 if (!hasIndirections!T) 448 { 449 return append(data); 450 } /// 451 452 Data opOpAssign()(Data data) 453 { 454 return append(data.contents); 455 } /// 456 457 Data opOpAssign()(ubyte value) // hack? 458 { 459 return append((&value)[0..1]); 460 } /// 461 } 462 463 /// Returns a `Data` pointing at a slice of this `Data`'s contents. 464 Data opSlice() 465 { 466 return this; 467 } 468 469 /// ditto 470 Data opSlice(size_t x, size_t y) 471 in 472 { 473 assert(x <= y); 474 assert(y <= length); 475 } 476 // https://issues.dlang.org/show_bug.cgi?id=13463 477 // out(result) 478 // { 479 // assert(result.length == y-x); 480 // } 481 do 482 { 483 if (x == y) 484 return Data(emptyDataWrapper.data[]); 485 else 486 { 487 Data result = this; 488 result.contents = result.contents[x..y]; 489 return result; 490 } 491 } 492 493 /// Return a new `Data` for the first `size` bytes, and slice this instance from size to end. 494 Data popFront(size_t size) 495 in 496 { 497 assert(size <= length); 498 } 499 do 500 { 501 Data result = this; 502 result.contents = contents[0..size]; 503 this .contents = contents[size..$]; 504 return result; 505 } 506 } 507 508 unittest 509 { 510 Data d = Data("aaaaa"); 511 assert(d.wrapper.references == 1); 512 Data s = d[1..4]; 513 assert(d.wrapper.references == 2); 514 } 515 516 // ************************************************************************ 517 518 /// How many bytes are currently in `Data`-owned memory. 519 static /*thread-local*/ size_t dataMemory, dataMemoryPeak; 520 /// How many `DataWrapper` instances there are live currently. 521 static /*thread-local*/ uint dataCount; 522 /// How many allocations have been done so far. 523 static /*thread-local*/ uint allocCount; 524 525 /// Base abstract class which owns a block of memory. 526 abstract class DataWrapper 527 { 528 sizediff_t references = 1; /// Reference count. 529 abstract @property inout(void)[] contents() inout; /// The owned memory 530 abstract @property size_t size() const; /// Length of `contents`. 531 abstract void setSize(size_t newSize); /// Resize `contents` up to `capacity`. 532 abstract @property size_t capacity() const; /// Maximum possible size. 533 534 debug ~this() @nogc 535 { 536 debug(DATA_REFCOUNT) debugLog("%.*s.~this, references==%d", this.classinfo.name.length, this.classinfo.name.ptr, references); 537 assert(references == 0, "Deleting DataWrapper with non-zero reference count"); 538 } 539 } 540 541 /// Set threshold of allocated memory to trigger a garbage collection. 542 void setGCThreshold(size_t value) { MemoryDataWrapper.collectThreshold = value; } 543 544 /// Allocate and construct a new class in `malloc`'d memory. 545 C unmanagedNew(C, Args...)(auto ref Args args) 546 if (is(C == class)) 547 { 548 import std.conv : emplace; 549 enum size = __traits(classInstanceSize, C); 550 auto p = unmanagedAlloc(size); 551 emplace!C(p[0..size], args); 552 return cast(C)p; 553 } 554 555 /// Delete a class instance created with `unmanagedNew`. 556 void unmanagedDelete(C)(C c) 557 if (is(C == class)) 558 { 559 c.__xdtor(); 560 unmanagedFree(p); 561 } 562 563 private: 564 565 void* unmanagedAlloc(size_t sz) 566 { 567 auto p = core.stdc.stdlib.malloc(sz); 568 569 debug(DATA_REFCOUNT) debugLog("? -> %p: Allocating via malloc (%d bytes)", p, cast(uint)sz); 570 571 if (!p) 572 throw new OutOfMemoryError(); 573 574 //GC.addRange(p, sz); 575 return p; 576 } 577 578 void unmanagedFree(void* p) @nogc 579 { 580 if (p) 581 { 582 debug(DATA_REFCOUNT) debugLog("? -> %p: Deleting via free", p); 583 584 //GC.removeRange(p); 585 core.stdc.stdlib.free(p); 586 } 587 } 588 589 version (Windows) 590 import core.sys.windows.windows; 591 else 592 { 593 import core.sys.posix.unistd; 594 import core.sys.posix.sys.mman; 595 } 596 597 /// Wrapper for data in RAM, allocated from the OS. 598 final class MemoryDataWrapper : DataWrapper 599 { 600 /// Pointer to actual data. 601 void* data; 602 /// Used size. Needed for safe appends. 603 size_t _size; 604 /// Allocated capacity. 605 size_t _capacity; 606 607 /// Threshold of allocated memory to trigger a collect. 608 __gshared size_t collectThreshold = 8*1024*1024; // 8MB 609 /// Counter towards the threshold. 610 static /*thread-local*/ size_t allocatedThreshold; 611 612 /// Create a new instance with given capacity. 613 this(size_t size, size_t capacity) 614 { 615 data = malloc(/*ref*/ capacity); 616 if (data is null) 617 { 618 debug(DATA) fprintf(stderr, "Garbage collect triggered by failed Data allocation of %llu bytes... ", cast(ulong)capacity); 619 GC.collect(); 620 debug(DATA) fprintf(stderr, "Done\n"); 621 data = malloc(/*ref*/ capacity); 622 allocatedThreshold = 0; 623 } 624 if (data is null) 625 onOutOfMemoryError(); 626 627 dataMemory += capacity; 628 if (dataMemoryPeak < dataMemory) 629 dataMemoryPeak = dataMemory; 630 dataCount ++; 631 allocCount ++; 632 633 this._size = size; 634 this._capacity = capacity; 635 636 // also collect 637 allocatedThreshold += capacity; 638 if (allocatedThreshold > collectThreshold) 639 { 640 debug(DATA) fprintf(stderr, "Garbage collect triggered by total allocated Data exceeding threshold... "); 641 GC.collect(); 642 debug(DATA) fprintf(stderr, "Done\n"); 643 allocatedThreshold = 0; 644 } 645 } 646 647 /// Destructor - destroys the wrapped data. 648 ~this() @nogc 649 { 650 free(data, capacity); 651 data = null; 652 // If DataWrapper is created and manually deleted, there is no need to cause frequent collections 653 if (allocatedThreshold > capacity) 654 allocatedThreshold -= capacity; 655 else 656 allocatedThreshold = 0; 657 658 dataMemory -= capacity; 659 dataCount --; 660 } 661 662 @property override 663 size_t size() const { return _size; } 664 665 @property override 666 size_t capacity() const @nogc { return _capacity; } 667 668 override void setSize(size_t newSize) 669 { 670 assert(newSize <= capacity); 671 _size = newSize; 672 } 673 674 @property override 675 inout(void)[] contents() inout 676 { 677 return data[0..size]; 678 } 679 680 // https://github.com/D-Programming-Language/druntime/pull/759 681 version(OSX) 682 enum _SC_PAGE_SIZE = 29; 683 684 // https://github.com/D-Programming-Language/druntime/pull/1140 685 version(FreeBSD) 686 enum _SC_PAGE_SIZE = 47; 687 688 version(Windows) 689 { 690 static immutable size_t pageSize; 691 692 shared static this() 693 { 694 SYSTEM_INFO si; 695 GetSystemInfo(&si); 696 pageSize = si.dwPageSize; 697 } 698 } 699 else 700 static if (is(typeof(_SC_PAGE_SIZE))) 701 { 702 static immutable size_t pageSize; 703 704 shared static this() 705 { 706 pageSize = sysconf(_SC_PAGE_SIZE); 707 } 708 } 709 710 static void* malloc(ref size_t size) 711 { 712 if (is(typeof(pageSize))) 713 size = ((size-1) | (pageSize-1))+1; 714 715 version(Windows) 716 { 717 return VirtualAlloc(null, size, MEM_COMMIT, PAGE_READWRITE); 718 } 719 else 720 version(Posix) 721 { 722 version(linux) 723 import core.sys.linux.sys.mman : MAP_ANON; 724 auto p = mmap(null, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 725 return (p == MAP_FAILED) ? null : p; 726 } 727 else 728 return core.stdc.malloc(size); 729 } 730 731 static void free(void* p, size_t size) @nogc 732 { 733 debug 734 { 735 (cast(ubyte*)p)[0..size] = 0xDB; 736 } 737 version(Windows) 738 VirtualFree(p, 0, MEM_RELEASE); 739 else 740 version(Posix) 741 munmap(p, size); 742 else 743 core.stdc.free(size); 744 } 745 } 746 747 // ************************************************************************ 748 749 /// DataWrapper implementation used for the empty (but non-null) Data slice. 750 class EmptyDataWrapper : DataWrapper 751 { 752 void[0] data; 753 754 override @property inout(void)[] contents() inout { return data[]; } 755 override @property size_t size() const { return data.length; } 756 override void setSize(size_t newSize) { assert(false); } 757 override @property size_t capacity() const { return data.length; } 758 } 759 760 __gshared EmptyDataWrapper emptyDataWrapper = new EmptyDataWrapper; 761 762 // ************************************************************************ 763 764 // Source: Win32 bindings project 765 version(Windows) 766 { 767 struct SYSTEM_INFO { 768 union { 769 DWORD dwOemId; 770 struct { 771 WORD wProcessorArchitecture; 772 WORD wReserved; 773 } 774 } 775 DWORD dwPageSize; 776 PVOID lpMinimumApplicationAddress; 777 PVOID lpMaximumApplicationAddress; 778 DWORD dwActiveProcessorMask; 779 DWORD dwNumberOfProcessors; 780 DWORD dwProcessorType; 781 DWORD dwAllocationGranularity; 782 WORD wProcessorLevel; 783 WORD wProcessorRevision; 784 } 785 alias SYSTEM_INFO* LPSYSTEM_INFO; 786 787 extern(Windows) VOID GetSystemInfo(LPSYSTEM_INFO); 788 } 789 790 debug(DATA_REFCOUNT) import ae.utils.exception, ae.sys.memory, std.stdio; 791 792 debug(DATA_REFCOUNT) void debugLog(Args...)(const char* s, Args args) @nogc 793 { 794 fprintf(stderr, s, args); 795 fprintf(stderr, "\n"); 796 if (inCollect()) 797 fprintf(stderr, "\t(in GC collect)\n"); 798 else 799 (cast(void function() @nogc)&debugStackTrace)(); 800 fflush(core.stdc.stdio.stderr); 801 } 802 803 debug(DATA_REFCOUNT) void debugStackTrace() 804 { 805 try 806 foreach (line; getStackTrace()) 807 writeln("\t", line); 808 catch (Throwable e) 809 writeln("\t(stacktrace error: ", e.msg, ")"); 810 }