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>