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