1 /** 2 3 Contextual parameter system for D. 4 5 Copyright: Copyright Guillaume Piolat 2021. 6 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 8 This is an implicit context system like in Odin, itself probably inspired by Scala "implicit 9 parameters". In other words, a system to have scoped globals. 10 11 "In each scope, there is an implicit value named context. This CONTEXT VARIABLE is local to each 12 scope and is implicitly passed by pointer to any procedure call in that scope (if the procedure 13 has the Odin calling convention)." 14 15 Without language support, we don't have the ABI and the scope-specific context, however with TLS 16 and manual scopes we can emulate that to pass parameters in an implicit way (scoped globals, in a way). 17 18 Note: this module uses TLS, and C runtime. 19 20 Examples: 21 - allocators 22 - loggers 23 - the UI "context" in UIs 24 ... 25 26 Note: internal stack is handled with malloc/realloc/free from the C stdlib. Performance of internal 27 stack relies on a malloc implementation that is itself lockfree, else the realloc could 28 stall other threads from time to time. The context are also not designed to be very large. 29 30 Important difference: 31 `context.push()` and `context.pop()` are explicit here. 32 Leaving a D scope {} doesn't restore parent context (there is no language support), you need 33 to call `push`/`pop` manually. On the plus side, no ABI change are needed and context stack 34 is not meant to be touched very frequently. 35 36 37 Example: 38 39 context.set!int("userStuff", 4); 40 assert(context.get!int("userStuff") == 4); 41 42 void subProc() 43 { 44 context.push; 45 46 assert(context.get!int("userStuff") == 4); // follows chain of contexts 47 48 context.set!int("userStuff", 3); 49 assert(context.get!int("userStuff") == 3); // stack-like organization of contexts 50 51 context.pop; 52 53 assert(context.get!int("userStuff") == 4); // stack-like organization of contexts, with hierarchic namespacing 54 } 55 subProc(); 56 57 */ 58 // TODO: error system that propagates on popContext? so as to report error codes and exceptions. An "errno" extension? 59 // TODO: what to do for lifetime stuff. A function to be called on release? See also: COM. 60 // TODO: what about a context destructor? like an at_exit stack. What about a destructor per variable? 61 // TOOD: what about arrays? What about pointer to variable. 62 // TODO: what to do for GC roots? context might be scanned somehow. 63 // TODO: should contexts be copyable? Why does Odin do this? probably, to give one to a new thread. In that case, need to copy whole stack. 64 // TODO: use a stackallocator to be able to rely on things staying in place, no realloc penalty + alloca becomes possible. 65 module implicitcontext; 66 67 import core.stdc.stdlib : malloc, free, realloc; 68 import core.stdc.stdio: printf, vsnprintf; 69 import core.stdc.stdarg: va_start, va_end, va_list; 70 71 72 nothrow @nogc @safe: 73 74 75 76 77 public /* <Public Context API> */ 78 { 79 /// Return current context object. 80 /// An `ImplicitContext` is safely copyable, but only the top-most context per-thread can be 81 /// modified (like in _the_ stack). 82 /// There is no reason to store it though? 83 ImplicitContext context() 84 { 85 return g_contextStack.currentContext(); 86 } 87 88 /// A "context" implements per-thread scoped globals. It is a frame in the secundary stack. 89 struct ImplicitContext // shouldn't be named normally 90 { 91 public: 92 nothrow @nogc @safe: 93 94 /// Get a context variable. The look-up will chain to above contexts like sort of namespaces 95 /// or a dynamic cast. Topmost context gets the lookup, like namespaces or prototype chains. 96 /// 97 /// Note: Using the wrong type, or the wrong identifier, is a programming error and will crash. 98 /// Identifiers mask those of earlier scopes. 99 /// Lookup MUST succeed else it's a bug. 100 T get(T)(const(char)[] name) 101 { 102 T res; 103 if (query!T(name, res)) 104 return res; 105 else 106 assert(false); // no match, program error 107 } 108 109 /// Query a context variable (with the possibility that it doesn't exist). The look-up will 110 /// chain to above contexts like sort of namespaces or a dynamic cast. Topmost context gets 111 /// the lookup, like namespaces or prototype chains. 112 /// 113 /// Note: Using a mismatched type size is a programming error and will crash. 114 bool query(T)(const(char)[] name, out T res) @trusted 115 { 116 size_t contextOffset = offset; 117 while(contextOffset != 0) 118 { 119 bool found = getValue(contextOffset, name, cast(ubyte*)(&res), res.sizeof); 120 if (found) 121 { 122 return true; 123 } 124 125 // Jump to parent scope. 126 stack.readValue!size_t(contextOffset, contextOffset); 127 } 128 return false; 129 } 130 131 132 /// Set a context variable. 133 /// 134 /// Note: if the variable already exist in this context, it is modified. 135 /// But its size cannot change in this way, without a crash. 136 /// The context where you create a variable MUST be the topmost one, else it's a crash. 137 void set(T)(const(char)[] name, T value) @trusted 138 { 139 if (stack.offsetOfTopContext != offset) 140 { 141 // Can't set variable except in top context. 142 assert(false); 143 } 144 145 const(ubyte)* pvalue = cast(const(ubyte)*)&value; 146 147 // First check if it already exists. 148 ubyte* existing; 149 size_t varSize; 150 hashcode_t hashCode; 151 bool found = getValueLocationAndSize(offset, name, existing, varSize, hashCode); 152 if (found) 153 { 154 // modify in place 155 if (varSize != T.sizeof) 156 assert(false); // bad size, programming error. Type safety error checked at runtime. 157 158 foreach(n; 0..T.sizeof) 159 existing[n] = pvalue[n]; 160 } 161 else 162 { 163 stack.pushValue!hashcode_t(hashCode); 164 stack.pushValue!size_t(name.length); 165 stack.pushValue!size_t(T.sizeof); 166 stack.pushBytes(cast(ubyte*) name.ptr, name.length); 167 stack.pushBytes(cast(ubyte*) pvalue, T.sizeof); 168 169 // Increment number of entries 170 stack.incrementVariableCount(); 171 stack.updateContextHashCode(hashCode); 172 } 173 } 174 175 176 /+ 177 178 Doesn't work if the stack is reallocated. This will invalidate such pointers. 179 We need another internal allocator to support such, see stackallocator.d in Dplug 180 181 /// Allocates a temporary buffer on the context stack. 182 /// The lifetime of this buffer extends until the `popContext` is called. 183 /// Note: This buffer is not scanned, and shouldn't contain GC pointers. 184 /// You can't search stack allocation created that way by name. 185 void* alloca(size_t size) 186 { 187 if (stack.offsetOfTopContext != offset) 188 { 189 // Can't alloca except from top-context. 190 assert(false); 191 } 192 stack.pushValue!hashcode_t(0); // zero hashcode 193 stack.pushValue!size_t(0); 194 stack.pushValue!size_t(size); 195 void* p = stack.pushBytesUninitialized(size); 196 197 // Increment number of entries 198 stack.incrementVariableCount(); 199 200 // Note: no need to update bloom, since hash of empty string is zero. 201 202 return p; 203 } 204 205 +/ 206 207 /// Helper for `pushContext()`. 208 /// This isn't tied to this particular context, so `static` it is. 209 static void push() 210 { 211 pushContext(); 212 } 213 214 static void pop() 215 { 216 popContext(); 217 } 218 219 private: 220 221 /// Non-owning pointer to thread-local context stack. 222 ContextStack* stack; 223 224 /// Offset of the context position in the context stack. 225 /// This way on realloc we don't have to update the offsets of existing contexts. 226 size_t offset; 227 228 229 // Find the location of a value and its size in this context. 230 bool getValueLocationAndSize(size_t contextOffset, 231 scope const(char)[] name, 232 ref ubyte* location, 233 out size_t varSize, 234 out hashcode_t outHashCode) @trusted 235 { 236 // Compute hash of identifier 237 hashcode_t hashCode; 238 bool validName = validateContextIdentifier(name, hashCode); 239 if (!validName) 240 { 241 // If you fail here, it is because the identifier searched for is not a valid 242 // Context identifier. This is a programming error to use such a name. 243 // Only strings that would be valid D identifier can go into an implicit context as variable name. 244 assert(false); 245 } 246 247 outHashCode = hashCode; 248 249 size_t entries; 250 stack.readValue(contextOffset + size_t.sizeof, entries); 251 252 hashcode_t hashUnion; 253 stack.readValue(contextOffset + size_t.sizeof * 2, hashUnion); 254 if ( (hashUnion & hashCode) != hashCode) 255 { 256 // If the name was in this context, then it would be in the hash union. (aka a bloom filter). 257 // Report not found. 258 // Stack allocation (empty string and 0 hashcode) cannot be searched for, so it's not an issue. 259 return false; 260 } 261 262 size_t varHeader = contextOffset + CONTEXT_HEADER_SIZE; // skip context header 263 264 for (size_t n = 0; n < entries; ++n) 265 { 266 hashcode_t hashHere; 267 size_t identSize, valueSize; 268 stack.readValue(varHeader, hashHere); 269 stack.readValue(varHeader + HASHCODE_BYTES, identSize); 270 stack.readValue(varHeader + HASHCODE_BYTES + size_t.sizeof, valueSize); 271 272 if (hashHere == hashCode) // Same hashcode? Compare the identifier then. 273 { 274 const(char)[] storedIndent = cast(const(char)[]) stack.bufferSlice(varHeader + HASHCODE_BYTES + size_t.sizeof * 2, identSize); 275 if (storedIndent == name) 276 { 277 varSize = valueSize; 278 location = cast(ubyte*)(&stack.buffer[varHeader + HASHCODE_BYTES + size_t.sizeof * 2 + identSize]); 279 return true; 280 } 281 } 282 283 varHeader = varHeader + HASHCODE_BYTES + size_t.sizeof * 2 + identSize + valueSize; 284 } 285 return false; 286 } 287 288 // Find a value in this context. 289 bool getValue(size_t contextOffset, 290 const(char)[] name, 291 ubyte* res, size_t size) @trusted 292 { 293 size_t varSize; 294 ubyte* location; 295 hashcode_t hashCode; 296 if (getValueLocationAndSize(contextOffset, name, location, varSize, hashCode)) 297 { 298 if (size == varSize) 299 { 300 foreach(n; 0..size) 301 res[n] = location[n]; 302 return true; 303 } 304 else 305 { 306 assert(false); // bad size 307 } 308 } 309 else 310 return false; 311 } 312 } 313 314 /// Saves context on the thread-local context stack. The current `context()` becomes a copy of that. 315 /// Needs to be paired with `pop`. 316 /// Returns: the new top context, so that you can set a context value immediately. 317 ImplicitContext pushContext() 318 { 319 return g_contextStack.pushContext(); 320 } 321 322 /// Restore formerly pushed context from thread-local context stack. 323 void popContext() 324 { 325 g_contextStack.popContext(); 326 } 327 328 } /* </Public Context API> */ 329 330 331 332 unittest /* <Usage Example> */ 333 { 334 335 void supertramp() 336 { 337 // this `context` is the same as the parent procedure that it was called from 338 assert(context.get!int("user_index") == 123); 339 340 context.push; 341 context.set!int("user_index", 64); // could use that one, or just `context`. 342 context.pop; 343 } 344 345 context.set!int("user_index", 456); 346 347 // Allocate on TLS stack. 348 /+ 349 () @trusted 350 { 351 ubyte* storage = cast(ubyte*) context.alloca(128); 352 storage[0..128] = 2; 353 }(); 354 +/ 355 356 357 assert(context.get!int("user_index") == 456); 358 359 { 360 context.set!(void*)("allocator", null); 361 context.push; 362 context.set!int("user_index", 123); 363 364 // The `context` for this scope is implicitly passed to `supertramp`, as it's in a TLS stack, there is no ABI change or anything to do. 365 // but you don't get implicit context push/pop. 366 supertramp(); 367 368 assert(context.get!int("user_index") == 123); 369 context.pop; 370 } 371 372 // `context` value is local to the scope it is in 373 assert(context.get!int("user_index") == 456); 374 375 } /* </Usage Example> */ 376 377 378 379 380 381 private /* <Implementation of context stack> */ 382 { 383 alias hashcode_t = uint; 384 385 /// Size in bytes of an identifier hashCode. 386 enum size_t HASHCODE_BYTES = hashcode_t.sizeof; 387 388 enum size_t CONTEXT_HEADER_SIZE = HASHCODE_BYTES + 2 * size_t.sizeof; 389 390 /// A TLS stack, one for each thread. 391 ContextStack g_contextStack; 392 393 394 /// A thread local implicit stack, one for each threads. 395 /// Must-be self-initializing. It is arranged a bit like the CPU stack actually. 396 /// Linear scan across scopes to find a particular identifier. 397 struct ContextStack 398 { 399 nothrow @nogc @safe: 400 public: 401 402 ImplicitContext currentContext() return 403 { 404 if (contextCount == 0) 405 createRootContext(); 406 407 return ImplicitContext(&this, offsetOfTopContext); 408 } 409 410 ImplicitContext pushContext() return 411 { 412 if (contextCount == 0) 413 createRootContext(); 414 415 size_t parentContextLocation = offsetOfTopContext; 416 417 // Point to new context. 418 offsetOfTopContext = size; 419 420 // Create number of entries and bloom field. 421 size_t entries = 0; 422 hashcode_t hashUnion = 0; // nothing yet 423 424 // Write frame header. 425 pushValue(parentContextLocation); 426 pushValue(entries); 427 pushValue(hashUnion); 428 429 contextCount += 1; 430 431 return currentContext(); 432 } 433 434 void popContext() @trusted 435 { 436 // Retrieve parent context location. 437 438 size_t parentLoc; 439 readValue(offsetOfTopContext, parentLoc); 440 441 // Drop former context content. 442 size = offsetOfTopContext; 443 444 // Point to parent context now. 445 offsetOfTopContext = parentLoc; 446 447 contextCount -= 1; 448 } 449 450 451 private: 452 453 // A single buffer for all scopes/contexts, singly-linked frames like "the" stack. 454 void* buffer = null; 455 456 // Offset of the start of the current context, in the stack. 457 // Stack grows to positie addresses. 458 size_t offsetOfTopContext = 0; 459 460 // Number bytes in the complete stack. 461 size_t size = 0; 462 463 // Number of bytes in the allocated buffer. 464 size_t capacity = 0; 465 466 // Number of contexts. 467 size_t contextCount = 0; 468 469 enum size_t offsetOfRootContext = size_t.sizeof; 470 471 void createRootContext() 472 { 473 assert(contextCount == 0); 474 475 size_t offset = 0; // null context = no parent 476 size_t entries = 0; 477 hashcode_t hashUnion = 0; 478 pushValue(offset); // 0 location is null context, should not be accessed. 479 480 pushValue(offset); 481 pushValue(entries); 482 pushValue(hashUnion); 483 offsetOfTopContext = offsetOfRootContext; 484 contextCount = 1; 485 486 // Populate with default implementations of allocator. 487 populateContextWithDefaultUserPointer(context); 488 populateContextWithDefaultAllocator(context); 489 populateContextWithDefaultLogger(context); 490 491 //dumpStack(context.stack); 492 } 493 494 void incrementVariableCount() @trusted 495 { 496 size_t* numValues = cast(size_t*)(&buffer[offsetOfTopContext + size_t.sizeof]); 497 *numValues = *numValues + 1; 498 } 499 500 void updateContextHashCode(hashcode_t hashCode) @trusted 501 { 502 hashcode_t* hashUnion = cast(hashcode_t*)(&buffer[offsetOfTopContext + size_t.sizeof*2]); 503 *hashUnion |= hashCode; 504 } 505 506 const(ubyte[]) bufferSlice(size_t offset, size_t len) return @trusted 507 { 508 return cast(ubyte[])buffer[offset..offset+len]; 509 } 510 511 void ensureCapacity(size_t sizeBytes) @trusted 512 { 513 if (capacity < sizeBytes) 514 { 515 size_t newCapacity = calculateGrowth(sizeBytes, capacity); 516 buffer = safe_realloc(buffer, newCapacity); 517 capacity = newCapacity; 518 } 519 } 520 521 /// Append byte storage at the end (uninitialized), extend memory if needed. Return pointer to allocated area. 522 void* pushBytesUninitialized(size_t sz) @trusted 523 { 524 ensureCapacity(size + sz); 525 void* p = &buffer[size]; 526 size += sz; 527 return p; 528 } 529 530 /// Push bytes on stack, extend memory if needed. 531 void pushBytes(scope const(ubyte)* bytes, size_t sz) @trusted 532 { 533 ensureCapacity(size + sz); 534 535 ubyte* p = cast(ubyte*) &buffer[size]; 536 foreach(n; 0..sz) 537 p[n] = bytes[n]; 538 size += sz; 539 } 540 541 ///ditto 542 void pushValue(T)(T value) @trusted 543 { 544 pushBytes(cast(ubyte*) &value, value.sizeof); 545 } 546 547 // Read bytes from stack. Crash on failure. 548 void readBytes(size_t location, ubyte* bytes, size_t sz) @trusted 549 { 550 ubyte* p = cast(ubyte*) &buffer[location]; 551 foreach(n; 0..sz) 552 bytes[n] = p[n]; 553 } 554 555 //ditto 556 void readValue(T)(size_t location, out T value) @trusted 557 { 558 const(T)* p = cast(const(T)*) &buffer[location]; 559 value = *p; 560 } 561 562 // Pop bytes from stack. 563 void popBytes(ubyte* bytes, size_t sz) @trusted 564 { 565 readBytes(size - sz, bytes, sz); 566 size -= sz; 567 568 // Note: this never resizes down, like std::vector. TODO add a shrink_to_fit function? 569 } 570 } 571 572 size_t calculateGrowth(size_t newSize, size_t oldCapacity) pure 573 { 574 size_t geometric = oldCapacity + oldCapacity / 2; 575 if (geometric < newSize) 576 return newSize; // geometric growth would be insufficient 577 return geometric; 578 } 579 580 // Validate identifier and compute hash at the same time. 581 // It's like D identifiers: 582 // "Identifiers start with a letter, _, or universal alpha, and are followed by any number of 583 // letters, _, digits, or universal alphas. Universal alphas are as defined in ISO/IEC 584 // 9899:1999(E) Appendix D of the C99 Standard. Identifiers can be arbitrarily long, and are 585 // case sensitive. 586 static bool validateContextIdentifier(const(char)[] identifier, ref hashcode_t hashCode) pure nothrow @nogc @safe 587 { 588 if (identifier.length == 0) 589 { 590 hashCode = 0; 591 return true; // empty string is a valid identifier (alloca allocations on the context stack). 592 } 593 594 static bool isDigit(char ch) 595 { 596 return ch >= '0' && ch <= '9'; 597 } 598 static bool isAlpha(char ch) 599 { 600 return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_'); 601 } 602 603 char ch = identifier[0]; 604 hashcode_t hash = ch; 605 606 if (!isAlpha(ch)) // first character must be an alpha 607 return false; 608 609 for(size_t n = 1; n < identifier.length; ++n) 610 { 611 ch = identifier[n]; 612 if (!isAlpha(ch) && !isDigit(ch)) 613 return false; 614 hash = hash * 31 + ch; 615 } 616 hashCode = hash; 617 return true; 618 } 619 unittest 620 { 621 hashcode_t hash = 2; 622 assert(validateContextIdentifier("", hash)); 623 assert(hash == 0); // hash of empty string is zero 624 625 assert(validateContextIdentifier("__allocator", hash)); 626 assert(!validateContextIdentifier("é", hash)); // invalid identifier 627 } 628 629 //debug = debugContext; 630 631 632 debug(debugContext) 633 { 634 import core.stdc.stdio; 635 636 void dumpStack(ContextStack* stack) @trusted 637 { 638 printf("\n\n"); 639 size_t ofs = ContextStack.offsetOfRootContext; 640 while(true) 641 { 642 printf("*** Context at %zu\n", ofs); 643 size_t parentContextOfs; 644 size_t entries; 645 hashcode_t hashUnion; 646 stack.readValue(ofs, parentContextOfs); 647 stack.readValue(ofs + size_t.sizeof, entries); 648 stack.readValue(ofs + size_t.sizeof*2, hashUnion); 649 650 printf(" - parent = %zu\n", parentContextOfs); 651 printf(" - entries = %zu\n", entries); 652 printf(" - hash union = %x\n", hashUnion); 653 654 ofs += CONTEXT_HEADER_SIZE; 655 656 for (size_t n = 0; n < entries; ++n) 657 { 658 hashcode_t hashCode; 659 size_t identLen; 660 size_t varLen; 661 stack.readValue(ofs, hashCode); 662 stack.readValue(ofs + HASHCODE_BYTES , identLen); 663 stack.readValue(ofs + HASHCODE_BYTES + size_t.sizeof, varLen); 664 665 printf(" - context variable %zu:\n", n); 666 667 const(ubyte)[] ident = stack.bufferSlice(ofs + HASHCODE_BYTES + size_t.sizeof * 2, identLen); 668 const(ubyte)[] data = stack.bufferSlice(ofs + HASHCODE_BYTES + size_t.sizeof * 2 + identLen, varLen); 669 printf(" * hash = %x\n", hashCode); 670 printf(` * identifier = "%.*s"` ~ " (%zu bytes)\n", cast(int)(ident.length), ident.ptr, ident.length); 671 printf(` * content = `); 672 for (size_t b = 0; b < data.length; ++b) 673 { 674 printf("%02X ", data[b]); 675 } 676 printf(" (%zu bytes)\n", varLen); 677 ofs += HASHCODE_BYTES + size_t.sizeof * 2 + identLen + varLen; 678 } 679 if (ofs == stack.size) 680 break; 681 assert(ofs < stack.size); // Else, structure broken 682 printf("\n"); 683 } 684 printf("\n"); 685 } 686 } 687 688 /** 689 APPENDIX: ABI of context stack implemented above. 690 691 Let SZ = size_t.sizeof; 692 Let HZ = hashcode_t.sizeof; 693 694 695 0000 parent Offset of parent context in the stack (root context is at 696 location SZ, null context at location 0) 697 SZ numEntries Number of entries in the context "numEntries" 698 SZ*2 hashUnion Bloom filter of identifier hashes (union of hashes), this allows 699 to skip whole context while searching for a key. (HZ bytes) 700 701 foreach(entry; 0..numEntries x times): 702 0000 hashCode Hash code of following identifier (HZ bytes). 703 HZ identLen Size of identifier in bytes. 704 0 is a special value for `alloca()` allocation. Such context 705 variables have no names. 706 HZ+SZ valueLen Size of value in bytes. 707 HZ+2*SZ name Identifier string (char[]). 708 HZ+2*SZ+identlen value Variable value. 709 710 */ 711 } /* </Implementation of context stack> */ 712 713 714 715 // This file could have ended here, but here is some example of API usage, to give some ideas and 716 // also implement the basics. 717 // 718 // Basically one can add any "contextual" API, you just need to find a unique identifier. 719 // (the prefixed __identifiers are reserved for this library). 720 // 721 // The idea of having allocators and such in this library is so that a package that depends on a 722 // package that itseld depends on "implicit-context" will not be forced to use "implicit-context" 723 // itself (unless of course if there is a need). 724 // Basically in nominal usage both dependent AND dependee must depend on implicit-context library. 725 726 727 // 728 // 1. <CONTEXT USER POINTER API> 729 // 730 // The simplest API just holds a void* "user pointer", like many C APIs do. 731 // This can avoid a bit of trampoline callbacks. 732 // This context variable shouldn't THAT useful in general, since you could as well use the 733 // implicit-context system now. It is there to clean-up the call stack of things that might 734 // use user pointers extensively. 735 public 736 { 737 /// UFCS Getter for user pointer. 738 void* userPointer(ImplicitContext ctx) 739 { 740 return ctx.get!(void*)(CONTEXT_USER_POINTER_IDENTIFIER); 741 } 742 743 /// UFCS Setter for user pointer. 744 void userPointer(ImplicitContext ctx, void* userData) 745 { 746 ctx.set!(void*)(CONTEXT_USER_POINTER_IDENTIFIER, userData); 747 } 748 } 749 private 750 { 751 static immutable CONTEXT_USER_POINTER_IDENTIFIER = "__userPointer"; 752 753 void populateContextWithDefaultUserPointer(ImplicitContext ctx) 754 { 755 ctx.set!(void*)(CONTEXT_USER_POINTER_IDENTIFIER, null); // default is simply a null pointer 756 } 757 } 758 @trusted unittest 759 { 760 struct Blob 761 { 762 } 763 Blob B; 764 context.userPointer = &B; 765 assert(context.userPointer == &B); 766 } 767 // 1. </CONTEXT USER POINTER API> 768 769 770 771 // 772 // 2. <CONTEXT ALLOCATOR API> 773 // 774 // Minimal allocator API. 775 // Like in STB libraries, this allows to pass a custom `realloc` function, and that one is 776 // sufficient to customize most things. 777 public 778 { 779 /// UFCS Getter for context allocator. 780 ContextAllocator allocator(ImplicitContext ctx) 781 { 782 return ctx.get!ContextAllocator(CONTEXT_ALLOCATOR_IDENTIFIER); 783 } 784 785 /// UFCS Setter for context allocator. 786 void allocator(ImplicitContext ctx, ContextAllocator allocator) 787 { 788 ctx.set!ContextAllocator(CONTEXT_ALLOCATOR_IDENTIFIER, allocator); 789 } 790 791 // Context allocator. 792 extern(C) @system nothrow @nogc 793 { 794 /// This is not _exactly_ the same as C's realloc function! 795 /// 796 /// Params: 797 /// p This is the pointer to a memory block previously allocated with the `realloc_fun_t`. 798 /// If this is `null`, a new block is allocated and a pointer to it is returned by the 799 /// function. 800 /// 801 /// size This is the new size for the memory block, in bytes. If it is 0 and `ptr` points 802 /// to an existing block of memory, the memory block pointed by ptr is deallocated and a 803 /// `null` pointer is returned. 804 /// 805 /// Returns: Pointer to allocated space. 806 /// This can return either `null` or 807 /// 808 /// Basically this can implement regular C's `malloc`, `free` and `realloc`, but is not 809 /// completely identical to C's realloc (see `safe_realloc` to see why). 810 alias realloc_fun_t = void* function(void* p, size_t size); 811 } 812 813 struct ContextAllocator 814 { 815 nothrow @nogc @safe: 816 817 /// A single function pointer for this allocator API. 818 realloc_fun_t realloc; // not owned, this function pointer must outlive the allocator. 819 820 // MAYDO: Could have a few more operations there maybe for convenience. 821 822 /// Allocate bytes, helper function. 823 /// Returns: an allocation that MUST be freed with either `ContextAllocator.free` or 824 /// `ContextAllocator.realloc(p, 0)`, even when asking zero size. 825 void* malloc(size_t sizeInBytes) @system 826 { 827 return realloc(null, sizeInBytes); 828 } 829 830 /// Deallocate bytes, helper function. `p` can be `null`. 831 void free(void* p) @system 832 { 833 realloc(p, 0); 834 } 835 } 836 } 837 private 838 { 839 static immutable CONTEXT_ALLOCATOR_IDENTIFIER = "__allocator"; 840 841 void populateContextWithDefaultAllocator(ImplicitContext ctx) 842 { 843 ContextAllocator defaultAllocator; 844 defaultAllocator.realloc = &safe_realloc; 845 ctx.set!ContextAllocator(CONTEXT_ALLOCATOR_IDENTIFIER, defaultAllocator); 846 } 847 /// A "fixed" realloc that supports the C++23 restrictions when newSize is zero. 848 /// This is often useful, so it may end up in public API? 849 extern(C) void* safe_realloc(void* ptr, size_t newSize) @system 850 { 851 if (newSize == 0) 852 { 853 free(ptr); 854 return null; 855 } 856 return realloc(ptr, newSize); 857 } 858 } 859 @system unittest 860 { 861 void* p = context.allocator.malloc(1024); 862 context.allocator.realloc(p, 24); 863 context.allocator.free(p); 864 } 865 // 2. </CONTEXT ALLOCATOR API> 866 867 868 // 869 // 3. <CONTEXT LOGGING API> 870 // 871 // Minimal logging API. 872 public 873 { 874 nothrow @nogc @system 875 { 876 /// This functions prints a format-less, ZERO-TERMINATED string. Hence, the @system interface. 877 /// Also, cannot fail. \n must flush. 878 alias print_fun_t = void function(const(char)* message); 879 } 880 881 struct ContextLogger 882 { 883 nothrow @nogc @system: 884 print_fun_t print; // not owned, this function pointer must outlive the logger. 885 886 extern (C) void printf(const(char)* fmt, ...) 887 { 888 enum MAX_MESSAGE = 256; // cropped above that 889 890 char[MAX_MESSAGE] buffer; 891 va_list args; 892 va_start (args, fmt); 893 vsnprintf (buffer.ptr, MAX_MESSAGE, fmt, args); 894 va_end (args); 895 896 print(buffer.ptr); 897 } 898 } 899 900 /// UFCS Getter for context allocator. 901 ContextLogger logger(ImplicitContext ctx) 902 { 903 return ctx.get!ContextLogger(CONTEXT_LOGGER_IDENTIFIER); 904 } 905 906 /// UFCS Setter for context allocator. 907 void logger(ImplicitContext ctx, ContextLogger allocator) 908 { 909 ctx.set!ContextLogger(CONTEXT_LOGGER_IDENTIFIER, allocator); 910 } 911 } 912 private 913 { 914 static immutable CONTEXT_LOGGER_IDENTIFIER = "__logger"; 915 916 void populateContextWithDefaultLogger(ImplicitContext ctx) @trusted 917 { 918 ContextLogger defaultLogger; 919 defaultLogger.print = &stdc_print; 920 ctx.set!ContextLogger(CONTEXT_LOGGER_IDENTIFIER, defaultLogger); 921 } 922 923 void stdc_print(const(char)* message) @system 924 { 925 printf("%s", message); 926 } 927 } 928 @system unittest 929 { 930 // Note: Format string follow the `printf` specifiers, not the `writeln` specifiers. 931 // Yup, don't forget to \n. 932 context.logger.printf("Context Logger API: Hello %s\n".ptr, "world.".ptr); 933 } 934 // 3. <:CONTEXT LOGGING API>