MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add wasm table fixtures

+172 -61
+14 -5
src/modules/wasi.c
··· 72 72 return js_mknum((double)(int32_t)wasm_argv[0]); 73 73 } 74 74 75 + static void wasi_func_finalize(ant_t *js, ant_object_t *obj) { 76 + if (obj->native.tag != WASI_FUNC_TAG) return; 77 + 78 + free(obj->native.ptr); 79 + obj->native.ptr = NULL; 80 + obj->native.tag = 0; 81 + } 82 + 75 83 static void wasi_instance_finalize(ant_t *js, ant_object_t *obj) { 76 84 if (obj->native.tag != WASI_INSTANCE_TAG) return; 77 85 wasi_instance_handle_t *handle = (wasi_instance_handle_t *)obj->native.ptr; ··· 102 110 } 103 111 104 112 static void wasi_bind_func_export( 105 - ant_t *js, ant_value_t exports_obj, 106 - wasm_module_inst_t inst, wasm_exec_env_t exec_env, 107 - const char *name 113 + ant_t *js, ant_value_t exports_obj, ant_value_t instance_obj, 114 + wasm_module_inst_t inst, wasm_exec_env_t exec_env, const char *name 108 115 ) { 109 116 wasm_function_inst_t func = wasm_runtime_lookup_function(inst, name); 110 117 if (!func) return; ··· 123 130 js_set_slot(obj, SLOT_CFUNC, js_mkfun(wasi_exported_func_call)); 124 131 js_set_native_ptr(obj, fenv); 125 132 js_set_native_tag(obj, WASI_FUNC_TAG); 133 + js_set_slot_wb(js, obj, SLOT_ENTRIES, instance_obj); 134 + js_set_finalizer(obj, wasi_func_finalize); 126 135 js_set(js, exports_obj, name, js_obj_to_func(obj)); 127 136 GC_ROOT_RESTORE(js, root_mark); 128 137 } ··· 229 238 for (int32_t i = 0; i < export_count; i++) { 230 239 wasm_export_t export_info; 231 240 wasm_runtime_get_export_type(rt_module, i, &export_info); 232 - 241 + 233 242 if (export_info.kind == WASM_IMPORT_EXPORT_KIND_FUNC) 234 - wasi_bind_func_export(js, exports_obj, inst, exec_env, export_info.name); 243 + wasi_bind_func_export(js, exports_obj, instance_obj, inst, exec_env, export_info.name); 235 244 else if (export_info.kind == WASM_IMPORT_EXPORT_KIND_MEMORY) 236 245 wasi_bind_memory_export(js, exports_obj, instance_obj, inst, export_info.name); 237 246 }
+138 -39
src/modules/wasm.c
··· 13 13 #pragma clang diagnostic pop 14 14 15 15 #include "ant.h" 16 + #include "ptr.h" 16 17 #include "errors.h" 17 18 #include "runtime.h" 18 19 #include "internal.h" ··· 62 63 ant_value_t fn; 63 64 } wasm_import_func_env_t; 64 65 65 - static wasm_engine_t *g_wasm_engine = NULL; 66 + typedef struct { 67 + wasm_func_t *func; 68 + bool own_func; 69 + } wasm_func_handle_t; 70 + 71 + enum { WASM_FUNC_STATE_TAG = 0x57465354u }; // WFST 72 + 73 + static size_t g_wasm_import_env_count = 0; 74 + static size_t g_wasm_import_env_cap = 0; 75 + 76 + static ant_value_t g_wasm_module_proto = 0; 77 + static ant_value_t g_wasm_instance_proto = 0; 78 + static ant_value_t g_wasm_global_proto = 0; 79 + static ant_value_t g_wasm_memory_proto = 0; 80 + static ant_value_t g_wasm_table_proto = 0; 81 + static ant_value_t g_wasm_tag_proto = 0; 82 + static ant_value_t g_wasm_exception_proto = 0; 83 + 84 + static ant_value_t g_wasm_compileerror_proto = 0; 85 + static ant_value_t g_wasm_linkerror_proto = 0; 86 + static ant_value_t g_wasm_runtimeerror_proto = 0; 66 87 88 + static wasm_engine_t *g_wasm_engine = NULL; 67 89 static wasm_import_func_env_t **g_wasm_import_envs = NULL; 68 - static size_t g_wasm_import_env_count = 0; 69 - static size_t g_wasm_import_env_cap = 0; 70 90 71 91 static void wasm_register_import_env(wasm_import_func_env_t *env) { 72 92 if (g_wasm_import_env_count == g_wasm_import_env_cap) { ··· 93 113 free(env); 94 114 } 95 115 96 - static ant_value_t g_wasm_module_proto = 0; 97 - static ant_value_t g_wasm_instance_proto = 0; 98 - static ant_value_t g_wasm_global_proto = 0; 99 - static ant_value_t g_wasm_memory_proto = 0; 100 - static ant_value_t g_wasm_table_proto = 0; 101 - static ant_value_t g_wasm_tag_proto = 0; 102 - static ant_value_t g_wasm_exception_proto = 0; 103 - 104 - static ant_value_t g_wasm_compileerror_proto = 0; 105 - static ant_value_t g_wasm_linkerror_proto = 0; 106 - static ant_value_t g_wasm_runtimeerror_proto = 0; 116 + static ant_value_t wasm_wrap_func( 117 + ant_t *js, wasm_func_t *func, 118 + ant_value_t owner, bool own_func 119 + ); 107 120 108 121 static bool ensure_wasm_engine(void) { 109 122 if (g_wasm_engine) return true; ··· 118 131 } 119 132 120 133 static const char *wasm_extern_kind_name(wasm_externkind_t kind) { 121 - switch (kind) { 122 - case WASM_EXTERN_FUNC: return "function"; 123 - case WASM_EXTERN_GLOBAL: return "global"; 124 - case WASM_EXTERN_TABLE: return "table"; 125 - case WASM_EXTERN_MEMORY: return "memory"; 126 - default: return "unknown"; 127 - } 128 - } 134 + switch (kind) { 135 + case WASM_EXTERN_FUNC: return "function"; 136 + case WASM_EXTERN_GLOBAL: return "global"; 137 + case WASM_EXTERN_TABLE: return "table"; 138 + case WASM_EXTERN_MEMORY: return "memory"; 139 + default: return "unknown"; 140 + }} 129 141 130 142 static wasm_valkind_t wasm_valkind_from_string(const char *name, size_t len, bool *ok) { 131 143 *ok = true; ··· 230 242 case WASM_F32: return js_mknum((double)value->of.f32); 231 243 case WASM_F64: return js_mknum(value->of.f64); 232 244 case WASM_EXTERNREF: 233 - case WASM_FUNCREF: 234 245 return value->of.ref ? js_mkundef() : js_mknull(); 235 - default: 236 - return js_mkundef(); 246 + case WASM_FUNCREF: { 247 + wasm_func_t *func; 248 + if (!value->of.ref) return js_mknull(); 249 + func = wasm_ref_as_func(value->of.ref); 250 + if (!func) return js_mkundef(); 251 + return wasm_wrap_func(js, func, js_mkundef(), true); 252 + } 253 + default: return js_mkundef(); 237 254 } 238 255 } 239 256 ··· 273 290 out->of.f64 = js_to_number(js, value); 274 291 return true; 275 292 case WASM_EXTERNREF: 276 - case WASM_FUNCREF: 277 293 out->of.ref = NULL; 278 294 return vtype(value) == T_NULL || vtype(value) == T_UNDEF; 279 - default: 280 - return false; 295 + case WASM_FUNCREF: { 296 + ant_value_t state; 297 + wasm_func_handle_t *handle; 298 + if (vtype(value) == T_NULL || vtype(value) == T_UNDEF) { 299 + out->of.ref = NULL; 300 + return true; 301 + } 302 + if (!is_callable(value)) return false; 303 + state = js_get_slot(value, SLOT_DATA); 304 + if (!is_object_type(state) || !js_check_native_tag(state, WASM_FUNC_STATE_TAG)) 305 + return false; 306 + handle = (wasm_func_handle_t *)js_get_native_ptr(state); 307 + if (!handle || !handle->func) return false; 308 + out->of.ref = wasm_func_as_ref(handle->func); 309 + return out->of.ref != NULL; 310 + } 311 + default: return false; 281 312 } 282 313 } 283 314 ··· 420 451 421 452 static ant_value_t js_wasm_exported_func_call(ant_t *js, ant_value_t *args, int nargs) { 422 453 ant_value_t state = js_get_slot(js->current_func, SLOT_DATA); 423 - if (vtype(state) != T_OBJ) return js_mkerr(js, "Invalid WebAssembly function"); 454 + wasm_func_handle_t *handle; 455 + wasm_func_t *func; 424 456 425 - ant_value_t func_ptr = js_get_slot(state, SLOT_DATA); 426 - if (vtype(func_ptr) != T_NUM) return js_mkerr(js, "Invalid WebAssembly function"); 457 + if (!is_object_type(state) || !js_check_native_tag(state, WASM_FUNC_STATE_TAG)) 458 + return js_mkerr(js, "Invalid WebAssembly function"); 427 459 428 - wasm_func_t *func = (wasm_func_t *)(uintptr_t)(size_t)js_getnum(func_ptr); 460 + handle = (wasm_func_handle_t *)js_get_native_ptr(state); 461 + func = handle ? handle->func : NULL; 429 462 if (!func) return js_mkerr(js, "Invalid WebAssembly function"); 430 463 431 464 wasm_functype_t *type = wasm_func_type(func); ··· 475 508 return result; 476 509 } 477 510 511 + static void wasm_func_state_finalize(ant_t *js, ant_object_t *obj) { 512 + if (obj->native.tag != WASM_FUNC_STATE_TAG) return; 513 + 514 + wasm_func_handle_t *handle = (wasm_func_handle_t *)obj->native.ptr; 515 + if (!handle) return; 516 + 517 + if (handle->own_func && handle->func) wasm_func_delete(handle->func); 518 + free(handle); 519 + obj->native.ptr = NULL; 520 + obj->native.tag = 0; 521 + } 522 + 523 + static ant_value_t wasm_wrap_func(ant_t *js, wasm_func_t *func, ant_value_t owner, bool own_func) { 524 + wasm_func_handle_t *handle; 525 + ant_value_t state; 526 + 527 + if (!func) return js_mkundef(); 528 + 529 + handle = calloc(1, sizeof(*handle)); 530 + if (!handle) { 531 + if (own_func) wasm_func_delete(func); 532 + return js_mkerr(js, "out of memory"); 533 + } 534 + 535 + handle->func = func; 536 + handle->own_func = own_func; 537 + 538 + state = js_mkobj(js); 539 + js_set_native_ptr(state, handle); 540 + js_set_native_tag(state, WASM_FUNC_STATE_TAG); 541 + 542 + if (is_object_type(owner)) js_set_slot_wb(js, state, SLOT_ENTRIES, owner); 543 + js_set_finalizer(state, wasm_func_state_finalize); 544 + 545 + return js_heavy_mkfun(js, js_wasm_exported_func_call, state); 546 + } 547 + 478 548 static ant_value_t wasm_wrap_export_value(ant_t *js, ant_value_t instance_obj, const wasm_exporttype_t *export_type, wasm_extern_t *external) { 479 549 switch (wasm_extern_kind(external)) { 480 550 case WASM_EXTERN_FUNC: { 481 - ant_value_t state = js_mkobj(js); 482 - js_set_slot(state, SLOT_DATA, ANT_PTR(wasm_extern_as_func(external))); 483 - js_set_slot_wb(js, state, SLOT_ENTRIES, instance_obj); 484 - return js_heavy_mkfun(js, js_wasm_exported_func_call, state); 551 + return wasm_wrap_func(js, wasm_extern_as_func(external), instance_obj, false); 485 552 } 486 553 case WASM_EXTERN_GLOBAL: { 487 554 ant_value_t obj = wasm_wrap_extern_object( ··· 1064 1131 static ant_value_t js_wasm_table_get(ant_t *js, ant_value_t *args, int nargs) { 1065 1132 wasm_extern_handle_t *handle = wasm_extern_handle(js->this_val, WASM_EXTERN_WRAP_TABLE); 1066 1133 wasm_ref_t *ref; 1134 + wasm_func_t *func; 1067 1135 uint32_t index; 1068 1136 1069 1137 if (!handle || !handle->as.table) ··· 1071 1139 1072 1140 index = (uint32_t)(nargs > 0 ? js_to_number(js, args[0]) : 0); 1073 1141 ref = wasm_table_get(handle->as.table, index); 1142 + 1143 + if (!ref) return js_mknull(); 1144 + func = wasm_ref_as_func(ref); 1074 1145 1075 - if (!ref) return js_mknull(); 1146 + if (func) { 1147 + ant_value_t owner = js_get_slot(js->this_val, SLOT_ENTRIES); 1148 + ant_value_t wrapped = wasm_wrap_func(js, func, owner, true); 1149 + wasm_ref_delete(ref); 1150 + return wrapped; 1151 + } 1152 + 1076 1153 wasm_ref_delete(ref); 1077 - 1078 1154 return js_mknull(); 1079 1155 } 1080 1156 1081 1157 static ant_value_t js_wasm_table_set(ant_t *js, ant_value_t *args, int nargs) { 1082 1158 wasm_extern_handle_t *handle = wasm_extern_handle(js->this_val, WASM_EXTERN_WRAP_TABLE); 1159 + wasm_ref_t *ref = NULL; 1083 1160 uint32_t index; 1084 1161 1085 1162 if (!handle || !handle->as.table) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected a WebAssembly.Table"); 1086 1163 if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "WebAssembly.Table.set requires 2 arguments"); 1087 1164 1088 1165 index = (uint32_t)js_to_number(js, args[0]); 1089 - if (!wasm_table_set(handle->as.table, index, NULL)) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to update WebAssembly.Table"); 1166 + if (!(vtype(args[1]) == T_NULL || vtype(args[1]) == T_UNDEF)) { 1167 + ant_value_t state; 1168 + wasm_func_handle_t *func_handle; 1169 + 1170 + if (!is_callable(args[1])) 1171 + return js_mkerr_typed(js, JS_ERR_TYPE, "WebAssembly.Table.set expects a WebAssembly function or null"); 1172 + 1173 + state = js_get_slot(args[1], SLOT_DATA); 1174 + if (!is_object_type(state) || !js_check_native_tag(state, WASM_FUNC_STATE_TAG)) 1175 + return js_mkerr_typed(js, JS_ERR_TYPE, "WebAssembly.Table.set expects a WebAssembly function or null"); 1176 + 1177 + func_handle = (wasm_func_handle_t *)js_get_native_ptr(state); 1178 + if (!func_handle || !func_handle->func) 1179 + return js_mkerr_typed(js, JS_ERR_TYPE, "WebAssembly.Table.set expects a WebAssembly function or null"); 1180 + 1181 + ref = wasm_func_as_ref(func_handle->func); 1182 + if (!ref) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to materialize WebAssembly function reference"); 1183 + } 1184 + 1185 + if (!wasm_table_set(handle->as.table, index, ref)) { 1186 + if (ref) wasm_ref_delete(ref); 1187 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to update WebAssembly.Table"); 1188 + } 1090 1189 1091 1190 return js_mkundef(); 1092 1191 }
+20 -17
tests/test_wasm_webassembly_api.mjs
··· 12 12 13 13 assert(api && typeof api === 'object', 'globalThis.WebAssembly should exist'); 14 14 15 - for (const name of [ 16 - 'Global', 17 - 'Instance', 18 - 'Memory', 19 - 'Module', 20 - 'Table', 21 - 'Tag', 22 - 'Exception', 23 - 'CompileError', 24 - 'LinkError', 25 - 'RuntimeError', 26 - ]) { 15 + for (const name of ['Global', 'Instance', 'Memory', 'Module', 'Table', 'Tag', 'Exception', 'CompileError', 'LinkError', 'RuntimeError']) { 27 16 assert(typeof api[name] === 'function', `WebAssembly.${name} should exist`); 28 17 } 29 18 30 19 const incrementer = fixture('wpt/wasm/webapi/resources/incrementer.wasm'); 20 + const tableExportFixture = new Uint8Array([ 21 + 0, 97, 115, 109, 1, 0, 0, 0, 1, 9, 2, 96, 0, 1, 127, 96, 0, 1, 127, 3, 3, 2, 0, 1, 4, 4, 1, 112, 0, 2, 7, 24, 3, 5, 115, 101, 118, 101, 110, 0, 0, 22 + 4, 110, 105, 110, 101, 0, 1, 5, 116, 97, 98, 108, 101, 1, 0, 9, 8, 1, 0, 65, 0, 11, 2, 0, 1, 10, 11, 2, 4, 0, 65, 7, 11, 4, 0, 65, 9, 11, 0, 21, 4, 23 + 110, 97, 109, 101, 1, 14, 2, 0, 5, 115, 101, 118, 101, 110, 1, 4, 110, 105, 110, 101 24 + ]); 31 25 32 26 assert(WebAssembly.validate(incrementer) === true, 'incrementer.wasm should validate'); 33 27 ··· 36 30 assert(Array.isArray(descriptors), 'WebAssembly.Module.exports() should return an array'); 37 31 assert( 38 32 descriptors.some(entry => entry && entry.name === 'increment' && entry.kind === 'function'), 39 - 'incrementer.wasm should export increment()', 33 + 'incrementer.wasm should export increment()' 40 34 ); 41 35 42 36 const instance = new WebAssembly.Instance(module); 43 37 assert(typeof instance.exports.increment === 'function', 'instance.exports.increment should exist'); 44 38 assert(instance.exports.increment(41) === 42, 'increment(41) should return 42'); 39 + 40 + const tableModule = new WebAssembly.Module(tableExportFixture); 41 + const tableInstance = new WebAssembly.Instance(tableModule); 42 + assert(tableInstance.exports.table.length === 2, 'exported table should expose its length'); 43 + const firstTableEntry = tableInstance.exports.table.get(0); 44 + const secondTableEntry = tableInstance.exports.table.get(1); 45 + assert(typeof firstTableEntry === 'function', 'table.get(0) should return a callable function'); 46 + assert(typeof secondTableEntry === 'function', 'table.get(1) should return a callable function'); 47 + assert(firstTableEntry() === 7, 'table.get(0) should invoke the first table function'); 48 + assert(secondTableEntry() === 9, 'table.get(1) should invoke the second table function'); 49 + tableInstance.exports.table.set(0, tableInstance.exports.nine); 50 + assert(tableInstance.exports.table.get(0)() === 9, 'table.set() should accept wasm-exported functions'); 45 51 46 52 const compiled = await WebAssembly.compile(incrementer); 47 53 assert(compiled instanceof WebAssembly.Module, 'WebAssembly.compile() should resolve a module'); ··· 65 71 new WebAssembly.Module(invalid); 66 72 } catch (error) { 67 73 sawCompileError = true; 68 - assert( 69 - error instanceof WebAssembly.CompileError || error?.name === 'CompileError', 70 - 'invalid module should throw CompileError', 71 - ); 74 + assert(error instanceof WebAssembly.CompileError || error?.name === 'CompileError', 'invalid module should throw CompileError'); 72 75 } 73 76 assert(sawCompileError, 'invalid module construction should fail'); 74 77