1 /**
2     Numem Core Hooks.
3 
4     This file contains all the core hooks numem calls internally to handle memory.
5     Given that some platforms may not have a C standard library, these hooks allow you
6     to override how numem handles memory for such platforms from an external library.
7 
8     In this case, most of the hooks presented here will need to be implemented to cover
9     all of the used internal hooks within numem.
10 
11     Hooks which are prefixed $(D nuopt_) are optional and may be omitted, they are
12     usually for language-specific interoperability.
13     
14     Copyright:
15         Copyright © 2023-2025, Kitsunebi Games
16         Copyright © 2023-2025, Inochi2D Project
17     
18     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
19     Authors:   Luna Nielsen
20 */
21 module numem.core.hooks;
22 import core.attribute : weak;
23 
24 /**
25     Allocates $(D bytes) worth of memory.
26 
27     Params:
28         bytes = How many bytes to allocate.
29     
30     Returns:
31         Newly allocated memory or $(D null) on failure.
32         To avoid a memory leak, free the memory with $(D nu_free).
33     
34     Notes:
35         Given the implementation of $(D nu_malloc) and $(D nu_free) may be
36         independent of the libc allocator, memory allocated with
37         $(D nu_malloc) should $(B always) be freed with $(D nu_free)!
38 */
39 export
40 extern(C)
41 void* nu_malloc(size_t bytes) @nogc nothrow @system pure @weak {
42     pragma(mangle, "malloc")
43     extern extern(C) void* malloc(size_t) @nogc nothrow @system pure;
44 
45     return malloc(bytes);
46 }
47 
48 /**
49     Reallocates memory prior allocated with $(D nu_malloc) or
50     $(D nu_alignedalloc).
51 
52     This function may re-allocate the memory if resizing the allocation
53     to the new size is not possible.
54 
55     Params:
56         data    = Pointer to prior allocated memory.
57         newSize = New size of the allocation, in bytes.
58 
59     Returns:
60         The address of the reallocated memory or $(D null) on failure.
61         To avoid a memory leak, free the memory with $(D nu_free).
62     
63     Notes:
64         Given the implementation of $(D nu_realloc) and $(D nu_free) may be
65         independent of the libc allocator, memory allocated with
66         $(D nu_realloc) should $(B always) be freed with $(D nu_free)!
67 */
68 export
69 extern(C)
70 void* nu_realloc(void* data, size_t newSize) @nogc nothrow @system pure @weak {
71     pragma(mangle, "realloc")
72     extern extern(C) void* realloc(void*, size_t) @nogc nothrow @system pure;
73 
74     return realloc(data, newSize);
75 }
76 
77 /**
78     Frees allocated memory.
79 
80     Params:
81         data = Pointer to start of memory prior allocated.
82 
83     Notes:
84         Given the implementation of the allocators and $(D nu_free) may be
85         independent of the libc allocator, memory allocated with
86         numem functions should $(B always) be freed with $(D nu_free)!
87 */
88 export
89 extern(C)
90 void nu_free(void* data) @nogc nothrow @system pure @weak {
91     pragma(mangle, "free")
92     extern extern(C) void free(void*) @nogc nothrow @system pure;
93 
94     return free(data);
95 }
96 
97 /**
98     Copies $(D bytes) worth of data from $(D src) into $(D dst).
99     
100     $(D src) and $(D dst) needs to be allocated and within range,
101     additionally the source and destination may not overlap.
102 
103     Params:
104         dst =   Destination of the memory copy operation.
105         src =   Source of the memory copy operation
106         bytes = The amount of bytes to copy.
107 */
108 export
109 extern(C)
110 void* nu_memcpy(return scope void* dst, scope const void* src, size_t bytes) @nogc nothrow @system pure @weak {
111     pragma(mangle, "memcpy")
112     extern extern(C) void* memcpy(return scope void*, scope const void*, size_t) @nogc nothrow @system pure;
113 
114     return memcpy(dst, src, bytes);
115 }
116 
117 /**
118     Moves $(D bytes) worth of data from $(D src) into $(D dst).
119     
120     $(D src) and $(D dst) needs to be allocated and within range.
121 
122     Params:
123         dst =   Destination of the memory copy operation.
124         src =   Source of the memory copy operation
125         bytes = The amount of bytes to copy.
126     
127     Returns:
128         Pointer to $(D dst)
129 */
130 export
131 extern(C)
132 void* nu_memmove(return scope void* dst, scope const void* src, size_t bytes) @nogc nothrow @system pure @weak {
133     pragma(mangle, "memmove")
134     extern extern(C) void* memmove(return scope void*, scope const void*, size_t) @nogc nothrow @system pure;
135 
136     return memmove(dst, src, bytes);
137 }
138 
139 /**
140     Fills $(D dst) with $(D bytes) amount of $(D value)s.
141 
142     Params:
143         dst =   Destination of the memory copy operation.
144         value = The byte to repeatedly copy to the memory starting at $(D dst)
145         bytes = The amount of bytes to write.
146     
147     Returns:
148         Pointer to $(D dst)
149 */
150 export
151 extern(C)
152 void* nu_memset(return scope void* dst, ubyte value, size_t bytes) @nogc nothrow @system pure @weak {
153     pragma(mangle, "memset")
154     extern extern(C) void* memset(return scope void*, int, size_t) @nogc nothrow @system pure;
155 
156     return memset(dst, value, bytes);
157 }
158 
159 /**
160     Called internally by numem if a fatal error occured.
161 
162     This function $(B should) exit the application and if possible,
163     print an error. But it $(B may) not print an error.
164 
165     Notes:
166         This function may trigger a trap to alert a debugger to
167         create a breakpoint.
168 
169     Params:
170         errMsg = A D string containing the error in question.
171     
172     Returns:
173         Never returns, the application should crash at this point.
174 */
175 export
176 extern(C)
177 void nu_fatal(const(char)[] errMsg) @nogc nothrow @system pure @weak {
178     pragma(mangle, "printf")
179     extern extern(C) void printf(const(char)*, ...) @nogc nothrow @system pure;
180     pragma(mangle, "abort")
181     extern extern(C) void abort() @nogc nothrow @system pure;
182 
183     // Print to stderr
184     printf("fatal error: %.*s\n", cast(int)errMsg.length, errMsg.ptr);
185 
186     // Debugger trap
187     debug {
188         version(LDC) {
189             import ldc.intrinsics : llvm_debugtrap;    
190 
191             llvm_debugtrap();
192         } else version(DigitalMars) {
193             version(X86) {
194                 asm @nogc pure nothrow {
195                     int 0x03;
196                 }
197             } else version(X86_64) {
198                 asm @nogc pure nothrow {
199                     int 0x03;
200                 }
201             } else version(AArch64) {
202                 asm @nogc pure nothrow {
203                     inst 0xd4200000;
204                 }
205             }
206         }
207     }
208 
209     // Final abort.
210     abort();
211 }
212 
213 /**
214     Gets the status of atomics support in the current
215     numem configuration.
216 
217     Notes:
218         If atomics are not supported, these functions will
219         act as non-atomic alternatives.
220 
221     Returns:
222         Whether atomics are supported by the loaded
223         hookset.
224 */
225 export
226 extern(C)
227 bool nu_atomic_supported() @nogc nothrow @system pure @weak {
228     version(LDC) {
229         return true;
230     } else return false;
231 }
232 
233 /**
234     Inserts a memory acquire barrier.
235 */
236 export
237 extern(C)
238 void nu_atomic_barrier_acquire() @nogc nothrow @system pure @weak {
239     version(LDC) {
240         import ldc.intrinsics;    
241         llvm_memory_fence(AtomicOrdering.Acquire, SynchronizationScope.CrossThread);
242     }
243 }
244 
245 /**
246     Inserts a memory release barrier.
247 */
248 export
249 extern(C)
250 void nu_atomic_barrier_release() @nogc nothrow @system pure @weak {
251     version(LDC) {
252         import ldc.intrinsics;    
253         llvm_memory_fence(AtomicOrdering.Release, SynchronizationScope.CrossThread);
254     }
255 }
256 
257 /**
258     Loads a 32 bit value atomically.
259 */
260 export
261 extern(C)
262 inout(uint) nu_atomic_load_32(ref inout(uint) src) @nogc nothrow @system pure @weak {
263     version(LDC) {
264         import ldc.intrinsics;    
265         return llvm_atomic_load!(inout(uint))(cast(shared(inout(uint)*))&src);
266     } else return src;
267 }
268 
269 /**
270     Stores a 32 bit value atomically.
271 */
272 export
273 extern(C)
274 void nu_atomic_store_32(ref uint dst, uint value) @nogc nothrow @system pure @weak {
275     version(LDC) {
276         import ldc.intrinsics;    
277         llvm_atomic_store!(uint)(value, cast(shared(uint*))&dst);
278     } else {
279         dst = value;
280     }
281 }
282 
283 /**
284     Adds a 32 bit value atomically.
285 */
286 export
287 extern(C)
288 extern uint nu_atomic_add_32(ref uint dst, uint value) @nogc nothrow @system pure @weak {
289     version(LDC) {
290         
291         import ldc.intrinsics;    
292         return llvm_atomic_rmw_add!(uint)(cast(shared(uint*))&dst, value);
293     } else {
294         uint oldval = dst;
295         dst += value;
296         return oldval;
297     }
298 }
299 
300 /**
301     Subtracts a 32 bit value atomically.
302 */
303 export
304 extern(C)
305 extern uint nu_atomic_sub_32(ref uint dst, uint value) @nogc nothrow @system pure @weak {
306     version(LDC) {
307         
308         import ldc.intrinsics;    
309         return llvm_atomic_rmw_sub!(uint)(cast(shared(uint*))&dst, value);
310     } else {
311         uint oldval = dst;
312         dst -= value;
313         return oldval;
314     }
315 }
316 
317 /**
318     Loads a pointer value atomically.
319 */
320 export
321 extern(C)
322 extern inout(void)* nu_atomic_load_ptr(inout(void)** src) @nogc nothrow @system pure @weak {
323     version(LDC) {
324         
325         import ldc.intrinsics;    
326         return llvm_atomic_load!(inout(void)*)(cast(shared(inout(void)**))src);
327     } else {
328         return *src;
329     }
330 }
331 
332 /**
333     Stores a pointer value atomically.
334 */
335 export
336 extern(C)
337 extern void nu_atomic_store_ptr(void** dst, void* value) @nogc nothrow @system pure @weak {
338     version(LDC) {
339         
340         import ldc.intrinsics;    
341         llvm_atomic_store!(void*)(value, cast(shared(void**))dst);
342     } else {
343         *dst = value;
344     }
345 }
346 
347 /**
348     Compares variable at $(D dst) and swaps it if it contains $(D oldvalue).
349 */
350 export
351 extern(C)
352 extern bool nu_atomic_cmpxhg_ptr(void** dst, void* oldvalue, void* value) @nogc nothrow @system pure @weak {
353     version(LDC) {
354         
355         import ldc.intrinsics;
356         return llvm_atomic_cmp_xchg!(void*)(cast(shared(void**))dst, oldvalue, value).exchanged;
357     } else {
358         if (*dst is oldvalue) {
359             *dst = value;
360             return true;
361         }
362 
363         return false;
364     }
365 }
366 
367 /**
368     Hooks for handling auto release pools.
369 
370     These $(I optional) pool callback functions allows implementers of
371     numem to hook into the auto release pool system.
372 
373     Push should push a new context onto an internal stack, while pop should
374     release an element from the stack.
375 
376     These functions are mainly useful for Objective-C interoperability.
377 
378     See_Also:
379         $(LINK2 https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool, ARC Documentation)
380 */
381 extern(C)
382 __gshared void* function() @nogc nothrow @system nuopt_autoreleasepool_push = null;
383 
384 /// ditto
385 extern(C)
386 __gshared void function(void*) @nogc nothrow @system nuopt_autoreleasepool_pop = null;