1 ///
2 module libwasm.rt.allocator;
3 
4 nothrow:
5 import std.typecons : Flag, Yes, No;
6 import std.traits : isFloatingPoint, isIntegral, isSigned, isNumeric;
7 
8 import memutils.utils;
9 import memutils.scoped;
10 import memutils.pool;
11 import memutils.freelist;
12 import memutils.hashmap;
13 import libwasm.rt.allocator;
14 import libwasm.types;
15 
16 pragma(LDC_intrinsic, "llvm.maxnum.f#")
17 T fmax(T)(in T vala, in T valb) if (isFloatingPoint!T);
18 /++
19 Iterates the passed arguments and returns the minimum value.
20 Params: args = The values to select the minimum from. At least two arguments
21     must be passed, and they must be comparable with `<`.
22 Returns: The minimum of the passed-in values.
23 +/
24 auto max(T...)(T args) if (T.length >= 2)
25 {
26     //Get "a"
27     static if (T.length <= 2)
28         alias a = args[0];
29     else
30         auto a = max(args[0 .. ($ + 1) / 2]);
31     alias T0 = typeof(a);
32 
33     //Get "b"
34     static if (T.length <= 3)
35         alias b = args[$ - 1];
36     else
37         auto b = max(args[($ + 1) / 2 .. $]);
38     alias T1 = typeof(b);
39 
40     static assert(is(typeof(a < b)), "Invalid arguments: Cannot compare types " ~ T0.stringof ~ " and " ~ T1.stringof ~ ".");
41 
42     static if ((isFloatingPoint!T0 && isNumeric!T1) || (isFloatingPoint!T1 && isNumeric!T0))
43     {
44         return fmax(a, b);
45     }
46     else
47     {
48         static if (isIntegral!T0 && isIntegral!T1)
49             static assert(isSigned!T0 == isSigned!T1,
50                 "mir.utility.max is not defined for signed + unsigned pairs because of security reasons."
51                     ~ "Please unify type or use a Phobos analog.");
52         //Do the "max" proper with a and b
53         return a > b ? a : b;
54     }
55 }
56 /**
57 The alignment that is guaranteed to accommodate any D object allocation on the
58 current platform.
59 */
60 enum uint platformAlignment = max(double.alignof, real.alignof);
61 
62 /**
63 Check whether a number is an integer power of two.
64 
65 Note that only positive numbers can be integer powers of two. This
66 function always return `false` if `x` is negative or zero.
67 
68 Params:
69     x = the number to test
70 
71 Returns:
72     `true` if `x` is an integer power of two.
73 */
74 bool isPowerOf2(X)(const X x) pure @safe nothrow @nogc if (isNumeric!X)
75 {
76     static if (isFloatingPoint!X)
77     {
78         import std.math : frexp;
79 
80         int exp;
81         const X sig = frexp(x, exp);
82 
83         return (exp != int.min) && (sig is cast(X) 0.5L);
84     }
85     else
86     {
87         static if (isSigned!X)
88         {
89             auto y = cast(typeof(x + 0)) x;
90             return y > 0 && !(y & (y - 1));
91         }
92         else
93         {
94             auto y = cast(typeof(x + 0u)) x;
95             return (y & -y) > (y - 1);
96         }
97     }
98 }
99 /**
100 Returns `n` rounded up to a multiple of alignment, which must be a power of 2.
101 */
102 @safe @nogc nothrow pure
103 size_t roundUpToAlignment()(size_t n, uint alignment)
104 {
105     assert(alignment.isPowerOf2);
106     immutable uint slack = cast(uint) n & (alignment - 1);
107     const result = slack
108         ? n + alignment - slack : n;
109     assert(result >= n);
110     return result;
111 }
112 
113 version (WebAssembly)
114 {
115 
116     private __gshared void* begin, current, end;
117     struct WasmAllocator
118     {
119         import libwasm.intrinsics;
120 
121     nothrow:
122     static:
123 
124         enum wasmPageSize = 64 * 1024;
125         enum uint alignment = platformAlignment;
126         @trusted void init(uint heap_base)
127         {
128             begin = cast(void*)(heap_base.roundUpToAlignment(alignment));
129             current = begin;
130             end = cast(void*)(wasmMemorySize * wasmPageSize);
131         }
132 
133         void[] allocate(size_t n)
134         {
135             const rounded = n.roundUpToAlignment(alignment);
136             if (current + rounded > end)
137                 grow(1 + rounded / wasmPageSize);
138             void* mem = current;
139             current += rounded;
140             return mem[0 .. rounded];
141         }
142 
143         bool deallocate(void[] data)
144         {
145             // we rely on memutils to deallocate stuff
146             return true;
147         }
148 
149         private void grow(size_t pages)
150         {
151             auto currentPages = wasmMemoryGrow(0);
152             current = cast(void*)(currentPages * wasmPageSize);
153             wasmMemoryGrow(pages);
154             end = cast(void*)((currentPages + pages) * wasmPageSize);
155         }
156     }
157 
158 }
159 /**
160    Returns `true` if `ptr` is aligned at `alignment`.
161 */
162 @nogc nothrow pure bool alignedAt(T)(T* ptr, uint alignment)
163 {
164     return cast(size_t) ptr % alignment == 0;
165 }
166 
167 // used in libwasm Lodash
168 @trusted extern (C)
169 {
170     void[] FL_allocate(size_t n)
171     {
172         return ThreadMemAllocator.allocate(n);
173     }
174 
175     void[] FL_reallocate(void[] mem, size_t n)
176     {
177         return ThreadMemAllocator.reallocate(mem, n);
178     }
179 
180     void FL_deallocate(void[] mem)
181     {
182         ThreadMemAllocator.deallocate(mem);
183     }
184 }
185 
186 @trusted extern (C) export @assumeUsed ubyte* allocString(uint bytes)
187 {
188     import memutils.scoped;
189 
190     void[] ret;
191     if (!PoolStack.empty) ret = PoolStack.top.alloc(bytes + 1);
192     else ret = FL_allocate(bytes+1);
193     *cast(ubyte*)(ret.ptr + ret.length - 1) = '\0'; // always return zero-ended. wrong size can be sent because this is not freed
194     return cast(ubyte*) ret.ptr;
195 }
196 
197 // implement closures with execution context or PoolStack.top
198 // references will be held by a ManagedPool
199 // maybe in some outer scope
200 extern (C) export void* _d_allocmemory(size_t sz)
201 {
202     import memutils.scoped;
203     if (!PoolStack.empty)
204         return PoolStack.top.alloc(sz).ptr;    
205     else
206         return FL_allocate(sz).ptr;
207     
208     
209 }
210 
211 struct ThreadMemAllocator
212 {
213 static:
214 nothrow:
215 @trusted:
216     void[] allocate(size_t n)
217     {
218         return ThreadMem.alloc!(void[])(n);
219     }
220 
221     void[] reallocate(void[] data, size_t n, bool free_mem = true)
222     {
223         if (data.length == 0)
224             return allocate(n);
225         return ThreadMem.realloc(data, n, free_mem);
226     }
227 
228     bool deallocate(void[] data, bool free_mem = true)
229     {
230         ThreadMem.free(data, free_mem);
231         return true;
232     }
233 }
234 
235 struct PoolStackAllocator
236 {
237 static:
238 nothrow:
239 @trusted:
240     void[] allocate(size_t n)
241     {
242         return PoolStack.top.alloc(n);
243     }
244 
245     void[] reallocate(void[] data, size_t n, bool free_mem = true)
246     {
247         if (data.length == 0)
248             return allocate(n);
249         return PoolStack.top.realloc(data, n, free_mem);
250     }
251 
252     bool deallocate(void[] data, bool free_mem = true)
253     {
254         PoolStack.top.free(data, free_mem);
255         return true;
256     }
257 }