/src/wasm3/source/m3_env.c
Line | Count | Source |
1 | | // |
2 | | // m3_env.c |
3 | | // |
4 | | // Created by Steven Massey on 4/19/19. |
5 | | // Copyright © 2019 Steven Massey. All rights reserved. |
6 | | // |
7 | | |
8 | | #include <stdarg.h> |
9 | | #include <limits.h> |
10 | | #include <errno.h> |
11 | | #include <float.h> |
12 | | #include <ctype.h> |
13 | | |
14 | | #include "m3_env.h" |
15 | | #include "m3_compile.h" |
16 | | #include "m3_exception.h" |
17 | | #include "m3_info.h" |
18 | | |
19 | | |
20 | | IM3Environment m3_NewEnvironment () |
21 | 1.89k | { |
22 | 1.89k | IM3Environment env = m3_AllocStruct(M3Environment); |
23 | | |
24 | 1.89k | if (env) { |
25 | 1.89k | _try |
26 | 1.89k | { |
27 | | // create FuncTypes for all simple block return ValueTypes. |
28 | | // v128 is skipped: it parses as a slot but has no operations. |
29 | 18.9k | for (u8 t = c_m3Type_none; t < c_m3Type_count; t++) { |
30 | 17.0k | if (t == c_m3Type_v128) { |
31 | 1.89k | continue; |
32 | 1.89k | } |
33 | | |
34 | 15.1k | IM3FuncType ftype; |
35 | 15.1k | _ (AllocFuncType(&ftype, 1)); |
36 | | |
37 | 15.1k | ftype->numArgs = 0; |
38 | 15.1k | ftype->numRets = (t == c_m3Type_none) ? 0 : 1; |
39 | 15.1k | ftype->types[0] = t; |
40 | | |
41 | 15.1k | Environment_AddFuncType(env, &ftype); |
42 | | |
43 | 15.1k | env->retFuncTypes[t] = ftype; |
44 | 15.1k | } |
45 | 1.89k | } |
46 | | |
47 | 1.89k | _catch: |
48 | 1.89k | if (result) { |
49 | 0 | m3_FreeEnvironment(env); |
50 | 0 | env = NULL; |
51 | 0 | } |
52 | 1.89k | } |
53 | | |
54 | 1.89k | return env; |
55 | 1.89k | } |
56 | | |
57 | | |
58 | | void Environment_Release (IM3Environment i_environment) |
59 | 1.89k | { |
60 | 1.89k | IM3FuncType ftype = i_environment->funcTypes; |
61 | | |
62 | 17.7k | while (ftype) { |
63 | 15.8k | IM3FuncType next = ftype->next; |
64 | 15.8k | m3_Free(ftype); |
65 | 15.8k | ftype = next; |
66 | 15.8k | } |
67 | | |
68 | 1.89k | m3log(runtime, "freeing %d pages from environment", CountCodePages(i_environment->pagesReleased)); |
69 | 1.89k | FreeCodePages(&i_environment->pagesReleased); |
70 | 1.89k | } |
71 | | |
72 | | |
73 | | void m3_FreeEnvironment (IM3Environment i_environment) |
74 | 1.89k | { |
75 | 1.89k | if (i_environment) { |
76 | 1.89k | Environment_Release(i_environment); |
77 | 1.89k | m3_Free(i_environment); |
78 | 1.89k | } |
79 | 1.89k | } |
80 | | |
81 | | |
82 | | void m3_SetCustomSectionHandler (IM3Environment i_environment, M3SectionHandler i_handler) |
83 | 0 | { |
84 | 0 | if (i_environment) { |
85 | 0 | i_environment->customSectionHandler = i_handler; |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | |
90 | | // returns the same io_funcType or replaces it with an equivalent that's already in the type linked list |
91 | | M3Result Environment_AddFuncType (IM3Environment i_environment, IM3FuncType* io_funcType) |
92 | 17.2k | { |
93 | 17.2k | IM3FuncType addType = *io_funcType; |
94 | 17.2k | IM3FuncType newType = i_environment->funcTypes; |
95 | | |
96 | 86.0k | while (newType) { |
97 | 70.1k | if (AreFuncTypesEqual(newType, addType)) { |
98 | 1.44k | m3_Free(addType); |
99 | 1.44k | break; |
100 | 1.44k | } |
101 | | |
102 | 68.7k | newType = newType->next; |
103 | 68.7k | } |
104 | | |
105 | 17.2k | if (newType == NULL) { |
106 | | // a type index has to fit in the heap type field of an m3type_t |
107 | 15.8k | if (i_environment->numFuncTypes >= d_m3MaxSaneTypesCount) { |
108 | 0 | m3_Free(addType); |
109 | 0 | *io_funcType = NULL; |
110 | 0 | return "too many distinct function types"; |
111 | 0 | } |
112 | | |
113 | 15.8k | newType = addType; |
114 | 15.8k | newType->canonicalIndex = i_environment->numFuncTypes++; |
115 | 15.8k | newType->next = i_environment->funcTypes; |
116 | 15.8k | i_environment->funcTypes = newType; |
117 | 15.8k | } |
118 | | |
119 | 17.2k | *io_funcType = newType; |
120 | | |
121 | 17.2k | return m3Err_none; |
122 | 17.2k | } |
123 | | |
124 | | |
125 | | IM3CodePage RemoveCodePageOfCapacity (M3CodePage** io_list, u32 i_minimumLineCount) |
126 | 2.39k | { |
127 | 2.39k | IM3CodePage prev = NULL; |
128 | 2.39k | IM3CodePage page = *io_list; |
129 | | |
130 | 2.39k | while (page) { |
131 | 125 | if (NumFreeLines(page) >= i_minimumLineCount) { d_m3Assert (page->info.usageCount == 0); |
132 | 125 | IM3CodePage next = page->info.next; |
133 | 125 | if (prev) { |
134 | 0 | prev->info.next = next; // mid-list |
135 | 125 | } else { |
136 | 125 | *io_list = next; // front of list |
137 | 125 | } |
138 | | |
139 | 125 | break; |
140 | 125 | } |
141 | | |
142 | 0 | prev = page; |
143 | 0 | page = page->info.next; |
144 | 0 | } |
145 | | |
146 | 2.39k | return page; |
147 | 2.39k | } |
148 | | |
149 | | |
150 | | IM3CodePage Environment_AcquireCodePage (IM3Environment i_environment, u32 i_minimumLineCount) |
151 | 1.16k | { |
152 | 1.16k | return RemoveCodePageOfCapacity(&i_environment->pagesReleased, i_minimumLineCount); |
153 | 1.16k | } |
154 | | |
155 | | |
156 | | void Environment_ReleaseCodePages (IM3Environment i_environment, IM3CodePage i_codePageList) |
157 | 4.04k | { |
158 | 4.04k | IM3CodePage end = i_codePageList; |
159 | | |
160 | 4.15k | while (end) { |
161 | 1.16k | end->info.lineIndex = 0; // reset page |
162 | | #if d_m3RecordBacktraces |
163 | | end->info.mapping->size = 0; |
164 | | #endif // d_m3RecordBacktraces |
165 | | |
166 | 1.16k | IM3CodePage next = end->info.next; |
167 | 1.16k | if (not next) { |
168 | 1.06k | break; |
169 | 1.06k | } |
170 | | |
171 | 106 | end = next; |
172 | 106 | } |
173 | | |
174 | 4.04k | if (end) { |
175 | | // push list to front |
176 | 1.06k | end->info.next = i_environment->pagesReleased; |
177 | 1.06k | i_environment->pagesReleased = i_codePageList; |
178 | 1.06k | } |
179 | 4.04k | } |
180 | | |
181 | | |
182 | | IM3Runtime m3_NewRuntime (IM3Environment i_environment, u32 i_stackSizeInBytes, void* i_userdata) |
183 | 1.89k | { |
184 | 1.89k | IM3Runtime runtime = m3_AllocStruct(M3Runtime); |
185 | | |
186 | 1.89k | if (runtime) { |
187 | 1.89k | m3_ResetErrorInfo(runtime); |
188 | | |
189 | 1.89k | runtime->environment = i_environment; |
190 | 1.89k | runtime->userdata = i_userdata; |
191 | | |
192 | 1.89k | runtime->originStack = m3_Malloc("Wasm Stack", i_stackSizeInBytes + 4 * sizeof(m3slot_t)); // TODO: more precise stack checks |
193 | | |
194 | 1.89k | if (runtime->originStack) { |
195 | 1.89k | runtime->stack = runtime->originStack; |
196 | 1.89k | runtime->numStackSlots = i_stackSizeInBytes / sizeof(m3slot_t); m3log (runtime, "new stack: %p, slots: %u", runtime->originStack, runtime->numStackSlots); |
197 | 1.89k | } else { |
198 | 0 | m3_Free(runtime); |
199 | 0 | } |
200 | 1.89k | } |
201 | | |
202 | 1.89k | return runtime; |
203 | 1.89k | } |
204 | | |
205 | | void m3_SetValidation (IM3Runtime i_runtime, bool i_enable) |
206 | 0 | { |
207 | 0 | #if d_m3EnableValidation |
208 | 0 | if (i_runtime) { |
209 | 0 | i_runtime->skipValidation = not i_enable; |
210 | 0 | } |
211 | | #else |
212 | | (void)i_runtime; |
213 | | (void)i_enable; // nothing to skip: the validator was compiled out |
214 | | #endif |
215 | 0 | } |
216 | | |
217 | | void m3_SetGasLimit (IM3Runtime i_runtime, double i_gas) |
218 | 1.89k | { |
219 | 1.89k | #if d_m3HasGasMetering |
220 | 1.89k | if (i_runtime) { |
221 | | // A budget bigger than the counter can hold is the same as no ceiling |
222 | | // worth speaking of, so it saturates rather than wrapping |
223 | 1.89k | const double maxGas = (double)INT64_MAX / d_m3GasUnitsPerGas; |
224 | | |
225 | 1.89k | i64 units; |
226 | 1.89k | if (i_gas >= maxGas) { |
227 | 0 | units = INT64_MAX; |
228 | 1.89k | } else if (i_gas > 0) { |
229 | 1.89k | units = (i64)(i_gas * d_m3GasUnitsPerGas); |
230 | 1.89k | } else { |
231 | 0 | units = 0; |
232 | 0 | } |
233 | | |
234 | 1.89k | i_runtime->gasLimit = i_runtime->gasRemaining = units; |
235 | 1.89k | } |
236 | | #else |
237 | | (void)i_runtime; |
238 | | (void)i_gas; // nothing to meter: the instrumentation was compiled out |
239 | | #endif |
240 | 1.89k | } |
241 | | |
242 | | double m3_GetGasLimit (IM3Runtime i_runtime) |
243 | 0 | { |
244 | 0 | #if d_m3HasGasMetering |
245 | 0 | if (i_runtime) { |
246 | 0 | return (double)i_runtime->gasLimit / d_m3GasUnitsPerGas; |
247 | 0 | } |
248 | | #else |
249 | | (void)i_runtime; |
250 | | #endif |
251 | 0 | return 0; |
252 | 0 | } |
253 | | |
254 | | double m3_GetGasUsed (IM3Runtime i_runtime) |
255 | 0 | { |
256 | 0 | #if d_m3HasGasMetering |
257 | 0 | if (i_runtime) { |
258 | 0 | return (double)(i_runtime->gasLimit - i_runtime->gasRemaining) / d_m3GasUnitsPerGas; |
259 | 0 | } |
260 | | #else |
261 | | (void)i_runtime; |
262 | | #endif |
263 | 0 | return 0; |
264 | 0 | } |
265 | | |
266 | | void* m3_GetUserData (IM3Runtime i_runtime) |
267 | 0 | { |
268 | 0 | return i_runtime ? i_runtime->userdata : NULL; |
269 | 0 | } |
270 | | |
271 | | |
272 | | void* ForEachModule (IM3Runtime i_runtime, ModuleVisitor i_visitor, void* i_info) |
273 | 5.61k | { |
274 | 5.61k | void* r = NULL; |
275 | | |
276 | 5.61k | IM3Module module = i_runtime->modules; |
277 | | |
278 | 8.86k | while (module) { |
279 | 4.77k | IM3Module next = module->next; |
280 | 4.77k | if ((r = i_visitor(module, i_info))) { |
281 | 1.51k | break; |
282 | 1.51k | } |
283 | | |
284 | 3.25k | module = next; |
285 | 3.25k | } |
286 | | |
287 | 5.61k | return r; |
288 | 5.61k | } |
289 | | |
290 | | |
291 | | void* _FreeModule (IM3Module i_module, void* i_info) |
292 | 1.60k | { |
293 | 1.60k | m3_FreeModule(i_module); |
294 | 1.60k | return NULL; |
295 | 1.60k | } |
296 | | |
297 | | |
298 | | // A module's memory and table index spaces hold borrowed pointers: linking |
299 | | // repoints an imported slot at the memory or table the exporting module owns. |
300 | | // m3_FreeModule tells a borrowed slot from an owned one by reading that owner, |
301 | | // which only holds while the object is still allocated - and the free walk |
302 | | // below visits modules in load order, with no ordering between an owner and |
303 | | // the modules borrowing from it. So clear the borrowed slots first, in a pass |
304 | | // that frees nothing; the walk then skips them on the NULL check it already |
305 | | // does, instead of reading a memory some earlier module has freed. |
306 | | void* _ReleaseBorrowedSlots (IM3Module i_module, void* i_info) |
307 | 1.60k | { |
308 | 2.37k | for (u32 i = 0; i < i_module->numMemories; ++i) { |
309 | 774 | IM3Memory memory = i_module->memories[i]; |
310 | | |
311 | 774 | if (memory and memory->owner != i_module) { |
312 | 0 | i_module->memories[i] = NULL; |
313 | 0 | } |
314 | 774 | } |
315 | | |
316 | 1.97k | for (u32 i = 0; i < i_module->numTables; ++i) { |
317 | 372 | IM3Table table = i_module->tables[i]; |
318 | | |
319 | 372 | if (table and table->owner != i_module) { |
320 | 0 | i_module->tables[i] = NULL; |
321 | 0 | } |
322 | 372 | } |
323 | | |
324 | 1.60k | return NULL; |
325 | 1.60k | } |
326 | | |
327 | | |
328 | | void Runtime_Release (IM3Runtime i_runtime) |
329 | 2.02k | { |
330 | 2.02k | ForEachModule(i_runtime, _ReleaseBorrowedSlots, NULL); |
331 | 2.02k | ForEachModule(i_runtime, _FreeModule, NULL); d_m3Assert (i_runtime->numActiveCodePages == 0); |
332 | | |
333 | 2.02k | Environment_ReleaseCodePages(i_runtime->environment, i_runtime->pagesOpen); |
334 | 2.02k | Environment_ReleaseCodePages(i_runtime->environment, i_runtime->pagesFull); |
335 | | |
336 | 2.02k | m3_Free(i_runtime->originStack); |
337 | | |
338 | 2.02k | #if d_m3EnableValidation |
339 | 2.02k | m3_Free(i_runtime->validator); |
340 | 2.02k | #endif |
341 | 2.02k | } |
342 | | |
343 | | |
344 | | void m3_FreeRuntime (IM3Runtime i_runtime) |
345 | 1.89k | { |
346 | 1.89k | if (i_runtime) { |
347 | 1.89k | m3_PrintProfilerInfo(); |
348 | | |
349 | 1.89k | Runtime_Release(i_runtime); |
350 | 1.89k | m3_Free(i_runtime); |
351 | 1.89k | } |
352 | 1.89k | } |
353 | | |
354 | | M3Result EvaluateExpression (IM3Module i_module, void* o_expressed, m3type_t i_type, bytes_t* io_bytes, cbytes_t i_end) |
355 | 125 | { |
356 | 125 | M3Result result = m3Err_none; |
357 | | |
358 | | // OPTZ: use a simplified interpreter for expressions |
359 | | |
360 | | // create a temporary runtime context |
361 | | #if defined(d_m3PreferStaticAlloc) |
362 | | static M3Runtime runtime; |
363 | | #else |
364 | 125 | M3Runtime runtime; |
365 | 125 | #endif |
366 | 125 | M3_INIT(runtime); |
367 | | |
368 | 125 | runtime.environment = i_module->runtime->environment; |
369 | 125 | runtime.numStackSlots = i_module->runtime->numStackSlots; |
370 | 125 | runtime.stack = i_module->runtime->stack; |
371 | | |
372 | 125 | m3stack_t stack = (m3stack_t)runtime.stack; |
373 | | |
374 | 125 | IM3Runtime savedRuntime = i_module->runtime; |
375 | 125 | i_module->runtime = &runtime; |
376 | | |
377 | 125 | IM3Compilation o = &runtime.compilation; |
378 | 125 | o->runtime = &runtime; |
379 | 125 | o->module = i_module; |
380 | 125 | o->wasm = *io_bytes; |
381 | 125 | o->wasmEnd = i_end; |
382 | 125 | o->lastOpcodeStart = o->wasm; |
383 | | |
384 | | // Borrowed for the length of this expression only. Runtime_Release below hands it |
385 | | // to Environment_ReleaseCodePages, which resets lineIndex, so the page comes back |
386 | | // empty and the next expression reuses it rather than burning fresh lines. |
387 | 125 | o->page = AcquireCodePage(&runtime); |
388 | | |
389 | 125 | if (o->page) { |
390 | 125 | IM3FuncType ftype = runtime.environment->retFuncTypes[BaseTypeOf(i_type)]; |
391 | | |
392 | 125 | pc_t m3code = GetPagePC(o->page); |
393 | 125 | result = CompileExpression(o, ftype); |
394 | | |
395 | 125 | if (not result && o->maxStackSlots >= runtime.numStackSlots) { |
396 | 3 | result = m3Err_trapStackOverflow; |
397 | 3 | } |
398 | | |
399 | 125 | if (not result) { |
400 | | #if (d_m3EnableOpProfiling || d_m3EnableOpTracing) |
401 | | m3ret_t r = RunCode(m3code, stack, NULL, d_m3OpDefaultArgs, d_m3BaseCstr); |
402 | | #else |
403 | 91 | m3ret_t r = RunCode(m3code, stack, NULL, d_m3OpDefaultArgs); |
404 | 91 | #endif |
405 | | |
406 | 91 | if (r == 0) { m3log (runtime, "expression result: %s", SPrintValue (stack, i_type)); |
407 | 91 | if (SizeOfType(BaseTypeOf(i_type)) == sizeof(u32)) { |
408 | 1 | *(u32*)o_expressed = *((u32*)stack); |
409 | 90 | } else { |
410 | 90 | *(u64*)o_expressed = *((u64*)stack); |
411 | 90 | } |
412 | 91 | } |
413 | 91 | } |
414 | | |
415 | 125 | ReleaseCodePage(&runtime, o->page); |
416 | 125 | } else { |
417 | 0 | result = m3Err_mallocFailedCodePage; |
418 | 0 | } |
419 | | |
420 | 125 | runtime.originStack = NULL; // prevent free(stack) in ReleaseRuntime |
421 | 125 | Runtime_Release(&runtime); |
422 | 125 | i_module->runtime = savedRuntime; |
423 | | |
424 | 125 | *io_bytes = o->wasm; |
425 | | |
426 | 125 | return result; |
427 | 125 | } |
428 | | |
429 | | |
430 | | //--------------------------------------------------------------------------------------------------------------------------------- |
431 | | // Linking a module's imports against the exports of the modules already loaded |
432 | | // into the same runtime, matched on the name a module was registered under. |
433 | | // |
434 | | // This is best-effort: an import nothing satisfies is left alone rather than |
435 | | // rejected, because a host function may still be bound to it after the module |
436 | | // is loaded (m3_LinkRawFunction needs the runtime, so it cannot run earlier), |
437 | | // and because an unsatisfiable memory or global still has to be backed by |
438 | | // something for the module to be loadable at all. |
439 | | //--------------------------------------------------------------------------------------------------------------------------------- |
440 | | |
441 | | // Whether an exporting memory or table satisfies what an import asks for. The |
442 | | // exporter's *current* size is its minimum - one that has been grown satisfies |
443 | | // a larger import than its declaration would - and it may be no less bounded. |
444 | | static |
445 | | bool LimitsSatisfy (u64 i_exportedSize, bool i_exportedHasMax, u64 i_exportedMax, |
446 | | u64 i_importMin, bool i_importHasMax, u64 i_importMax) |
447 | 0 | { |
448 | 0 | if (i_exportedSize < i_importMin) { |
449 | 0 | return false; |
450 | 0 | } |
451 | | |
452 | 0 | if (i_importHasMax and (not i_exportedHasMax or i_exportedMax > i_importMax)) { |
453 | 0 | return false; |
454 | 0 | } |
455 | | |
456 | 0 | return true; |
457 | 0 | } |
458 | | |
459 | | |
460 | | static |
461 | | IM3Function Module_FindExportedFunction (IM3Module i_module, cstr_t i_name) |
462 | 0 | { |
463 | 0 | for (u32 i = 0; i < i_module->numFunctions; ++i) { |
464 | 0 | IM3Function f = &i_module->functions[i]; |
465 | |
|
466 | 0 | if (f->export_name and strcmp(f->export_name, i_name) == 0) { |
467 | | // A module that re-exports an import names the placeholder here. |
468 | | // Resolve to the function that actually runs, or a host-side call |
469 | | // would run it against the importing module's memory. |
470 | 0 | return Function_Implementation(f); |
471 | 0 | } |
472 | 0 | } |
473 | | |
474 | 0 | return NULL; |
475 | 0 | } |
476 | | |
477 | | |
478 | | static |
479 | | IM3Memory Module_FindExportedMemory (IM3Module i_module, cstr_t i_name) |
480 | 0 | { |
481 | 0 | for (u32 i = 0; i < i_module->numMemories; ++i) { |
482 | 0 | IM3Memory memory = i_module->memories[i]; |
483 | |
|
484 | 0 | if (memory->exportName and strcmp(memory->exportName, i_name) == 0) { |
485 | 0 | return memory; |
486 | 0 | } |
487 | 0 | } |
488 | | |
489 | 0 | return NULL; |
490 | 0 | } |
491 | | |
492 | | |
493 | | static |
494 | | IM3Table Module_FindExportedTable (IM3Module i_module, cstr_t i_name) |
495 | 0 | { |
496 | 0 | for (u32 i = 0; i < i_module->numTables; ++i) { |
497 | 0 | IM3Table table = i_module->tables[i]; |
498 | |
|
499 | 0 | if (table->exportName and strcmp(table->exportName, i_name) == 0) { |
500 | 0 | return table; |
501 | 0 | } |
502 | 0 | } |
503 | | |
504 | 0 | return NULL; |
505 | 0 | } |
506 | | |
507 | | |
508 | | static |
509 | | IM3Global Module_FindExportedGlobal (IM3Module i_module, cstr_t i_name) |
510 | 0 | { |
511 | 0 | for (u32 i = 0; i < i_module->numGlobals; ++i) { |
512 | 0 | IM3Global g = &i_module->globals[i]; |
513 | |
|
514 | 0 | if (g->name and strcmp(g->name, i_name) == 0) { |
515 | 0 | return g; |
516 | 0 | } |
517 | 0 | } |
518 | | |
519 | 0 | return NULL; |
520 | 0 | } |
521 | | |
522 | | |
523 | | // Whether the module exports anything at all under this name. Export names are |
524 | | // unique within a module, so a name one of the lookups above missed but this |
525 | | // one finds is exported as something else - a kind the import cannot be |
526 | | // satisfied by, rather than a name the module never exported. |
527 | | static |
528 | | bool Module_HasExport (IM3Module i_module, cstr_t i_name) |
529 | 0 | { |
530 | 0 | for (u32 i = 0; i < i_module->numFunctions; ++i) { |
531 | 0 | IM3Function f = &i_module->functions[i]; |
532 | |
|
533 | 0 | if (f->export_name and strcmp(f->export_name, i_name) == 0) { |
534 | 0 | return true; |
535 | 0 | } |
536 | 0 | } |
537 | | |
538 | 0 | for (u32 i = 0; i < i_module->numMemories; ++i) { |
539 | 0 | IM3Memory memory = i_module->memories[i]; |
540 | |
|
541 | 0 | if (memory->exportName and strcmp(memory->exportName, i_name) == 0) { |
542 | 0 | return true; |
543 | 0 | } |
544 | 0 | } |
545 | | |
546 | 0 | for (u32 i = 0; i < i_module->numTables; ++i) { |
547 | 0 | IM3Table table = i_module->tables[i]; |
548 | |
|
549 | 0 | if (table->exportName and strcmp(table->exportName, i_name) == 0) { |
550 | 0 | return true; |
551 | 0 | } |
552 | 0 | } |
553 | | |
554 | 0 | for (u32 i = 0; i < i_module->numGlobals; ++i) { |
555 | 0 | IM3Global g = &i_module->globals[i]; |
556 | |
|
557 | 0 | if (g->name and strcmp(g->name, i_name) == 0) { |
558 | 0 | return true; |
559 | 0 | } |
560 | 0 | } |
561 | | |
562 | 0 | #if d_m3HasExceptionHandling |
563 | 0 | for (u32 i = 0; i < i_module->numTags; ++i) { |
564 | 0 | IM3Tag tag = &i_module->tags[i]; |
565 | |
|
566 | 0 | if (tag->name and strcmp(tag->name, i_name) == 0) { |
567 | 0 | return true; |
568 | 0 | } |
569 | 0 | } |
570 | 0 | #endif |
571 | | |
572 | 0 | return false; |
573 | 0 | } |
574 | | |
575 | | |
576 | | // Points each of the module's imports at whatever already-loaded module exports |
577 | | // it. Runs before anything is allocated or initialized: a memory import has to |
578 | | // be resolved before InitMemory would give it pages of its own, and a global |
579 | | // import before InitGlobals runs an initializer that reads it. |
580 | | static |
581 | | M3Result LinkImports (IM3Runtime io_runtime, IM3Module io_module) |
582 | 1.60k | { |
583 | 1.60k | M3Result result = m3Err_none; |
584 | | |
585 | 4.39k | for (u32 i = 0; i < io_module->numFunctions; ++i) { |
586 | 2.78k | IM3Function f = &io_module->functions[i]; |
587 | | |
588 | 2.78k | if (f->wasm or not (f->import.moduleUtf8 and f->import.fieldUtf8)) { |
589 | 1.52k | continue; |
590 | 1.52k | } |
591 | | |
592 | 1.26k | IM3Module from = m3_FindModule(io_runtime, f->import.moduleUtf8); |
593 | 1.26k | if (not from) { |
594 | 1.26k | continue; |
595 | 1.26k | } |
596 | | |
597 | 0 | IM3Function exported = Module_FindExportedFunction(from, f->import.fieldUtf8); |
598 | 0 | if (not exported) { |
599 | | // the import names a module that is loaded, so its exports settle the |
600 | | // question: a name it exports as something else is a type mismatch, and |
601 | | // one it does not export at all is an unknown import |
602 | 0 | _throwif(m3Err_incompatibleImportType, Module_HasExport(from, f->import.fieldUtf8)); |
603 | 0 | _throw(m3Err_unknownImport); |
604 | 0 | } |
605 | | |
606 | | // func types are canonical within an environment, so this is the |
607 | | // structural equivalence the spec asks for |
608 | 0 | _throwif(m3Err_incompatibleImportType, exported->funcType != f->funcType); |
609 | |
|
610 | 0 | f->resolved = Function_Implementation(exported); |
611 | 0 | } |
612 | | |
613 | 2.37k | for (u32 i = 0; i < io_module->numMemories; ++i) { |
614 | 774 | IM3Memory memory = io_module->memories[i]; |
615 | | |
616 | 774 | if (not memory->imported or memory->owner != io_module) { |
617 | 756 | continue; |
618 | 756 | } |
619 | | |
620 | 18 | IM3Module from = m3_FindModule(io_runtime, memory->import.moduleUtf8); |
621 | 18 | if (not from) { |
622 | 18 | continue; |
623 | 18 | } |
624 | | |
625 | 0 | IM3Memory exported = Module_FindExportedMemory(from, memory->import.fieldUtf8); |
626 | 0 | if (not exported) { |
627 | 0 | _throwif(m3Err_incompatibleImportType, Module_HasExport(from, memory->import.fieldUtf8)); |
628 | 0 | _throw(m3Err_unknownImport); |
629 | 0 | } |
630 | | |
631 | | // the address type is part of the memory type, so an i64 memory does |
632 | | // not satisfy an i32 import, or the other way round - and so is the page |
633 | | // size, which custom page sizes made a declared property of a memory |
634 | 0 | _throwif(m3Err_incompatibleImportType, exported->isMemory64 != memory->isMemory64); |
635 | 0 | _throwif(m3Err_incompatibleImportType, Memory_PageSize(exported) != Memory_PageSize(memory)); |
636 | |
|
637 | 0 | _throwif(m3Err_incompatibleImportType, |
638 | 0 | not LimitsSatisfy(exported->numPages, exported->hasMax, exported->maxPages, |
639 | 0 | memory->initPages, memory->hasMax, memory->maxPages)); |
640 | | |
641 | | // hand the slot over to the exporter's memory, and drop the placeholder |
642 | 0 | m3_Free(memory->mallocated); |
643 | 0 | m3_Free(memory->exportName); |
644 | 0 | FreeImportInfo(&memory->import); |
645 | 0 | m3_Free(memory); |
646 | |
|
647 | 0 | io_module->memories[i] = exported; |
648 | 0 | } |
649 | | |
650 | 1.97k | for (u32 i = 0; i < io_module->numTables; ++i) { |
651 | 372 | IM3Table table = io_module->tables[i]; |
652 | | |
653 | 372 | if (not table->imported or table->owner != io_module) { |
654 | 298 | continue; |
655 | 298 | } |
656 | | |
657 | 74 | IM3Module from = m3_FindModule(io_runtime, table->import.moduleUtf8); |
658 | 74 | if (not from) { |
659 | 74 | continue; |
660 | 74 | } |
661 | | |
662 | 0 | IM3Table exported = Module_FindExportedTable(from, table->import.fieldUtf8); |
663 | 0 | if (not exported) { |
664 | 0 | _throwif(m3Err_incompatibleImportType, Module_HasExport(from, table->import.fieldUtf8)); |
665 | 0 | _throw(m3Err_unknownImport); |
666 | 0 | } |
667 | | |
668 | 0 | _throwif(m3Err_incompatibleImportType, exported->type != table->type); |
669 | | |
670 | | // the index type is part of the table type, the same way it is for a memory |
671 | 0 | _throwif(m3Err_incompatibleImportType, exported->isTable64 != table->isTable64); |
672 | |
|
673 | 0 | _throwif(m3Err_incompatibleImportType, |
674 | 0 | not LimitsSatisfy(exported->size, exported->hasMax, exported->maxSize, |
675 | 0 | table->initSize, table->hasMax, table->maxSize)); |
676 | | |
677 | | // hand the slot over to the exporter's table, and drop the placeholder |
678 | 0 | m3_Free(table->elements); |
679 | 0 | m3_Free(table->exportName); |
680 | 0 | FreeImportInfo(&table->import); |
681 | 0 | m3_Free(table); |
682 | |
|
683 | 0 | io_module->tables[i] = exported; |
684 | 0 | } |
685 | | |
686 | 1.73k | for (u32 i = 0; i < io_module->numGlobals; ++i) { |
687 | 133 | IM3Global g = &io_module->globals[i]; |
688 | | |
689 | 133 | if (not g->imported or not (g->import.moduleUtf8 and g->import.fieldUtf8)) { |
690 | 126 | continue; |
691 | 126 | } |
692 | | |
693 | 7 | IM3Module from = m3_FindModule(io_runtime, g->import.moduleUtf8); |
694 | 7 | if (not from) { |
695 | 7 | continue; |
696 | 7 | } |
697 | | |
698 | 0 | IM3Global exported = Module_FindExportedGlobal(from, g->import.fieldUtf8); |
699 | 0 | if (not exported) { |
700 | 0 | _throwif(m3Err_incompatibleImportType, Module_HasExport(from, g->import.fieldUtf8)); |
701 | 0 | _throw(m3Err_unknownImport); |
702 | 0 | } |
703 | | |
704 | 0 | _throwif(m3Err_incompatibleImportType, exported->type != g->type); |
705 | 0 | _throwif(m3Err_incompatibleImportType, exported->isMutable != g->isMutable); |
706 | |
|
707 | 0 | g->resolved = exported->resolved ? exported->resolved : exported; |
708 | 0 | } |
709 | | |
710 | 1.60k | _catch: return result; |
711 | 1.60k | } |
712 | | |
713 | | |
714 | | // Backs each of the module's memories with pages. LinkImports has already |
715 | | // pointed any import it could satisfy at the exporting module's memory, and |
716 | | // those are skipped here. An import nothing satisfied is still backed locally |
717 | | // from its own declared limits, so that the module remains loadable. |
718 | | M3Result InitMemory (IM3Runtime io_runtime, IM3Module i_module) |
719 | 1.60k | { |
720 | 1.60k | M3Result result = m3Err_none; |
721 | | |
722 | | // Fixed from here on: the index space stops changing after parse, and |
723 | | // linking has already repointed any slot it was going to. |
724 | 1.60k | i_module->memory0 = i_module->numMemories ? i_module->memories[0] |
725 | 1.60k | : &i_module->emptyMemory; |
726 | | |
727 | 1.60k | if (i_module->numMemories == 0) { |
728 | | // nothing addressable, but _mem still has to point somewhere |
729 | 869 | i_module->emptyMemory.owner = i_module; |
730 | 869 | i_module->emptyMemory.pageSize = d_m3DefaultMemPageSize; |
731 | | |
732 | 869 | _ (ResizeMemory(io_runtime, &i_module->emptyMemory, 0)); |
733 | 869 | } |
734 | | |
735 | 2.37k | for (u32 i = 0; i < i_module->numMemories; ++i) { |
736 | 774 | IM3Memory memory = i_module->memories[i]; |
737 | | |
738 | | // a slot that already points at another module's memory is that |
739 | | // module's to allocate |
740 | 774 | if (memory->owner != i_module or memory->mallocated) { |
741 | 0 | continue; |
742 | 0 | } |
743 | | |
744 | 774 | u32 pageSize = Memory_PageSize(memory); |
745 | | |
746 | 774 | memory->pageSize = pageSize; |
747 | | |
748 | | // Without a declared maximum a memory may grow to the spec limit of |
749 | | // 2^|addrtype|/pagesize pages, which is the usual 65536 at the default |
750 | | // page size, 2^48 for a 64-bit memory, and a whole address space of |
751 | | // them when a page is a single byte. A declared maximum of zero is a |
752 | | // real limit, not the absence of one. |
753 | | // |
754 | | // 2^64/pagesize overflows a u64 only when a page is a single byte, and |
755 | | // a power-of-two page size divides the address space exactly, so the |
756 | | // 64-bit division below is 2^64/pagesize written so that it fits. |
757 | 774 | if (not memory->hasMax) { |
758 | 483 | memory->maxPages = memory->isMemory64 |
759 | 483 | ? (pageSize > 1 ? (UINT64_MAX / pageSize) + 1 : UINT64_MAX) |
760 | 483 | : (0x100000000ull / pageSize); |
761 | 483 | } |
762 | | |
763 | 774 | _ (ResizeMemory(io_runtime, memory, memory->initPages)); |
764 | 774 | } |
765 | | |
766 | 1.60k | _catch: return result; |
767 | 1.60k | } |
768 | | |
769 | | |
770 | | M3Result ResizeMemory (IM3Runtime io_runtime, IM3Memory memory, u64 i_numPages) |
771 | 1.65k | { |
772 | 1.65k | M3Result result = m3Err_none; |
773 | | |
774 | 1.65k | u64 numPagesToAlloc = i_numPages; |
775 | | |
776 | 1.65k | if (numPagesToAlloc <= memory->maxPages) { |
777 | | // A 64-bit memory may ask for up to 2^48 pages, which overflows a u64 |
778 | | // of bytes. Nothing that large can be backed, so refuse it up front |
779 | | // rather than multiplying into a wrapped size. |
780 | 1.64k | _throwif("linear memory limitation exceeded", |
781 | 1.64k | numPagesToAlloc > d_m3AddressLimit / memory->pageSize); |
782 | | |
783 | 1.64k | u64 numPageBytes = numPagesToAlloc * memory->pageSize; |
784 | | |
785 | 1.64k | #if d_m3MaxLinearMemoryPages > 0 |
786 | | // the limit is a memory size, counted in default-sized pages; comparing |
787 | | // it against a raw page count would make it 65536 times stricter for a |
788 | | // module whose pages are one byte |
789 | 1.64k | _throwif("linear memory limitation exceeded", |
790 | 1.64k | numPageBytes > (u64)d_m3MaxLinearMemoryPages * d_m3DefaultMemPageSize); |
791 | 1.64k | #endif |
792 | | |
793 | | // Limit the amount of memory that gets actually allocated |
794 | 1.64k | if (io_runtime->memoryLimit) { |
795 | 1.64k | numPageBytes = M3_MIN(numPageBytes, (u64)io_runtime->memoryLimit); |
796 | 1.64k | } |
797 | | |
798 | 1.64k | _throwif("linear memory limitation exceeded", numPageBytes > (u64)SIZE_MAX - sizeof(M3MemoryHeader)); |
799 | | |
800 | 1.64k | size_t numBytes = (size_t)numPageBytes + sizeof(M3MemoryHeader); |
801 | | |
802 | 1.64k | size_t numPreviousBytes = (size_t)memory->numPages * memory->pageSize; |
803 | 1.64k | if (numPreviousBytes) { |
804 | 6 | numPreviousBytes += sizeof(M3MemoryHeader); |
805 | 6 | } |
806 | | |
807 | 1.64k | void* newMem = m3_Realloc("Wasm Linear Memory", memory->mallocated, numBytes, numPreviousBytes); |
808 | 1.64k | _throwifnull(newMem); |
809 | | |
810 | 1.64k | memory->mallocated = (M3MemoryHeader*)newMem; |
811 | | |
812 | | #if d_m3LogRuntime |
813 | | M3MemoryHeader* oldMallocated = memory->mallocated; |
814 | | #endif |
815 | | |
816 | 1.64k | memory->numPages = numPagesToAlloc; |
817 | | |
818 | 1.64k | memory->mallocated->length = (size_t)numPageBytes; |
819 | 1.64k | memory->mallocated->runtime = io_runtime; |
820 | 1.64k | memory->mallocated->memory = memory; |
821 | | |
822 | 1.64k | memory->mallocated->maxStack = (m3slot_t*)io_runtime->stack + io_runtime->numStackSlots; |
823 | | |
824 | 1.64k | m3log(runtime, "resized old: %p; mem: %p; length: %zu; pages: %llu", oldMallocated, memory->mallocated, memory->mallocated->length, (unsigned long long)memory->numPages); |
825 | 1.64k | } else { |
826 | 1 | result = m3Err_wasmMemoryOverflow; |
827 | 1 | } |
828 | | |
829 | 1.65k | _catch: return result; |
830 | 1.65k | } |
831 | | |
832 | | |
833 | | M3Result InitGlobals (IM3Module io_module) |
834 | 1.60k | { |
835 | 1.60k | M3Result result = m3Err_none; |
836 | | |
837 | 1.60k | if (io_module->numGlobals) { |
838 | | // placing the globals in their structs isn't good for cache locality, but i don't really know what the global |
839 | | // access patterns typically look like yet. |
840 | | |
841 | | // io_module->globalMemory = m3Alloc (m3reg_t, io_module->numGlobals); |
842 | | |
843 | | // if (io_module->globalMemory) |
844 | 132 | { |
845 | 230 | for (u32 i = 0; i < io_module->numGlobals; ++i) { |
846 | 132 | M3Global* g = &io_module->globals[i]; m3log (runtime, "initializing global: %d", i); |
847 | | |
848 | 132 | if (g->initExpr) { |
849 | 125 | bytes_t start = g->initExpr; |
850 | | |
851 | 125 | result = EvaluateExpression(io_module, &g->i64Value, g->type, &start, g->initExpr + g->initExprSize); |
852 | | |
853 | 125 | if (not result) { |
854 | | // io_module->globalMemory [i] = initValue; |
855 | 91 | } else { |
856 | 34 | break; |
857 | 34 | } |
858 | 125 | } else { m3log (runtime, "importing global"); |
859 | 7 | } |
860 | 132 | } |
861 | 132 | } |
862 | | // else result = ErrorModule (m3Err_mallocFailed, io_module, "could allocate globals for module: '%s", io_module->name); |
863 | 132 | } |
864 | | |
865 | 1.60k | return result; |
866 | 1.60k | } |
867 | | |
868 | | |
869 | | M3Result InitDataSegments (IM3Module io_module) |
870 | 1.56k | { |
871 | 1.56k | M3Result result = m3Err_none; |
872 | | |
873 | 1.56k | for (u32 i = 0; i < io_module->numDataSegments; ++i) { |
874 | 0 | M3DataSegment* segment = &io_module->dataSegments[i]; |
875 | | |
876 | | // A passive segment stays available for memory.init until data.drop. |
877 | | // An active one is copied here and then counts as dropped. |
878 | 0 | if (segment->isPassive) { |
879 | 0 | continue; |
880 | 0 | } |
881 | | |
882 | 0 | _throwif("data segment memory index out of range", |
883 | 0 | segment->memoryRegion >= io_module->numMemories); |
884 | |
|
885 | 0 | IM3Memory io_memory = io_module->memories[segment->memoryRegion]; |
886 | |
|
887 | 0 | _throwif("unallocated linear memory", !(io_memory->mallocated)); |
888 | | |
889 | | // The offset expression has the memory's address type, and is |
890 | | // unsigned: an i32 offset of -1 is 4294967295, way out of bounds |
891 | | // rather than negative. |
892 | 0 | u64 segmentOffset = 0; |
893 | 0 | bytes_t start = segment->initExpr; |
894 | |
|
895 | 0 | if (io_memory->isMemory64) { |
896 | 0 | _ (EvaluateExpression(io_module, &segmentOffset, c_m3Type_i64, &start, segment->initExpr + segment->initExprSize)); |
897 | 0 | } else { |
898 | 0 | u32 offset32; |
899 | 0 | _ (EvaluateExpression(io_module, &offset32, c_m3Type_i32, &start, segment->initExpr + segment->initExprSize)); |
900 | 0 | segmentOffset = offset32; |
901 | 0 | } |
902 | | |
903 | 0 | m3log(runtime, "loading data segment: %d; size: %d; offset: %llu", i, segment->size, (unsigned long long)segmentOffset); |
904 | |
|
905 | 0 | if (segmentOffset <= io_memory->mallocated->length && |
906 | 0 | (u64)segment->size <= io_memory->mallocated->length - segmentOffset) { |
907 | 0 | u8* dest = m3MemData(io_memory->mallocated) + segmentOffset; |
908 | 0 | memcpy(dest, segment->data, segment->size); |
909 | 0 | } else { |
910 | 0 | _throw("data segment out of bounds"); |
911 | 0 | } |
912 | | |
913 | 0 | segment->dropped = true; |
914 | 0 | } |
915 | | |
916 | 1.56k | _catch: return result; |
917 | 1.56k | } |
918 | | |
919 | | |
920 | | // Turns a segment's elements into references. Element expressions are constant |
921 | | // expressions restricted to ref.null/ref.func, so they're read directly rather |
922 | | // than run through the compiler. |
923 | | static |
924 | | M3Result ResolveElements (IM3Module io_module, M3ElementSegment* i_segment, void** o_elements) |
925 | 0 | { |
926 | 0 | M3Result result = m3Err_none; |
927 | |
|
928 | 0 | bytes_t pos = i_segment->elements; |
929 | 0 | cbytes_t end = io_module->elementSectionEnd; |
930 | |
|
931 | 0 | for (u32 e = 0; e < i_segment->numElements; ++e) { |
932 | 0 | u32 funcIndex; |
933 | 0 | void* ref = NULL; |
934 | |
|
935 | 0 | if (i_segment->isExpr) { |
936 | 0 | m3opcode_t opcode; |
937 | 0 | _ (Read_opcode(&opcode, &pos, end)); |
938 | |
|
939 | 0 | if (opcode == c_waOp_refFunc) { |
940 | 0 | _ (ReadLEB_u32(&funcIndex, &pos, end)); |
941 | 0 | _throwif("function index out of range", funcIndex >= io_module->numFunctions); |
942 | 0 | ref = Function_Implementation(&io_module->functions[funcIndex]); |
943 | 0 | } else if (opcode == c_waOp_refNull) { |
944 | 0 | i8 waType; |
945 | 0 | u8 nullType; |
946 | 0 | _ (ReadLEB_i7(&waType, &pos, end)); |
947 | 0 | _ (NormalizeType(&nullType, waType)); |
948 | 0 | _throwif(m3Err_typeMismatch, nullType != i_segment->type); |
949 | 0 | } else if (opcode == c_waOp_getGlobal) { |
950 | | // wasm 2.0 lets an element expression read an imported |
951 | | // immutable global, which is how one module seeds another's |
952 | | // table with a reference it exported. |
953 | 0 | u32 globalIndex; |
954 | 0 | _ (ReadLEB_u32(&globalIndex, &pos, end)); |
955 | 0 | _throwif(m3Err_globaIndexOutOfBounds, globalIndex >= io_module->numGlobals); |
956 | |
|
957 | 0 | IM3Global global = &io_module->globals[globalIndex]; |
958 | |
|
959 | 0 | _throwif(m3Err_globaIndexOutOfBounds, not global->imported); |
960 | 0 | _throwif(m3Err_wasmMalformed, global->isMutable); |
961 | 0 | _throwif(m3Err_typeMismatch, BaseTypeOf(global->type) != BaseTypeOf(i_segment->type)); |
962 | | |
963 | | // read the cell the import was linked to, not the placeholder |
964 | 0 | if (global->resolved) { |
965 | 0 | global = global->resolved; |
966 | 0 | } |
967 | |
|
968 | 0 | ref = global->refValue; |
969 | 0 | } else { |
970 | 0 | _throw("constant expression required"); |
971 | 0 | } |
972 | | |
973 | 0 | _ (Read_opcode(&opcode, &pos, end)); |
974 | 0 | _throwif(m3Err_wasmMalformed, opcode != c_waOp_end); |
975 | 0 | } else { |
976 | 0 | _ (ReadLEB_u32(&funcIndex, &pos, end)); |
977 | 0 | _throwif("function index out of range", funcIndex >= io_module->numFunctions); |
978 | 0 | ref = Function_Implementation(&io_module->functions[funcIndex]); |
979 | 0 | } |
980 | | |
981 | 0 | o_elements[e] = ref; |
982 | 0 | } |
983 | | |
984 | 0 | _catch: return result; |
985 | 0 | } |
986 | | |
987 | | |
988 | | M3Result InitTableAndElements (IM3Module io_module) |
989 | 1.56k | { |
990 | 1.56k | M3Result result = m3Err_none; |
991 | | |
992 | 1.56k | cbytes_t end = io_module->elementSectionEnd; |
993 | 1.56k | M3Table* table; |
994 | | |
995 | 1.94k | for (u32 i = 0; i < io_module->numTables; ++i) { |
996 | 372 | table = io_module->tables[i]; |
997 | | |
998 | | // a slot pointing at another module's table is that module's to fill |
999 | 372 | if (table->owner != io_module) { |
1000 | 0 | continue; |
1001 | 0 | } |
1002 | | |
1003 | 372 | if (table->size) { |
1004 | 277 | table->elements = m3_AllocArray(void*, table->size); |
1005 | 277 | _throwifnull(table->elements); |
1006 | | |
1007 | 277 | if (table->initExpr) { |
1008 | 0 | void* value = NULL; |
1009 | 0 | bytes_t start = table->initExpr; |
1010 | 0 | _ (EvaluateExpression(io_module, &value, BaseTypeOf(table->type), |
1011 | 0 | &start, table->initExpr + table->initExprSize)); |
1012 | |
|
1013 | 0 | for (u32 e = 0; e < table->size; ++e) { |
1014 | 0 | table->elements[e] = value; |
1015 | 0 | } |
1016 | 0 | } |
1017 | 277 | } |
1018 | 372 | } |
1019 | | |
1020 | 1.56k | for (u32 i = 0; i < io_module->numElementSegments; ++i) { |
1021 | 0 | M3ElementSegment* segment = &io_module->elementSegments[i]; |
1022 | | |
1023 | | // Declarative segments only make their functions referenceable, and |
1024 | | // passive ones wait for table.init, so neither is written out here. |
1025 | 0 | if (segment->mode == c_m3Elem_declarative) { |
1026 | 0 | segment->dropped = true; |
1027 | 0 | continue; |
1028 | 0 | } |
1029 | | |
1030 | 0 | if (segment->mode == c_m3Elem_passive) { |
1031 | 0 | if (segment->numElements) { |
1032 | 0 | segment->resolved = m3_AllocArray(void*, segment->numElements); |
1033 | 0 | _throwifnull(segment->resolved); |
1034 | 0 | _ (ResolveElements(io_module, segment, segment->resolved)); |
1035 | 0 | } |
1036 | 0 | continue; |
1037 | 0 | } |
1038 | | |
1039 | 0 | table = io_module->tables[segment->tableIndex]; |
1040 | | |
1041 | | // The offset expression has the table's index type, and is unsigned: |
1042 | | // an i32 offset of -1 is 4294967295, out of bounds rather than negative. |
1043 | 0 | u64 offset = 0; |
1044 | 0 | bytes_t expr = segment->initExpr; |
1045 | |
|
1046 | 0 | if (table->isTable64) { |
1047 | 0 | _ (EvaluateExpression(io_module, &offset, c_m3Type_i64, &expr, end)); |
1048 | 0 | } else { |
1049 | 0 | u32 offset32; |
1050 | 0 | _ (EvaluateExpression(io_module, &offset32, c_m3Type_i32, &expr, end)); |
1051 | 0 | offset = offset32; |
1052 | 0 | } |
1053 | | |
1054 | 0 | _throwif("out of bounds table access", |
1055 | 0 | offset > table->size or segment->numElements > table->size - offset); |
1056 | |
|
1057 | 0 | _ (ResolveElements(io_module, segment, table->elements + offset)); |
1058 | |
|
1059 | 0 | segment->dropped = true; |
1060 | 0 | } |
1061 | | |
1062 | 1.56k | _catch: return result; |
1063 | 1.56k | } |
1064 | | |
1065 | | M3Result m3_CompileModule (IM3Module io_module) |
1066 | 0 | { |
1067 | 0 | M3Result result = m3Err_none; |
1068 | |
|
1069 | 0 | for (u32 i = 0; i < io_module->numFunctions; ++i) { |
1070 | 0 | IM3Function f = &io_module->functions[i]; |
1071 | 0 | if (f->wasm and not f->compiled) { |
1072 | 0 | _ (CompileFunction(f)); |
1073 | 0 | } |
1074 | 0 | } |
1075 | | |
1076 | 0 | _catch: return result; |
1077 | 0 | } |
1078 | | |
1079 | | #if d_m3HasExceptionHandling |
1080 | | |
1081 | | M3Exception* NewException (IM3Runtime io_runtime, IM3Tag i_tag, u32 i_numArgs) |
1082 | 2 | { |
1083 | 2 | M3Exception* exception = (M3Exception*)m3_Malloc("M3Exception", sizeof(M3Exception) + i_numArgs * sizeof(u64)); |
1084 | | |
1085 | 2 | if (exception) { |
1086 | 2 | exception->tag = i_tag; |
1087 | 2 | exception->numArgs = i_numArgs; |
1088 | 2 | exception->reified = false; |
1089 | 2 | exception->prev = NULL; |
1090 | 2 | exception->next = io_runtime->exceptions; |
1091 | | |
1092 | 2 | if (exception->next) { |
1093 | 0 | exception->next->prev = exception; |
1094 | 0 | } |
1095 | | |
1096 | 2 | io_runtime->exceptions = exception; |
1097 | 2 | } |
1098 | | |
1099 | 2 | return exception; |
1100 | 2 | } |
1101 | | |
1102 | | |
1103 | | // Releases one exception ahead of the rest. The caller has to know nothing can |
1104 | | // still name it: no exnref was ever taken of it, and its payload has already |
1105 | | // been copied out. |
1106 | | void FreeException (IM3Runtime io_runtime, M3Exception* i_exception) |
1107 | 0 | { |
1108 | 0 | if (i_exception->prev) { |
1109 | 0 | i_exception->prev->next = i_exception->next; |
1110 | 0 | } else { |
1111 | 0 | io_runtime->exceptions = i_exception->next; |
1112 | 0 | } |
1113 | |
|
1114 | 0 | if (i_exception->next) { |
1115 | 0 | i_exception->next->prev = i_exception->prev; |
1116 | 0 | } |
1117 | |
|
1118 | 0 | if (io_runtime->pendingException == i_exception) { |
1119 | 0 | io_runtime->pendingException = NULL; |
1120 | 0 | } |
1121 | |
|
1122 | 0 | m3_Free_Impl(i_exception); |
1123 | 0 | } |
1124 | | |
1125 | | |
1126 | | // Releases every exception the runtime still holds. Only safe once the Wasm |
1127 | | // stack is empty, which is why the outermost RunCodeChecked() is the one that |
1128 | | // calls it. |
1129 | | void FreeExceptions (IM3Runtime io_runtime) |
1130 | 448 | { |
1131 | 448 | M3Exception* exception = io_runtime->exceptions; |
1132 | | |
1133 | 448 | io_runtime->exceptions = NULL; |
1134 | 448 | io_runtime->pendingException = NULL; |
1135 | | |
1136 | 450 | while (exception) { |
1137 | 2 | M3Exception* next = exception->next; |
1138 | 2 | m3_Free_Impl(exception); |
1139 | 2 | exception = next; |
1140 | 2 | } |
1141 | 448 | } |
1142 | | |
1143 | | #endif // d_m3HasExceptionHandling |
1144 | | |
1145 | | |
1146 | | // Run compiled code on the runtime's stack, bounding native recursion for the |
1147 | | // duration of the call. The outermost invocation establishes the stack limit; |
1148 | | // nested ones (an imported function calling back into Wasm) inherit it. |
1149 | | static inline |
1150 | | M3Result RunCodeChecked (IM3Runtime i_runtime, IM3Function i_function) |
1151 | 448 | { |
1152 | 448 | pc_t i_pc = i_function->compiled; |
1153 | | |
1154 | | // execution runs against the memory of the module the entry point belongs |
1155 | | // to, not against some runtime-wide one |
1156 | 448 | M3MemoryHeader* _mem = Module_MemoryHeader(i_function->module); |
1157 | | |
1158 | 448 | d_m3StackLimitEnter(i_runtime); |
1159 | 448 | #if d_m3HasExceptionHandling |
1160 | | // handler stacks don't nest across a call boundary: a host function calling |
1161 | | // back into Wasm cannot be caught by a try_table its own caller entered |
1162 | 448 | u32 savedTryDepth = i_runtime->tryDepth; |
1163 | 448 | i_runtime->tryDepth = 0; |
1164 | 448 | i_runtime->exceptionNesting++; |
1165 | 448 | #endif |
1166 | | #if (d_m3EnableOpProfiling || d_m3EnableOpTracing) |
1167 | | M3Result result = (M3Result)RunCode(i_pc, (m3stack_t)i_runtime->stack, _mem, d_m3OpDefaultArgs, d_m3BaseCstr); |
1168 | | #else |
1169 | 448 | M3Result result = (M3Result)RunCode(i_pc, (m3stack_t)i_runtime->stack, _mem, d_m3OpDefaultArgs); |
1170 | 448 | #endif |
1171 | 448 | #if d_m3HasExceptionHandling |
1172 | 448 | i_runtime->tryDepth = savedTryDepth; |
1173 | | |
1174 | | // an exception that reached the bottom of the call stack found no handler |
1175 | 448 | if (M3_UNLIKELY(result == m3Err_pendingException)) { |
1176 | 2 | result = m3Err_trapUncaughtException; |
1177 | 2 | } |
1178 | | |
1179 | 448 | if (--i_runtime->exceptionNesting == 0) { |
1180 | 448 | FreeExceptions(i_runtime); |
1181 | 448 | } |
1182 | 448 | #endif |
1183 | 448 | d_m3StackLimitLeave(i_runtime); |
1184 | | |
1185 | 448 | return result; |
1186 | 448 | } |
1187 | | |
1188 | | M3Result m3_RunStart (IM3Module io_module) |
1189 | 0 | { |
1190 | 0 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
1191 | | // Execution disabled for fuzzing builds |
1192 | 0 | return m3Err_none; |
1193 | 0 | #endif |
1194 | | |
1195 | 0 | M3Result result = m3Err_none; |
1196 | 0 | i32 startFunctionTmp = -1; |
1197 | |
|
1198 | 0 | if (io_module and io_module->startFunction >= 0) { |
1199 | 0 | IM3Function function = &io_module->functions[io_module->startFunction]; |
1200 | |
|
1201 | 0 | if (not function->compiled) { |
1202 | 0 | _ (CompileFunction(function)); |
1203 | 0 | } |
1204 | | |
1205 | 0 | IM3FuncType ftype = function->funcType; |
1206 | 0 | if (ftype->numArgs != 0 || ftype->numRets != 0) { |
1207 | 0 | _throw(m3Err_argumentCountMismatch); |
1208 | 0 | } |
1209 | | |
1210 | 0 | IM3Module module = function->module; |
1211 | 0 | IM3Runtime runtime = module->runtime; |
1212 | |
|
1213 | 0 | startFunctionTmp = io_module->startFunction; |
1214 | |
|
1215 | 0 | io_module->startFunction = -1; |
1216 | |
|
1217 | 0 | result = RunCodeChecked(runtime, function); |
1218 | |
|
1219 | 0 | if (result) { |
1220 | 0 | io_module->startFunction = startFunctionTmp; |
1221 | 0 | EXCEPTION_PRINT(result); |
1222 | 0 | goto _catch; |
1223 | 0 | } |
1224 | 0 | } |
1225 | | |
1226 | 0 | _catch: return result; |
1227 | 0 | } |
1228 | | |
1229 | | // TODO: deal with main + side-modules loading efforcement |
1230 | | M3Result m3_LoadModule (IM3Runtime io_runtime, IM3Module io_module) |
1231 | 1.60k | { |
1232 | 1.60k | M3Result result = m3Err_none; |
1233 | | |
1234 | 1.60k | if (M3_UNLIKELY(io_module->runtime)) { |
1235 | 0 | return m3Err_moduleAlreadyLinked; |
1236 | 0 | } |
1237 | | |
1238 | 1.60k | io_module->runtime = io_runtime; |
1239 | | |
1240 | | // linking first: a memory import has to be resolved before InitMemory would |
1241 | | // give it pages of its own, and a global import before an initializer reads it |
1242 | 1.60k | _ (LinkImports(io_runtime, io_module)); |
1243 | | |
1244 | 1.60k | _ (InitMemory(io_runtime, io_module)); |
1245 | 1.60k | _ (InitGlobals(io_module)); |
1246 | | // Spec order: element segments are applied before data segments. It matters |
1247 | | // when one of them traps - whatever ran before the trap stays done. |
1248 | 1.56k | _ (InitTableAndElements(io_module)); |
1249 | 1.56k | _ (InitDataSegments(io_module)); |
1250 | | |
1251 | | // Start func might use imported functions, which are not liked here yet, |
1252 | | // so it will be called before a function call is attempted (in m3_FindFunction) |
1253 | | |
1254 | | #ifdef DEBUG |
1255 | | Module_GenerateNames(io_module); |
1256 | | #endif |
1257 | | |
1258 | 1.56k | io_module->next = io_runtime->modules; |
1259 | 1.56k | io_runtime->modules = io_module; |
1260 | 1.56k | return result; // ok |
1261 | | |
1262 | 34 | _catch: |
1263 | | // The runtime owns the module either way. Instantiation may already have |
1264 | | // written this module's functions into a table another module owns, and the |
1265 | | // spec keeps whatever it managed to do before the trap, so those entries |
1266 | | // stay callable - retaining the module is what stops them dangling. |
1267 | | // |
1268 | | // It goes on the tail of the list rather than the head: it is not a module |
1269 | | // anyone should find by name, only one whose functions may still be |
1270 | | // reachable through someone else's table. |
1271 | 34 | io_module->next = NULL; |
1272 | | |
1273 | 34 | IM3Module* tail = &io_runtime->modules; |
1274 | 34 | while (*tail) { |
1275 | 0 | tail = &(*tail)->next; |
1276 | 0 | } |
1277 | 34 | *tail = io_module; |
1278 | | |
1279 | 34 | return result; |
1280 | 1.56k | } |
1281 | | |
1282 | | IM3Global m3_FindGlobal (IM3Module io_module, |
1283 | | const char* const i_globalName) |
1284 | 0 | { |
1285 | | // Search exports |
1286 | 0 | for (u32 i = 0; i < io_module->numGlobals; ++i) { |
1287 | 0 | IM3Global g = &io_module->globals[i]; |
1288 | 0 | if (g->name and strcmp(g->name, i_globalName) == 0) { |
1289 | | // a re-exported global is the one that was imported, so reads and |
1290 | | // writes have to reach the cell that actually holds the value |
1291 | 0 | return g->resolved ? g->resolved : g; |
1292 | 0 | } |
1293 | 0 | } |
1294 | | |
1295 | | // Search imports |
1296 | 0 | for (u32 i = 0; i < io_module->numGlobals; ++i) { |
1297 | 0 | IM3Global g = &io_module->globals[i]; |
1298 | |
|
1299 | 0 | if (g->import.moduleUtf8 and g->import.fieldUtf8) { |
1300 | 0 | if (strcmp(g->import.fieldUtf8, i_globalName) == 0) { |
1301 | 0 | return g->resolved ? g->resolved : g; |
1302 | 0 | } |
1303 | 0 | } |
1304 | 0 | } |
1305 | 0 | return NULL; |
1306 | 0 | } |
1307 | | |
1308 | | M3Result m3_GetGlobal (IM3Global i_global, |
1309 | | IM3TaggedValue o_value) |
1310 | 0 | { |
1311 | 0 | if (not i_global) { |
1312 | 0 | return m3Err_globalLookupFailed; |
1313 | 0 | } |
1314 | | |
1315 | 0 | switch (i_global->type) { |
1316 | 0 | case c_m3Type_i32: o_value->value.i32 = i_global->i32Value; break; |
1317 | 0 | case c_m3Type_i64: o_value->value.i64 = i_global->i64Value; break; |
1318 | 0 | #if d_m3HasFloat |
1319 | 0 | case c_m3Type_f32: o_value->value.f32 = i_global->f32Value; break; |
1320 | 0 | case c_m3Type_f64: o_value->value.f64 = i_global->f64Value; break; |
1321 | 0 | #endif |
1322 | 0 | default: return m3Err_invalidTypeId; |
1323 | 0 | } |
1324 | | |
1325 | 0 | o_value->type = (M3ValueType)(i_global->type); |
1326 | 0 | return m3Err_none; |
1327 | 0 | } |
1328 | | |
1329 | | M3Result m3_SetGlobal (IM3Global i_global, |
1330 | | const IM3TaggedValue i_value) |
1331 | 0 | { |
1332 | 0 | if (not i_global) { |
1333 | 0 | return m3Err_globalLookupFailed; |
1334 | 0 | } |
1335 | 0 | if (not i_global->isMutable) { |
1336 | 0 | return m3Err_globalNotMutable; |
1337 | 0 | } |
1338 | 0 | if (i_global->type != i_value->type) { |
1339 | 0 | return m3Err_globalTypeMismatch; |
1340 | 0 | } |
1341 | | |
1342 | 0 | switch (i_value->type) { |
1343 | 0 | case c_m3Type_i32: i_global->i32Value = i_value->value.i32; break; |
1344 | 0 | case c_m3Type_i64: i_global->i64Value = i_value->value.i64; break; |
1345 | 0 | #if d_m3HasFloat |
1346 | 0 | case c_m3Type_f32: i_global->f32Value = i_value->value.f32; break; |
1347 | 0 | case c_m3Type_f64: i_global->f64Value = i_value->value.f64; break; |
1348 | 0 | #endif |
1349 | 0 | default: return m3Err_invalidTypeId; |
1350 | 0 | } |
1351 | | |
1352 | 0 | return m3Err_none; |
1353 | 0 | } |
1354 | | |
1355 | | M3ValueType m3_GetGlobalType (IM3Global i_global) |
1356 | 0 | { |
1357 | 0 | return (i_global) ? (M3ValueType)(i_global->type) : c_m3Type_none; |
1358 | 0 | } |
1359 | | |
1360 | | |
1361 | | void* v_FindFunction (IM3Module i_module, void* i_info) |
1362 | 1.56k | { |
1363 | 1.56k | const char* const i_name = (const char*)i_info; |
1364 | | |
1365 | | // Prefer exported functions |
1366 | 4.35k | for (u32 i = 0; i < i_module->numFunctions; ++i) { |
1367 | 2.78k | IM3Function f = &i_module->functions[i]; |
1368 | 2.78k | if (f->export_name and strcmp(f->export_name, i_name) == 0) { |
1369 | | // A module that re-exports an import names the placeholder here. |
1370 | | // Resolve to the function that actually runs, or a host-side call |
1371 | | // would run it against the importing module's memory. |
1372 | 0 | return Function_Implementation(f); |
1373 | 0 | } |
1374 | 2.78k | } |
1375 | | |
1376 | | // Search internal functions |
1377 | 2.83k | for (u32 i = 0; i < i_module->numFunctions; ++i) { |
1378 | 2.78k | IM3Function f = &i_module->functions[i]; |
1379 | | |
1380 | 2.78k | bool isImported = f->import.moduleUtf8 or f->import.fieldUtf8; |
1381 | | |
1382 | 2.78k | if (isImported) { |
1383 | 1.26k | continue; |
1384 | 1.26k | } |
1385 | | |
1386 | 1.53k | for (int j = 0; j < f->numNames; j++) { |
1387 | 1.52k | if (f->names[j] and strcmp(f->names[j], i_name) == 0) { |
1388 | 1.51k | return f; |
1389 | 1.51k | } |
1390 | 1.52k | } |
1391 | 1.52k | } |
1392 | | |
1393 | 51 | return NULL; |
1394 | 1.56k | } |
1395 | | |
1396 | | |
1397 | | // Shared tail of the two lookups: a function is only usable once it has code. |
1398 | | static |
1399 | | M3Result PrepareFoundFunction (IM3Function* o_function, IM3Function i_function) |
1400 | 1.51k | { |
1401 | 1.51k | M3Result result = m3Err_none; |
1402 | | |
1403 | 1.51k | if (not i_function->compiled) { |
1404 | 1.51k | _ (CompileFunction(i_function)) |
1405 | 1.51k | } |
1406 | | |
1407 | 1.51k | _catch: |
1408 | 1.51k | *o_function = result ? NULL : i_function; |
1409 | | |
1410 | 1.51k | return result; |
1411 | 1.51k | } |
1412 | | |
1413 | | |
1414 | | // Searches every module in the runtime, most recently loaded first. That is a |
1415 | | // guess once more than one module is loaded and two of them export the same |
1416 | | // name - m3_FindFunctionIn says which module is meant. |
1417 | | M3Result m3_FindFunction (IM3Function* o_function, IM3Runtime i_runtime, const char* const i_functionName) |
1418 | 1.56k | { |
1419 | 1.56k | d_m3Assert (o_function and i_runtime and i_functionName); |
1420 | 1.56k | IM3Function function = NULL; |
1421 | | |
1422 | 1.56k | if (not i_runtime->modules) { |
1423 | 0 | *o_function = NULL; |
1424 | 0 | return "no modules loaded"; |
1425 | 0 | } |
1426 | | |
1427 | 1.56k | function = (IM3Function)ForEachModule(i_runtime, v_FindFunction, (void*)i_functionName); |
1428 | | |
1429 | 1.56k | if (not function) { |
1430 | 51 | *o_function = NULL; |
1431 | 51 | return ErrorModule(m3Err_functionLookupFailed, i_runtime->modules, "'%s'", i_functionName); |
1432 | 51 | } |
1433 | | |
1434 | 1.51k | return PrepareFoundFunction(o_function, function); |
1435 | 1.56k | } |
1436 | | |
1437 | | |
1438 | | IM3Module m3_FindModule (IM3Runtime i_runtime, const char* const i_moduleName) |
1439 | 1.36k | { |
1440 | 1.36k | if (not i_runtime or not i_moduleName) { |
1441 | 0 | return NULL; |
1442 | 0 | } |
1443 | | |
1444 | | // the list is newest-first, so a name registered twice names the newer one |
1445 | 1.36k | for (IM3Module m = i_runtime->modules; m; m = m->next) { |
1446 | 0 | if (m->name and strcmp(m->name, i_moduleName) == 0) { |
1447 | 0 | return m; |
1448 | 0 | } |
1449 | 0 | } |
1450 | | |
1451 | 1.36k | return NULL; |
1452 | 1.36k | } |
1453 | | |
1454 | | |
1455 | | // Searches one module's exports, which is what naming a module means. |
1456 | | M3Result m3_FindFunctionIn (IM3Function* o_function, IM3Module i_module, const char* const i_functionName) |
1457 | 0 | { |
1458 | 0 | d_m3Assert (o_function and i_functionName); |
1459 | 0 | if (not i_module) { |
1460 | 0 | *o_function = NULL; |
1461 | 0 | return m3Err_functionLookupFailed; // ErrorModule would deref it |
1462 | 0 | } |
1463 | | |
1464 | 0 | IM3Function function = (IM3Function)v_FindFunction(i_module, (void*)i_functionName); |
1465 | |
|
1466 | 0 | if (not function) { |
1467 | 0 | *o_function = NULL; |
1468 | 0 | return ErrorModule(m3Err_functionLookupFailed, i_module, "'%s'", i_functionName); |
1469 | 0 | } |
1470 | | |
1471 | 0 | return PrepareFoundFunction(o_function, function); |
1472 | 0 | } |
1473 | | |
1474 | | |
1475 | | M3Result m3_GetTableFunction (IM3Function* o_function, IM3Module i_module, uint32_t i_index) |
1476 | 0 | { |
1477 | 0 | _try { |
1478 | 0 | M3Table* table; |
1479 | 0 | IM3Function function; |
1480 | |
|
1481 | 0 | _throwif("no table", i_module->numTables == 0); |
1482 | |
|
1483 | 0 | table = i_module->tables[0]; |
1484 | 0 | _throwif("function index out of range", i_index >= table->size); |
1485 | |
|
1486 | 0 | function = (IM3Function)table->elements[i_index]; |
1487 | |
|
1488 | 0 | if (function) { |
1489 | 0 | if (not function->compiled) { |
1490 | 0 | _ (CompileFunction(function)) |
1491 | 0 | } |
1492 | 0 | } |
1493 | | |
1494 | 0 | *o_function = function; |
1495 | 0 | } _catch: |
1496 | 0 | return result; |
1497 | 0 | } |
1498 | | |
1499 | | |
1500 | | static |
1501 | | M3Result checkStartFunction (IM3Module i_module) |
1502 | 467 | { |
1503 | 467 | M3Result result = m3Err_none; d_m3Assert(i_module); |
1504 | | |
1505 | | // Check if start function needs to be called |
1506 | 467 | if (i_module->startFunction >= 0) { |
1507 | 0 | result = m3_RunStart(i_module); |
1508 | 0 | } |
1509 | | |
1510 | 467 | return result; |
1511 | 467 | } |
1512 | | |
1513 | | uint32_t m3_GetArgCount (IM3Function i_function) |
1514 | 0 | { |
1515 | 0 | if (i_function) { |
1516 | 0 | IM3FuncType ft = i_function->funcType; |
1517 | 0 | if (ft) { |
1518 | 0 | return ft->numArgs; |
1519 | 0 | } |
1520 | 0 | } |
1521 | 0 | return 0; |
1522 | 0 | } |
1523 | | |
1524 | | uint32_t m3_GetRetCount (IM3Function i_function) |
1525 | 0 | { |
1526 | 0 | if (i_function) { |
1527 | 0 | IM3FuncType ft = i_function->funcType; |
1528 | 0 | if (ft) { |
1529 | 0 | return ft->numRets; |
1530 | 0 | } |
1531 | 0 | } |
1532 | 0 | return 0; |
1533 | 0 | } |
1534 | | |
1535 | | |
1536 | | M3ValueType m3_GetArgType (IM3Function i_function, uint32_t index) |
1537 | 0 | { |
1538 | 0 | if (i_function) { |
1539 | 0 | IM3FuncType ft = i_function->funcType; |
1540 | 0 | if (ft and index < ft->numArgs) { |
1541 | 0 | return (M3ValueType)BaseTypeOf(d_FuncArgType(ft, index)); |
1542 | 0 | } |
1543 | 0 | } |
1544 | 0 | return c_m3Type_none; |
1545 | 0 | } |
1546 | | |
1547 | | M3ValueType m3_GetRetType (IM3Function i_function, uint32_t index) |
1548 | 0 | { |
1549 | 0 | if (i_function) { |
1550 | 0 | IM3FuncType ft = i_function->funcType; |
1551 | 0 | if (ft and index < ft->numRets) { |
1552 | 0 | return (M3ValueType)BaseTypeOf(d_FuncRetType(ft, index)); |
1553 | 0 | } |
1554 | 0 | } |
1555 | 0 | return c_m3Type_none; |
1556 | 0 | } |
1557 | | |
1558 | | |
1559 | | u8* GetStackPointerForArgs (IM3Function i_function) |
1560 | 467 | { |
1561 | 467 | u64* stack = (u64*)i_function->module->runtime->stack; |
1562 | 467 | IM3FuncType ftype = i_function->funcType; |
1563 | | |
1564 | 467 | stack += ftype->numRets; |
1565 | | |
1566 | 467 | return (u8*)stack; |
1567 | 467 | } |
1568 | | |
1569 | | |
1570 | | M3Result m3_CallV (IM3Function i_function, ...) |
1571 | 467 | { |
1572 | 467 | va_list ap; |
1573 | 467 | va_start(ap, i_function); |
1574 | 467 | M3Result r = m3_CallVL(i_function, ap); |
1575 | 467 | va_end(ap); |
1576 | 467 | return r; |
1577 | 467 | } |
1578 | | |
1579 | | static |
1580 | | void ReportNativeStackUsage () |
1581 | 448 | { |
1582 | | #if d_m3LogNativeStack |
1583 | | int stackUsed = m3StackGetMax(); |
1584 | | fprintf(stderr, "Native stack used: %d\n", stackUsed); |
1585 | | #endif |
1586 | 448 | } |
1587 | | |
1588 | | |
1589 | | M3Result m3_CallVL (IM3Function i_function, va_list i_args) |
1590 | 467 | { |
1591 | 467 | IM3Runtime runtime = i_function->module->runtime; |
1592 | 467 | IM3FuncType ftype = i_function->funcType; |
1593 | 467 | M3Result result = m3Err_none; |
1594 | 467 | u8* s = NULL; |
1595 | | |
1596 | 467 | if (!i_function->compiled) { |
1597 | 0 | return m3Err_missingCompiledCode; |
1598 | 0 | } |
1599 | | |
1600 | | #if d_m3RecordBacktraces |
1601 | | ClearBacktrace(runtime); |
1602 | | #endif |
1603 | | |
1604 | 467 | m3StackCheckInit(); |
1605 | | |
1606 | 467 | _ (checkStartFunction(i_function->module)) |
1607 | | |
1608 | 467 | s = GetStackPointerForArgs(i_function); |
1609 | | |
1610 | 487 | for (u32 i = 0; i < ftype->numArgs; ++i) { |
1611 | 39 | switch (d_FuncArgType(ftype, i)) { |
1612 | 15 | case c_m3Type_i32: |
1613 | 15 | *(i32*)(s) = va_arg(i_args, i32); |
1614 | 15 | s += 8; |
1615 | 15 | break; |
1616 | 2 | case c_m3Type_i64: |
1617 | 2 | *(i64*)(s) = va_arg(i_args, i64); |
1618 | 2 | s += 8; |
1619 | 2 | break; |
1620 | 0 | case c_m3Type_funcref: |
1621 | 0 | case c_m3Type_externref: |
1622 | 0 | case c_m3Type_exnref: |
1623 | 0 | *(uintptr_t*)(s) = va_arg(i_args, uintptr_t); |
1624 | 0 | s += 8; |
1625 | 0 | break; |
1626 | 0 | #if d_m3HasFloat |
1627 | 0 | case c_m3Type_f32: |
1628 | 0 | *(f32*)(s) = (f32)va_arg(i_args, f64); |
1629 | 0 | s += 8; |
1630 | 0 | break; // f32 is passed as f64 |
1631 | 3 | case c_m3Type_f64: |
1632 | 3 | *(f64*)(s) = va_arg(i_args, f64); |
1633 | 3 | s += 8; |
1634 | 3 | break; |
1635 | 0 | #endif |
1636 | 19 | default: return "unknown argument type"; |
1637 | 39 | } |
1638 | 39 | } |
1639 | | |
1640 | 448 | result = RunCodeChecked(runtime, i_function); |
1641 | 448 | ReportNativeStackUsage(); |
1642 | | |
1643 | 448 | runtime->lastCalled = result ? NULL : i_function; |
1644 | | |
1645 | 448 | _catch: return result; |
1646 | 448 | } |
1647 | | |
1648 | | M3Result m3_Call (IM3Function i_function, uint32_t i_argc, const void* i_argptrs[]) |
1649 | 0 | { |
1650 | 0 | IM3Runtime runtime = i_function->module->runtime; |
1651 | 0 | IM3FuncType ftype = i_function->funcType; |
1652 | 0 | M3Result result = m3Err_none; |
1653 | 0 | u8* s = NULL; |
1654 | |
|
1655 | 0 | if (i_argc != ftype->numArgs) { |
1656 | 0 | return m3Err_argumentCountMismatch; |
1657 | 0 | } |
1658 | 0 | if (!i_function->compiled) { |
1659 | 0 | return m3Err_missingCompiledCode; |
1660 | 0 | } |
1661 | | |
1662 | | #if d_m3RecordBacktraces |
1663 | | ClearBacktrace(runtime); |
1664 | | #endif |
1665 | | |
1666 | 0 | m3StackCheckInit(); |
1667 | |
|
1668 | 0 | _ (checkStartFunction(i_function->module)) |
1669 | | |
1670 | 0 | s = GetStackPointerForArgs(i_function); |
1671 | |
|
1672 | 0 | for (u32 i = 0; i < ftype->numArgs; ++i) { |
1673 | 0 | switch (d_FuncArgType(ftype, i)) { |
1674 | 0 | case c_m3Type_i32: |
1675 | 0 | *(i32*)(s) = *(i32*)i_argptrs[i]; |
1676 | 0 | s += 8; |
1677 | 0 | break; |
1678 | 0 | case c_m3Type_i64: |
1679 | 0 | *(i64*)(s) = *(i64*)i_argptrs[i]; |
1680 | 0 | s += 8; |
1681 | 0 | break; |
1682 | 0 | case c_m3Type_funcref: |
1683 | 0 | case c_m3Type_externref: |
1684 | 0 | case c_m3Type_exnref: |
1685 | 0 | *(uintptr_t*)(s) = *(uintptr_t*)i_argptrs[i]; |
1686 | 0 | s += 8; |
1687 | 0 | break; |
1688 | 0 | #if d_m3HasFloat |
1689 | 0 | case c_m3Type_f32: |
1690 | 0 | *(f32*)(s) = *(f32*)i_argptrs[i]; |
1691 | 0 | s += 8; |
1692 | 0 | break; |
1693 | 0 | case c_m3Type_f64: |
1694 | 0 | *(f64*)(s) = *(f64*)i_argptrs[i]; |
1695 | 0 | s += 8; |
1696 | 0 | break; |
1697 | 0 | #endif |
1698 | 0 | default: return "unknown argument type"; |
1699 | 0 | } |
1700 | 0 | } |
1701 | | |
1702 | 0 | result = RunCodeChecked(runtime, i_function); |
1703 | |
|
1704 | 0 | ReportNativeStackUsage(); |
1705 | |
|
1706 | 0 | runtime->lastCalled = result ? NULL : i_function; |
1707 | |
|
1708 | 0 | _catch: return result; |
1709 | 0 | } |
1710 | | |
1711 | | // Argument parsing for m3_CallArgv. Strict on purpose: the whole string has to |
1712 | | // be consumed, so "12abc" is rejected rather than read as 12, and an empty or |
1713 | | // unparsable argument is an error rather than the zero that strtoul with a |
1714 | | // NULL end pointer used to hand back. See wasm3/wasm3#367. |
1715 | | static |
1716 | | M3Result ParseArgInteger (ccstr_t i_arg, u32 i_numBits, u64* o_value) |
1717 | 0 | { |
1718 | 0 | if (not i_arg or not *i_arg) { |
1719 | 0 | return "empty argument"; |
1720 | 0 | } |
1721 | | |
1722 | | // strtoull would skip leading space, but trailing space is rejected below; |
1723 | | // accepting one and not the other would just be confusing |
1724 | 0 | if (isspace((unsigned char)*i_arg)) { |
1725 | 0 | return "argument is not a number"; |
1726 | 0 | } |
1727 | | |
1728 | 0 | char* end = NULL; |
1729 | 0 | u64 value; |
1730 | |
|
1731 | 0 | errno = 0; |
1732 | | |
1733 | | // an argument may be spelled signed or unsigned: -1 and 4294967295 name the |
1734 | | // same i32 |
1735 | 0 | if (*i_arg == '-') { |
1736 | 0 | i64 signedValue = strtoll(i_arg, &end, 10); |
1737 | |
|
1738 | 0 | if (i_numBits == 32 and (signedValue < INT32_MIN or signedValue > INT32_MAX)) { |
1739 | 0 | return "argument out of range"; |
1740 | 0 | } |
1741 | | |
1742 | 0 | value = (u64)signedValue; |
1743 | 0 | } else { |
1744 | 0 | value = strtoull(i_arg, &end, 10); |
1745 | |
|
1746 | 0 | if (i_numBits == 32 and value > UINT32_MAX) { |
1747 | 0 | return "argument out of range"; |
1748 | 0 | } |
1749 | 0 | } |
1750 | | |
1751 | 0 | if (errno == ERANGE) { |
1752 | 0 | return "argument out of range"; |
1753 | 0 | } |
1754 | | |
1755 | 0 | if (end == i_arg or *end) { |
1756 | 0 | return "argument is not a number"; |
1757 | 0 | } |
1758 | | |
1759 | 0 | *o_value = value; |
1760 | |
|
1761 | 0 | return m3Err_none; |
1762 | 0 | } |
1763 | | |
1764 | | |
1765 | | #if d_m3HasFloat |
1766 | | static |
1767 | | M3Result ParseArgFloat (ccstr_t i_arg, f64* o_value) |
1768 | 0 | { |
1769 | 0 | if (not i_arg or not *i_arg) { |
1770 | 0 | return "empty argument"; |
1771 | 0 | } |
1772 | | |
1773 | 0 | if (isspace((unsigned char)*i_arg)) { |
1774 | 0 | return "argument is not a number"; |
1775 | 0 | } |
1776 | | |
1777 | 0 | char* end = NULL; |
1778 | |
|
1779 | 0 | errno = 0; |
1780 | |
|
1781 | 0 | f64 value = strtod(i_arg, &end); |
1782 | |
|
1783 | 0 | if (end == i_arg or *end) { |
1784 | 0 | return "argument is not a number"; |
1785 | 0 | } |
1786 | | |
1787 | | // strtod reports underflow through ERANGE as well, and a denormal result is |
1788 | | // perfectly usable, so only an overflow to infinity is out of range |
1789 | 0 | if (errno == ERANGE and (value > DBL_MAX or value < -DBL_MAX)) { |
1790 | 0 | return "argument out of range"; |
1791 | 0 | } |
1792 | | |
1793 | 0 | *o_value = value; |
1794 | |
|
1795 | 0 | return m3Err_none; |
1796 | 0 | } |
1797 | | #endif |
1798 | | |
1799 | | |
1800 | | // A reference argument is either the null reference or a host handle written as |
1801 | | // an integer. |
1802 | | static |
1803 | | M3Result ParseArgReference (ccstr_t i_arg, u64* o_value) |
1804 | 0 | { |
1805 | 0 | if (i_arg and strcmp(i_arg, "null") == 0) { |
1806 | 0 | *o_value = 0; |
1807 | 0 | return m3Err_none; |
1808 | 0 | } |
1809 | | |
1810 | 0 | return ParseArgInteger(i_arg, 64, o_value); |
1811 | 0 | } |
1812 | | |
1813 | | |
1814 | | M3Result m3_CallArgv (IM3Function i_function, uint32_t i_argc, const char* i_argv[]) |
1815 | 0 | { |
1816 | 0 | IM3FuncType ftype = i_function->funcType; |
1817 | 0 | IM3Runtime runtime = i_function->module->runtime; |
1818 | 0 | M3Result result = m3Err_none; |
1819 | 0 | u8* s = NULL; |
1820 | |
|
1821 | 0 | if (i_argc != ftype->numArgs) { |
1822 | 0 | return m3Err_argumentCountMismatch; |
1823 | 0 | } |
1824 | 0 | if (!i_function->compiled) { |
1825 | 0 | return m3Err_missingCompiledCode; |
1826 | 0 | } |
1827 | | |
1828 | | #if d_m3RecordBacktraces |
1829 | | ClearBacktrace(runtime); |
1830 | | #endif |
1831 | | |
1832 | 0 | m3StackCheckInit(); |
1833 | |
|
1834 | 0 | _ (checkStartFunction(i_function->module)) |
1835 | | |
1836 | 0 | s = GetStackPointerForArgs(i_function); |
1837 | |
|
1838 | 0 | for (u32 i = 0; i < ftype->numArgs; ++i) { |
1839 | 0 | u64 value = 0; |
1840 | 0 | #if d_m3HasFloat |
1841 | 0 | f64 fvalue = 0; |
1842 | 0 | #endif |
1843 | 0 | switch (d_FuncArgType(ftype, i)) { |
1844 | 0 | case c_m3Type_i32: |
1845 | 0 | _ (ParseArgInteger(i_argv[i], 32, &value)) * (i32*)(s) = (i32)value; |
1846 | 0 | s += 8; |
1847 | 0 | break; |
1848 | 0 | case c_m3Type_i64: |
1849 | 0 | _ (ParseArgInteger(i_argv[i], 64, &value)) * (i64*)(s) = (i64)value; |
1850 | 0 | s += 8; |
1851 | 0 | break; |
1852 | 0 | case c_m3Type_funcref: |
1853 | 0 | case c_m3Type_externref: |
1854 | 0 | case c_m3Type_exnref: |
1855 | 0 | _ (ParseArgReference(i_argv[i], &value)) * (uintptr_t*)(s) = (uintptr_t)value; |
1856 | 0 | s += 8; |
1857 | 0 | break; |
1858 | 0 | #if d_m3HasFloat |
1859 | | // strtof would be less portable |
1860 | 0 | case c_m3Type_f32: |
1861 | 0 | _ (ParseArgFloat(i_argv[i], &fvalue)) * (f32*)(s) = (f32)fvalue; |
1862 | 0 | s += 8; |
1863 | 0 | break; |
1864 | 0 | case c_m3Type_f64: |
1865 | 0 | _ (ParseArgFloat(i_argv[i], &fvalue)) * (f64*)(s) = fvalue; |
1866 | 0 | s += 8; |
1867 | 0 | break; |
1868 | 0 | #endif |
1869 | 0 | default: _throw("unknown argument type"); |
1870 | 0 | } |
1871 | 0 | } |
1872 | | |
1873 | 0 | result = RunCodeChecked(runtime, i_function); |
1874 | |
|
1875 | 0 | ReportNativeStackUsage(); |
1876 | |
|
1877 | 0 | runtime->lastCalled = result ? NULL : i_function; |
1878 | |
|
1879 | 0 | _catch: return result; |
1880 | 0 | } |
1881 | | |
1882 | | |
1883 | | //u8 * AlignStackPointerTo64Bits (const u8 * i_stack) |
1884 | | //{ |
1885 | | // uintptr_t ptr = (uintptr_t) i_stack; |
1886 | | // return (u8 *) ((ptr + 7) & ~7); |
1887 | | //} |
1888 | | |
1889 | | |
1890 | | M3Result m3_GetResults (IM3Function i_function, uint32_t i_retc, const void* o_retptrs[]) |
1891 | 0 | { |
1892 | 0 | IM3FuncType ftype = i_function->funcType; |
1893 | 0 | IM3Runtime runtime = i_function->module->runtime; |
1894 | |
|
1895 | 0 | if (i_retc != ftype->numRets) { |
1896 | 0 | return m3Err_argumentCountMismatch; |
1897 | 0 | } |
1898 | 0 | if (i_function != runtime->lastCalled) { |
1899 | 0 | return "function not called"; |
1900 | 0 | } |
1901 | | |
1902 | 0 | u8* s = (u8*)runtime->stack; |
1903 | |
|
1904 | 0 | for (u32 i = 0; i < ftype->numRets; ++i) { |
1905 | 0 | switch (d_FuncRetType(ftype, i)) { |
1906 | 0 | case c_m3Type_i32: |
1907 | 0 | *(i32*)o_retptrs[i] = *(i32*)(s); |
1908 | 0 | s += 8; |
1909 | 0 | break; |
1910 | 0 | case c_m3Type_i64: |
1911 | 0 | *(i64*)o_retptrs[i] = *(i64*)(s); |
1912 | 0 | s += 8; |
1913 | 0 | break; |
1914 | 0 | case c_m3Type_funcref: |
1915 | 0 | case c_m3Type_externref: |
1916 | 0 | case c_m3Type_exnref: |
1917 | 0 | *(uintptr_t*)o_retptrs[i] = *(uintptr_t*)(s); |
1918 | 0 | s += 8; |
1919 | 0 | break; |
1920 | 0 | #if d_m3HasFloat |
1921 | 0 | case c_m3Type_f32: |
1922 | 0 | *(f32*)o_retptrs[i] = *(f32*)(s); |
1923 | 0 | s += 8; |
1924 | 0 | break; |
1925 | 0 | case c_m3Type_f64: |
1926 | 0 | *(f64*)o_retptrs[i] = *(f64*)(s); |
1927 | 0 | s += 8; |
1928 | 0 | break; |
1929 | 0 | #endif |
1930 | 0 | default: return "unknown return type"; |
1931 | 0 | } |
1932 | 0 | } |
1933 | 0 | return m3Err_none; |
1934 | 0 | } |
1935 | | |
1936 | | M3Result m3_GetResultsV (IM3Function i_function, ...) |
1937 | 0 | { |
1938 | 0 | va_list ap; |
1939 | 0 | va_start(ap, i_function); |
1940 | 0 | M3Result r = m3_GetResultsVL(i_function, ap); |
1941 | 0 | va_end(ap); |
1942 | 0 | return r; |
1943 | 0 | } |
1944 | | |
1945 | | M3Result m3_GetResultsVL (IM3Function i_function, va_list o_rets) |
1946 | 0 | { |
1947 | 0 | IM3Runtime runtime = i_function->module->runtime; |
1948 | 0 | IM3FuncType ftype = i_function->funcType; |
1949 | |
|
1950 | 0 | if (i_function != runtime->lastCalled) { |
1951 | 0 | return "function not called"; |
1952 | 0 | } |
1953 | | |
1954 | 0 | u8* s = (u8*)runtime->stack; |
1955 | 0 | for (u32 i = 0; i < ftype->numRets; ++i) { |
1956 | 0 | switch (d_FuncRetType(ftype, i)) { |
1957 | 0 | case c_m3Type_i32: |
1958 | 0 | *va_arg(o_rets, i32*) = *(i32*)(s); |
1959 | 0 | s += 8; |
1960 | 0 | break; |
1961 | 0 | case c_m3Type_i64: |
1962 | 0 | *va_arg(o_rets, i64*) = *(i64*)(s); |
1963 | 0 | s += 8; |
1964 | 0 | break; |
1965 | 0 | case c_m3Type_funcref: |
1966 | 0 | case c_m3Type_externref: |
1967 | 0 | case c_m3Type_exnref: |
1968 | 0 | *va_arg(o_rets, uintptr_t*) = *(uintptr_t*)(s); |
1969 | 0 | s += 8; |
1970 | 0 | break; |
1971 | 0 | #if d_m3HasFloat |
1972 | 0 | case c_m3Type_f32: |
1973 | 0 | *va_arg(o_rets, f32*) = *(f32*)(s); |
1974 | 0 | s += 8; |
1975 | 0 | break; |
1976 | 0 | case c_m3Type_f64: |
1977 | 0 | *va_arg(o_rets, f64*) = *(f64*)(s); |
1978 | 0 | s += 8; |
1979 | 0 | break; |
1980 | 0 | #endif |
1981 | 0 | default: return "unknown argument type"; |
1982 | 0 | } |
1983 | 0 | } |
1984 | 0 | return m3Err_none; |
1985 | 0 | } |
1986 | | |
1987 | | void ReleaseCodePageNoTrack (IM3Runtime i_runtime, IM3CodePage i_codePage) |
1988 | 1.23k | { |
1989 | 1.23k | if (i_codePage) { |
1990 | 1.23k | IM3CodePage* list; |
1991 | | |
1992 | 1.23k | bool pageFull = (NumFreeLines(i_codePage) < d_m3CodePageFreeLinesThreshold); |
1993 | 1.23k | if (pageFull) { |
1994 | 8 | list = &i_runtime->pagesFull; |
1995 | 1.22k | } else { |
1996 | 1.22k | list = &i_runtime->pagesOpen; |
1997 | 1.22k | } |
1998 | | |
1999 | 1.23k | PushCodePage(list, i_codePage); m3log (emit, "release page: %d to queue: '%s'", i_codePage->info.sequence, pageFull ? "full" : "open") |
2000 | 1.23k | } |
2001 | 1.23k | } |
2002 | | |
2003 | | |
2004 | | IM3CodePage AcquireCodePageWithCapacity (IM3Runtime i_runtime, u32 i_minLineCount) |
2005 | 1.23k | { |
2006 | 1.23k | IM3CodePage page = RemoveCodePageOfCapacity(&i_runtime->pagesOpen, i_minLineCount); |
2007 | | |
2008 | 1.23k | if (not page) { |
2009 | 1.16k | page = Environment_AcquireCodePage(i_runtime->environment, i_minLineCount); |
2010 | | |
2011 | 1.16k | if (not page) { |
2012 | 1.10k | page = NewCodePage(i_runtime, i_minLineCount); |
2013 | 1.10k | } |
2014 | | |
2015 | 1.16k | if (page) { |
2016 | 1.16k | i_runtime->numCodePages++; |
2017 | 1.16k | } |
2018 | 1.16k | } |
2019 | | |
2020 | 1.23k | if (page) { m3log (emit, "acquire page: %d", page->info.sequence); |
2021 | 1.23k | i_runtime->numActiveCodePages++; |
2022 | 1.23k | } |
2023 | | |
2024 | 1.23k | return page; |
2025 | 1.23k | } |
2026 | | |
2027 | | |
2028 | | IM3CodePage AcquireCodePage (IM3Runtime i_runtime) |
2029 | 1.22k | { |
2030 | 1.22k | return AcquireCodePageWithCapacity(i_runtime, d_m3CodePageFreeLinesThreshold); |
2031 | 1.22k | } |
2032 | | |
2033 | | |
2034 | | void ReleaseCodePage (IM3Runtime i_runtime, IM3CodePage i_codePage) |
2035 | 1.23k | { |
2036 | 1.23k | if (i_codePage) { |
2037 | 1.23k | ReleaseCodePageNoTrack(i_runtime, i_codePage); |
2038 | 1.23k | i_runtime->numActiveCodePages--; |
2039 | | |
2040 | | #if defined(DEBUG) |
2041 | | u32 numOpen = CountCodePages(i_runtime->pagesOpen); |
2042 | | u32 numFull = CountCodePages(i_runtime->pagesFull); |
2043 | | |
2044 | | m3log(runtime, "runtime: %p; open-pages: %d; full-pages: %d; active: %d; total: %d", i_runtime, numOpen, numFull, i_runtime->numActiveCodePages, i_runtime->numCodePages); |
2045 | | |
2046 | | d_m3Assert(numOpen + numFull + i_runtime->numActiveCodePages == i_runtime->numCodePages); |
2047 | | |
2048 | | # if d_m3LogCodePages |
2049 | | dump_code_page(i_codePage, /* startPC: */ NULL); |
2050 | | # endif |
2051 | | #endif |
2052 | 1.23k | } |
2053 | 1.23k | } |
2054 | | |
2055 | | |
2056 | | #if d_m3VerboseErrorMessages |
2057 | | M3Result m3Error (M3Result i_result, IM3Runtime i_runtime, IM3Module i_module, IM3Function i_function, |
2058 | | const char* const i_file, u32 i_lineNum, const char* const i_errorMessage, ...) |
2059 | 414 | { |
2060 | 414 | if (i_runtime) { |
2061 | 414 | i_runtime->error = (M3ErrorInfo){ .result = i_result, .runtime = i_runtime, .module = i_module, .function = i_function, .file = i_file, .line = i_lineNum }; |
2062 | 414 | i_runtime->error.message = i_runtime->error_message; |
2063 | | |
2064 | 414 | va_list args; |
2065 | 414 | va_start(args, i_errorMessage); |
2066 | 414 | vsnprintf(i_runtime->error_message, sizeof(i_runtime->error_message), i_errorMessage, args); |
2067 | 414 | va_end(args); |
2068 | 414 | } |
2069 | | |
2070 | 414 | return i_result; |
2071 | 414 | } |
2072 | | #endif |
2073 | | |
2074 | | |
2075 | | void m3_GetErrorInfo (IM3Runtime i_runtime, M3ErrorInfo* o_info) |
2076 | 0 | { |
2077 | 0 | if (i_runtime) { |
2078 | 0 | *o_info = i_runtime->error; |
2079 | 0 | m3_ResetErrorInfo(i_runtime); |
2080 | 0 | } |
2081 | 0 | } |
2082 | | |
2083 | | |
2084 | | void m3_ResetErrorInfo (IM3Runtime i_runtime) |
2085 | 1.89k | { |
2086 | 1.89k | if (i_runtime) { |
2087 | 1.89k | M3_INIT(i_runtime->error); |
2088 | 1.89k | i_runtime->error.message = ""; |
2089 | 1.89k | } |
2090 | 1.89k | } |
2091 | | |
2092 | | uint8_t* m3_GetMemory (IM3Module i_module, size_t* o_memorySizeInBytes, uint32_t i_memoryIndex) |
2093 | 0 | { |
2094 | 0 | uint8_t* memory = NULL; |
2095 | 0 | size_t size = 0; |
2096 | |
|
2097 | 0 | if (i_module and i_memoryIndex < i_module->numMemories) { |
2098 | 0 | IM3Memory mem = i_module->memories[i_memoryIndex]; |
2099 | |
|
2100 | 0 | if (mem->mallocated) { |
2101 | 0 | size = mem->mallocated->length; |
2102 | |
|
2103 | 0 | if (size) { |
2104 | 0 | memory = m3MemData(mem->mallocated); |
2105 | 0 | } |
2106 | 0 | } |
2107 | 0 | } |
2108 | |
|
2109 | 0 | if (o_memorySizeInBytes) { |
2110 | 0 | *o_memorySizeInBytes = size; |
2111 | 0 | } |
2112 | |
|
2113 | 0 | return memory; |
2114 | 0 | } |
2115 | | |
2116 | | |
2117 | | size_t m3_GetMemorySize (IM3Module i_module, uint32_t i_memoryIndex) |
2118 | 0 | { |
2119 | 0 | if (not i_module or i_memoryIndex >= i_module->numMemories) { |
2120 | 0 | return 0; |
2121 | 0 | } |
2122 | | |
2123 | 0 | IM3Memory mem = i_module->memories[i_memoryIndex]; |
2124 | |
|
2125 | 0 | return mem->mallocated ? mem->mallocated->length : 0; |
2126 | 0 | } |
2127 | | |
2128 | | |
2129 | | size_t m3_GetMemorySizeAt (const void* i_memory) |
2130 | 0 | { |
2131 | 0 | if (not i_memory) { |
2132 | 0 | return 0; |
2133 | 0 | } |
2134 | | |
2135 | | // the header sits immediately before the data it describes |
2136 | 0 | const M3MemoryHeader* header = ((const M3MemoryHeader*)i_memory) - 1; |
2137 | |
|
2138 | 0 | return header->length; |
2139 | 0 | } |
2140 | | |
2141 | | |
2142 | | M3Result m3_FindExportedMemory (IM3Module i_module, cstr_t i_name, u32* o_memoryIndex) |
2143 | 0 | { |
2144 | 0 | if (not i_module or not i_name or not o_memoryIndex) { |
2145 | 0 | return m3Err_unknownMemory; |
2146 | 0 | } |
2147 | | |
2148 | 0 | for (u32 i = 0; i < i_module->numMemories; ++i) { |
2149 | 0 | IM3Memory memory = i_module->memories[i]; |
2150 | |
|
2151 | 0 | if (memory->exportName and strcmp(memory->exportName, i_name) == 0) { |
2152 | 0 | *o_memoryIndex = i; |
2153 | 0 | return m3Err_none; |
2154 | 0 | } |
2155 | 0 | } |
2156 | | |
2157 | 0 | return m3Err_unknownMemory; |
2158 | 0 | } |
2159 | | |
2160 | | |
2161 | | bool Module_HasLinkedHostImport (IM3Module i_module, cstr_t i_importModule) |
2162 | 0 | { |
2163 | 0 | for (u32 i = 0; i < i_module->numFunctions; ++i) { |
2164 | 0 | IM3Function f = &i_module->functions[i]; |
2165 | | |
2166 | | // hostMemory is set when a host function is bound to the import, so it |
2167 | | // is also the mark of one - see CompileRawFunction |
2168 | 0 | if (not f->hostMemory or f->wasm or f->resolved) { |
2169 | 0 | continue; |
2170 | 0 | } |
2171 | | |
2172 | 0 | if (f->import.moduleUtf8 and strcmp(f->import.moduleUtf8, i_importModule) == 0) { |
2173 | 0 | return true; |
2174 | 0 | } |
2175 | 0 | } |
2176 | | |
2177 | 0 | return false; |
2178 | 0 | } |
2179 | | |
2180 | | |
2181 | | M3Result m3_BindImportMemory (IM3Module io_module, cstr_t i_importModule, u32 i_memoryIndex) |
2182 | 0 | { |
2183 | 0 | if (not io_module or not i_importModule) { |
2184 | 0 | return m3Err_moduleNotLinked; |
2185 | 0 | } |
2186 | | |
2187 | 0 | if (i_memoryIndex >= io_module->numMemories) { |
2188 | 0 | return m3Err_unknownMemory; |
2189 | 0 | } |
2190 | | |
2191 | | // the memory itself, not the index: linking may have pointed this slot at |
2192 | | // another module's memory, and growing one reallocates behind it |
2193 | 0 | IM3Memory memory = io_module->memories[i_memoryIndex]; |
2194 | |
|
2195 | 0 | const bool wildcardModule = (strcmp(i_importModule, "*") == 0); |
2196 | |
|
2197 | 0 | for (u32 i = 0; i < io_module->numFunctions; ++i) { |
2198 | 0 | IM3Function f = &io_module->functions[i]; |
2199 | | |
2200 | | // only an import that a host function was bound to has a memory to |
2201 | | // pin: one with a body of its own reads memory through the interpreter, |
2202 | | // and one resolved to another module's export runs that module's code |
2203 | 0 | if (not f->hostMemory or f->wasm or f->resolved) { |
2204 | 0 | continue; |
2205 | 0 | } |
2206 | | |
2207 | 0 | if (wildcardModule or (f->import.moduleUtf8 and strcmp(f->import.moduleUtf8, i_importModule) == 0)) { |
2208 | 0 | f->hostMemory = memory; |
2209 | 0 | } |
2210 | 0 | } |
2211 | |
|
2212 | 0 | return m3Err_none; |
2213 | 0 | } |
2214 | | |
2215 | | |
2216 | | M3BacktraceInfo* m3_GetBacktrace (IM3Runtime i_runtime) |
2217 | 0 | { |
2218 | | #if d_m3RecordBacktraces |
2219 | | return &i_runtime->backtrace; |
2220 | | #else |
2221 | | return NULL; |
2222 | 0 | #endif |
2223 | 0 | } |