MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5#include "ant.h"
6#include "errors.h"
7#include "internal.h"
8
9#include "ptr.h"
10#include "gc/roots.h"
11#include "modules/buffer.h"
12#include "modules/wasi.h"
13#include "wasm_c_api.h"
14#include "wasm_export.h"
15
16#define WASM_MAX_PARAMS 32
17#define WASM_MAX_ARGS 256
18
19enum {
20 WASI_INSTANCE_TAG = 0x57415349u, // WASI
21 WASI_FUNC_TAG = 0x57415346u, // WASF
22};
23
24typedef struct {
25 uint8_t *binary;
26 wasm_module_t module;
27 wasm_module_inst_t inst;
28 wasm_exec_env_t exec_env;
29} wasi_instance_handle_t;
30
31typedef struct {
32 ant_t *js;
33 wasm_module_inst_t inst;
34 wasm_exec_env_t exec_env;
35 wasm_function_inst_t func;
36} wasi_func_env_t;
37
38enum {
39 WASM_SECTION_IMPORT = 2,
40 WASM_SECTION_EXPORT = 7,
41};
42
43static bool wasm_read_u32_leb(const uint8_t *buf, size_t len, size_t *offset, uint32_t *out) {
44 uint32_t value = 0;
45 uint32_t shift = 0;
46
47 while (*offset < len && shift < 35) {
48 uint8_t byte = buf[(*offset)++];
49 value |= (uint32_t)(byte & 0x7f) << shift;
50
51 if ((byte & 0x80) == 0) {
52 *out = value;
53 return true;
54 }
55
56 shift += 7;
57 }
58
59 return false;
60}
61
62static bool wasm_read_name(const uint8_t *buf, size_t len, size_t *offset, const uint8_t **data, uint32_t *name_len) {
63 uint32_t size = 0;
64
65 if (
66 !wasm_read_u32_leb(buf, len, offset, &size)
67 || *offset + size > len
68 ) return false;
69
70 *data = buf + *offset;
71 *name_len = size;
72 *offset += size;
73
74 return true;
75}
76
77static bool wasm_name_equals(const uint8_t *data, uint32_t len, const char *expected) {
78 size_t expected_len = strlen(expected);
79 return len == expected_len && memcmp(data, expected, expected_len) == 0;
80}
81
82static bool wasi_bytes_have_wasi_imports(const uint8_t *wasm_bytes, size_t wasm_len) {
83 size_t offset = 8;
84
85 if (!wasm_bytes || wasm_len < 8 || memcmp(wasm_bytes, "\0asm\x01\0\0\0", 8) != 0)
86 return false;
87
88 while (offset < wasm_len) {
89 uint8_t section_id = wasm_bytes[offset++];
90 uint32_t section_size = 0;
91 const uint8_t *section;
92 size_t section_offset = 0;
93 size_t section_len;
94
95 if (
96 !wasm_read_u32_leb(wasm_bytes, wasm_len, &offset, §ion_size)
97 || offset + section_size > wasm_len
98 ) return false;
99
100 section = wasm_bytes + offset;
101 section_len = section_size;
102 offset += section_size;
103
104 if (section_id != WASM_SECTION_IMPORT) continue;
105 if (!wasm_read_u32_leb(section, section_size, §ion_offset, §ion_size))
106 return false;
107
108 for (uint32_t i = 0; i < section_size; i++) {
109 const uint8_t *module_name = NULL;
110 const uint8_t *field_name = NULL;
111 uint32_t module_name_len = 0;
112 uint32_t field_name_len = 0;
113 uint32_t discard = 0;
114 uint32_t flags = 0;
115
116 if (!wasm_read_name(section, section_len, §ion_offset, &module_name, &module_name_len)
117 || !wasm_read_name(section, section_len, §ion_offset, &field_name, &field_name_len)
118 || section_offset >= section_len)
119 return false;
120
121 if (wasm_name_equals(module_name, module_name_len, "wasi_snapshot_preview1"))
122 return true;
123
124 switch (section[section_offset++]) {
125 case 0:
126 if (!wasm_read_u32_leb(section, section_len, §ion_offset, &discard))
127 return false;
128 break;
129 case 1: {
130 if (section_offset >= section_len) return false;
131 section_offset++;
132 if (!wasm_read_u32_leb(section, section_len, §ion_offset, &flags)
133 || !wasm_read_u32_leb(section, section_len, §ion_offset, &discard))
134 return false;
135 if ((flags & 0x1) != 0
136 && !wasm_read_u32_leb(section, section_len, §ion_offset, &discard))
137 return false;
138 break;
139 }
140 case 2:
141 if (!wasm_read_u32_leb(section, section_len, §ion_offset, &flags)
142 || !wasm_read_u32_leb(section, section_len, §ion_offset, &discard))
143 return false;
144 if ((flags & 0x1) != 0
145 && !wasm_read_u32_leb(section, section_len, §ion_offset, &discard))
146 return false;
147 break;
148 case 3:
149 if (section_offset + 2 > section_len) return false;
150 section_offset += 2;
151 break;
152 default:
153 return false;
154 }
155 }
156 }
157
158 return false;
159}
160
161static bool wasi_bytes_have_command_or_reactor_entry(const uint8_t *wasm_bytes, size_t wasm_len) {
162 size_t offset = 8;
163
164 if (!wasm_bytes || wasm_len < 8 || memcmp(wasm_bytes, "\0asm\x01\0\0\0", 8) != 0)
165 return false;
166
167 while (offset < wasm_len) {
168 uint8_t section_id = wasm_bytes[offset++];
169 uint32_t section_size = 0;
170 const uint8_t *section;
171 size_t section_offset = 0;
172 size_t section_len;
173
174 if (!wasm_read_u32_leb(wasm_bytes, wasm_len, &offset, §ion_size)
175 || offset + section_size > wasm_len)
176 return false;
177
178 section = wasm_bytes + offset;
179 section_len = section_size;
180 offset += section_size;
181
182 if (section_id != WASM_SECTION_EXPORT) continue;
183 if (!wasm_read_u32_leb(section, section_len, §ion_offset, §ion_size))
184 return false;
185
186 for (uint32_t i = 0; i < section_size; i++) {
187 const uint8_t *name = NULL;
188 uint32_t name_len = 0;
189 uint32_t discard = 0;
190
191 if (!wasm_read_name(section, section_len, §ion_offset, &name, &name_len)
192 || section_offset >= section_len)
193 return false;
194
195 if (section[section_offset++] != 0) {
196 if (!wasm_read_u32_leb(section, section_len, §ion_offset, &discard))
197 return false;
198 continue;
199 }
200
201 if (!wasm_read_u32_leb(section, section_len, §ion_offset, &discard))
202 return false;
203
204 if (wasm_name_equals(name, name_len, "_start")
205 || wasm_name_equals(name, name_len, "_initialize"))
206 return true;
207 }
208 }
209
210 return false;
211}
212
213static inline bool wasi_is_proc_exit_exception(const char *exception) {
214 return exception != NULL && strstr(exception, "wasi proc exit") != NULL;
215}
216
217static ant_value_t wasi_handle_proc_exit(wasm_module_inst_t inst) {
218 uint32_t exit_code = wasm_runtime_get_wasi_exit_code(inst);
219 wasm_runtime_clear_exception(inst);
220 if (exit_code != 0) exit((int)exit_code);
221 return js_mkundef();
222}
223
224static ant_value_t wasi_exported_func_call(ant_t *js, ant_value_t *args, int nargs) {
225 wasi_func_env_t *env = (wasi_func_env_t *)js_get_native(js->current_func, WASI_FUNC_TAG);
226 if (!env) return js_mkerr(js, "Invalid WASI function");
227
228 uint32_t param_count = wasm_func_get_param_count(env->func, env->inst);
229 uint32_t result_count = wasm_func_get_result_count(env->func, env->inst);
230 if (param_count > WASM_MAX_PARAMS) param_count = WASM_MAX_PARAMS;
231
232 uint32_t wasm_argv[WASM_MAX_PARAMS];
233 memset(wasm_argv, 0, sizeof(wasm_argv));
234
235 for (int i = 0; i < nargs && (uint32_t)i < param_count; i++) {
236 wasm_argv[i] = (uint32_t)js_getnum(args[i]);
237 }
238
239 if (!wasm_runtime_call_wasm(env->exec_env, env->func, param_count, wasm_argv)) {
240 const char *exception = wasm_runtime_get_exception(env->inst);
241 if (wasi_is_proc_exit_exception(exception)) return wasi_handle_proc_exit(env->inst);
242 return js_mkerr(js, "%s", exception ? exception : "WASI function call failed");
243 }
244
245 if (result_count == 0) return js_mkundef();
246 return js_mknum((double)(int32_t)wasm_argv[0]);
247}
248
249static void wasi_func_finalize(ant_t *js, ant_object_t *obj) {
250 ant_value_t value = js_obj_from_ptr(obj);
251 free(js_get_native(value, WASI_FUNC_TAG));
252 js_clear_native(value, WASI_FUNC_TAG);
253}
254
255static void wasi_instance_finalize(ant_t *js, ant_object_t *obj) {
256 ant_value_t value = js_obj_from_ptr(obj);
257 wasi_instance_handle_t *handle = (wasi_instance_handle_t *)js_get_native(value, WASI_INSTANCE_TAG);
258
259 if (!handle) return;
260 if (handle->exec_env) wasm_runtime_destroy_exec_env(handle->exec_env);
261 if (handle->inst) wasm_runtime_deinstantiate(handle->inst);
262 js_clear_native(value, WASI_INSTANCE_TAG);
263 if (handle->module) wasm_runtime_unload(handle->module);
264
265 free(handle->binary);
266 free(handle);
267}
268
269bool wasi_module_has_wasi_imports(void *c_api_module) {
270 wasm_importtype_vec_t import_types = {0};
271 wasm_module_imports((wasm_module_t *)c_api_module, &import_types);
272
273 bool has_wasi = false;
274 for (size_t i = 0; i < import_types.size; i++) {
275 const wasm_name_t *mod = wasm_importtype_module(import_types.data[i]);
276 if (mod && mod->size >= 22 && memcmp(mod->data, "wasi_snapshot_preview1", 22) == 0) {
277 has_wasi = true;
278 break;
279 }}
280
281 wasm_importtype_vec_delete(&import_types);
282 return has_wasi;
283}
284
285bool wasi_module_is_command_or_reactor(void *c_api_module) {
286 wasm_exporttype_vec_t export_types = {0};
287 bool has_entry = false;
288
289 wasm_module_exports((wasm_module_t *)c_api_module, &export_types);
290
291 for (size_t i = 0; i < export_types.size; i++) {
292 const wasm_name_t *name = wasm_exporttype_name(export_types.data[i]);
293 const wasm_externtype_t *type = wasm_exporttype_type(export_types.data[i]);
294 const wasm_functype_t *func_type;
295 size_t name_len;
296
297 if (!name || wasm_externtype_kind(type) != WASM_EXTERN_FUNC) continue;
298 name_len = name->size;
299 if (name_len > 0 && name->data[name_len - 1] == '\0') name_len--;
300
301 if (!((name_len == 6 && memcmp(name->data, "_start", 6) == 0)
302 || (name_len == 11 && memcmp(name->data, "_initialize", 11) == 0)))
303 continue;
304
305 func_type = wasm_externtype_as_functype_const(type);
306 if (!func_type) continue;
307 if (wasm_functype_params(func_type)->size == 0 && wasm_functype_results(func_type)->size == 0) {
308 has_entry = true;
309 break;
310 }
311 }
312
313 wasm_exporttype_vec_delete(&export_types);
314 return has_entry;
315}
316
317bool wasi_bytes_need_wasi_command_warning_suppression(const uint8_t *wasm_bytes, size_t wasm_len) {
318 return wasi_bytes_have_wasi_imports(wasm_bytes, wasm_len)
319 && !wasi_bytes_have_command_or_reactor_entry(wasm_bytes, wasm_len);
320}
321
322static void wasi_bind_func_export(
323 ant_t *js, ant_value_t exports_obj, ant_value_t instance_obj,
324 wasm_module_inst_t inst, wasm_exec_env_t exec_env, const char *name
325) {
326 wasm_function_inst_t func = wasm_runtime_lookup_function(inst, name);
327 if (!func) return;
328
329 wasi_func_env_t *fenv = calloc(1, sizeof(*fenv));
330 if (!fenv) return;
331 fenv->js = js;
332 fenv->inst = inst;
333 fenv->exec_env = exec_env;
334 fenv->func = func;
335
336 GC_ROOT_SAVE(root_mark, js);
337 ant_value_t obj = js_mkobj(js);
338 GC_ROOT_PIN(js, obj);
339
340 js_set_slot(obj, SLOT_CFUNC, js_mkfun(wasi_exported_func_call));
341 js_set_native(obj, fenv, WASI_FUNC_TAG);
342 js_set_slot_wb(js, obj, SLOT_ENTRIES, instance_obj);
343 js_set_finalizer(obj, wasi_func_finalize);
344 js_set(js, exports_obj, name, js_obj_to_func(obj));
345 GC_ROOT_RESTORE(js, root_mark);
346}
347
348static void wasi_bind_memory_export(
349 ant_t *js, ant_value_t exports_obj, ant_value_t instance_obj,
350 wasm_module_inst_t inst, const char *name
351) {
352 void *mem_data = wasm_runtime_addr_app_to_native(inst, 0);
353 if (!mem_data) return;
354
355 wasm_memory_inst_t mem = wasm_runtime_get_default_memory(inst);
356 uint64_t pages = mem ? wasm_memory_get_cur_page_count(mem) : 0;
357 size_t mem_size = (size_t)(pages * 65536);
358
359 ArrayBufferData *buffer = calloc(1, sizeof(ArrayBufferData));
360 if (!buffer) return;
361
362 buffer->data = (uint8_t *)mem_data;
363 buffer->length = mem_size;
364 buffer->capacity = mem_size;
365 buffer->ref_count = 1;
366
367 ant_value_t ab = create_arraybuffer_obj(js, buffer);
368 ant_value_t mem_obj = js_mkobj(js);
369 js_set_slot_wb(js, mem_obj, SLOT_DATA, ab);
370 js_set_slot_wb(js, mem_obj, SLOT_CTOR, instance_obj);
371 js_set(js, exports_obj, name, mem_obj);
372}
373
374ant_value_t wasi_instantiate(
375 ant_t *js, const uint8_t *wasm_bytes, size_t wasm_len,
376 ant_value_t module_obj, ant_value_t wasi_opts
377) {
378 char error_buf[128] = {0};
379 uint8_t *bin_copy = malloc(wasm_len);
380
381 if (!bin_copy) return js_mkerr(js, "out of memory");
382 memcpy(bin_copy, wasm_bytes, wasm_len);
383
384 wasm_module_t rt_module = wasm_runtime_load(bin_copy, (uint32_t)wasm_len, error_buf, sizeof(error_buf));
385 if (!rt_module) {
386 free(bin_copy);
387 return js_mkerr(js, "%s", error_buf[0] ? error_buf : "Failed to load WASI module");
388 }
389
390 const char *dirs[] = { "." };
391 ant_value_t args_val = is_object_type(wasi_opts)
392 ? js_get(js, wasi_opts, "args")
393 : js_mkundef();
394
395 int argc = vtype(args_val) == T_ARR ? (int)js_arr_len(js, args_val) : 0;
396 if (argc < 1) argc = 1;
397 if (argc > WASM_MAX_ARGS) argc = WASM_MAX_ARGS;
398
399 char *argv[argc];
400 if (vtype(args_val) == T_ARR) {
401 for (int i = 0; i < argc; i++) {
402 char *s = js_getstr(js, js_arr_get(js, args_val, (ant_offset_t)i), NULL);
403 argv[i] = s ? s : (char *)"";
404 }
405 } else argv[0] = (char *)"wasi";
406
407 wasm_runtime_set_wasi_args(rt_module, dirs, 1, NULL, 0, NULL, 0, argv, argc);
408 wasm_module_inst_t inst = wasm_runtime_instantiate(rt_module, 512 * 1024, 256 * 1024, error_buf, sizeof(error_buf));
409
410 if (!inst) {
411 wasm_runtime_unload(rt_module);
412 free(bin_copy);
413 return js_mkerr(js, "%s", error_buf[0] ? error_buf : "Failed to instantiate WASI module");
414 }
415
416 wasm_exec_env_t exec_env = wasm_runtime_create_exec_env(inst, 512 * 1024);
417 if (!exec_env) {
418 wasm_runtime_deinstantiate(inst);
419 wasm_runtime_unload(rt_module);
420 free(bin_copy);
421 return js_mkerr(js, "Failed to create WASI exec env");
422 }
423
424 wasi_instance_handle_t *handle = calloc(1, sizeof(*handle));
425 if (!handle) {
426 wasm_runtime_destroy_exec_env(exec_env);
427 wasm_runtime_deinstantiate(inst);
428 wasm_runtime_unload(rt_module);
429 free(bin_copy);
430 return js_mkerr(js, "out of memory");
431 }
432
433 handle->binary = bin_copy;
434 handle->module = rt_module;
435 handle->inst = inst;
436 handle->exec_env = exec_env;
437
438 ant_value_t instance_obj = js_mkobj(js);
439 ant_value_t exports_obj = js_mkobj(js);
440
441 js_set_native(instance_obj, handle, WASI_INSTANCE_TAG);
442 js_set_slot_wb(js, instance_obj, SLOT_CTOR, module_obj);
443 js_set_finalizer(instance_obj, wasi_instance_finalize);
444
445 int32_t export_count = wasm_runtime_get_export_count(rt_module);
446 for (int32_t i = 0; i < export_count; i++) {
447 wasm_export_t export_info;
448 wasm_runtime_get_export_type(rt_module, i, &export_info);
449
450 if (export_info.kind == WASM_IMPORT_EXPORT_KIND_FUNC)
451 wasi_bind_func_export(js, exports_obj, instance_obj, inst, exec_env, export_info.name);
452 else if (export_info.kind == WASM_IMPORT_EXPORT_KIND_MEMORY)
453 wasi_bind_memory_export(js, exports_obj, instance_obj, inst, export_info.name);
454 }
455
456 js_set_slot_wb(js, instance_obj, SLOT_ENTRIES, exports_obj);
457 js_set(js, instance_obj, "exports", exports_obj);
458
459 return instance_obj;
460}