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.

migrate Array.prototype.includes into dedicated opcode

900% increase in throughput

+291 -9
+4
include/internal.h
··· 469 469 ant_value_t builtin_object_isPrototypeOf(ant_t *js, ant_value_t *args, int nargs); 470 470 ant_value_t builtin_object_freeze(ant_t *js, ant_value_t *args, int nargs); 471 471 472 + bool js_is_array_includes_builtin(ant_value_t func); 473 + ant_value_t js_array_includes_call(ant_t *js, ant_value_t this_val, ant_value_t *args, int nargs); 474 + ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs); 475 + 472 476 void js_module_eval_ctx_push(ant_t *js, ant_module_t *ctx); 473 477 void js_module_eval_ctx_pop(ant_t *js, ant_module_t *ctx); 474 478
+1
include/object.h
··· 87 87 uint8_t is_constructor: 1; 88 88 uint8_t fast_array: 1; 89 89 uint8_t may_have_holes: 1; 90 + uint8_t may_have_dense_elements: 1; 90 91 uint8_t gc_permanent: 1; 91 92 uint8_t generation: 1; 92 93 uint8_t in_remember_set: 1;
+6
include/silver/glue.h
··· 75 75 ant_value_t *out_this 76 76 ); 77 77 78 + ant_value_t jit_helper_call_array_includes( 79 + sv_vm_t *vm, ant_t *js, 80 + ant_value_t call_func, ant_value_t call_this, 81 + ant_value_t *args, int argc 82 + ); 83 + 78 84 ant_value_t jit_helper_apply( 79 85 sv_vm_t *vm, ant_t *js, 80 86 ant_value_t func, ant_value_t this_val,
+1
include/silver/opcode.h
··· 162 162 OP_DEF( CALL, 3, 1, 1, npop) /* func args... -> result */ 163 163 OP_DEF( CALL_METHOD, 3, 2, 1, npop) /* this func args... -> result */ 164 164 OP_DEF( CALL_IS_PROTO, 3, 3, 1, u16) /* this func arg -> bool (ic_idx:u16) */ 165 + OP_DEF( CALL_ARRAY_INCLUDES, 3, 2, 1, npop) /* this func args... -> bool */ 165 166 OP_DEF( RE_EXEC_TRUTHY, 1, 3, 1, none) /* this func arg -> bool */ 166 167 OP_DEF( TAIL_CALL, 3, 1, 0, npop) /* tail-position call */ 167 168 OP_DEF( TAIL_CALL_METHOD, 3, 2, 0, npop)
+125 -7
src/ant.c
··· 912 912 return ptr ? ptr->may_have_holes : true; 913 913 } 914 914 915 + static inline bool array_may_have_dense_elements(ant_value_t obj) { 916 + ant_object_t *ptr = array_obj_ptr(obj); 917 + return ptr ? ptr->may_have_dense_elements : true; 918 + } 919 + 915 920 static inline void array_mark_may_have_holes(ant_value_t obj) { 916 921 ant_object_t *ptr = array_obj_ptr(obj); 917 922 if (ptr) ptr->may_have_holes = 1; ··· 2653 2658 ant_object_t *ptr = dense_obj(doff); 2654 2659 if (!ptr || idx >= ptr->u.array.cap) return; 2655 2660 ptr->u.array.data[idx] = val; 2661 + if (!is_empty_slot(val)) ptr->may_have_dense_elements = 1; 2656 2662 gc_write_barrier(js, ptr, val); 2657 2663 } 2658 2664 ··· 3020 3026 for (uint32_t i = 0; i < obj->u.array.cap; i++) obj->u.array.data[i] = T_EMPTY; 3021 3027 obj->fast_array = 1; 3022 3028 obj->may_have_holes = 0; 3029 + obj->may_have_dense_elements = 0; 3023 3030 } else { 3024 3031 obj->u.array.cap = 0; 3025 3032 obj->u.array.len = 0; 3026 3033 obj->fast_array = 0; 3027 3034 obj->may_have_holes = 1; 3035 + obj->may_have_dense_elements = 1; 3028 3036 } 3029 3037 3030 3038 return arr; ··· 8478 8486 return js_getprop_super(js, get_proto(js, arr), arr, idxstr); 8479 8487 } 8480 8488 8489 + static bool array_includes_get_dense_index_value( 8490 + ant_value_t obj, ant_offset_t idx, ant_value_t *out 8491 + ) { 8492 + ant_value_t arr = (vtype(obj) == T_FUNC) ? js_func_obj(obj) : obj; 8493 + if (vtype(arr) != T_ARR) return false; 8494 + 8495 + ant_offset_t doff = get_dense_buf(arr); 8496 + if (!doff || idx >= dense_capacity(doff)) return false; 8497 + 8498 + ant_value_t val = dense_get(doff, idx); 8499 + if (is_empty_slot(val)) return false; 8500 + 8501 + *out = val; 8502 + return true; 8503 + } 8504 + 8505 + static bool array_includes_get_proto_dense_index_value( 8506 + ant_t *js, ant_value_t arr, ant_offset_t idx, ant_value_t *out 8507 + ) { 8508 + ant_value_t proto = get_proto(js, arr); 8509 + for (int depth = 0; is_object_type(proto) && depth < MAX_PROTO_CHAIN_DEPTH; depth++) { 8510 + if (array_includes_get_dense_index_value(proto, idx, out)) return true; 8511 + proto = get_proto(js, proto); 8512 + } 8513 + 8514 + return false; 8515 + } 8516 + 8481 8517 static ant_value_t array_includes_get_array_index_value( 8482 - ant_t *js, ant_value_t arr, ant_offset_t 8483 - idx, char *idxstr, size_t idxlen 8518 + ant_t *js, ant_value_t arr, ant_offset_t idx, char *idxstr, size_t idxlen 8484 8519 ) { 8485 8520 if (arr_has(js, arr, idx)) return arr_get(js, arr, idx); 8521 + ant_value_t proto_dense_val = js_mkundef(); 8522 + if (array_includes_get_proto_dense_index_value(js, arr, idx, &proto_dense_val)) 8523 + return proto_dense_val; 8486 8524 if (lkp_proto(js, arr, idxstr, idxlen) == 0) return js_mkundef(); 8487 8525 idxstr[idxlen] = '\0'; 8488 8526 return js_getprop_super(js, get_proto(js, arr), arr, idxstr); 8489 8527 } 8490 8528 8529 + static bool array_includes_object_may_have_indexed_props( 8530 + ant_t *js, ant_value_t obj, bool include_dense 8531 + ) { 8532 + if (!is_object_type(obj) || is_proxy(obj)) return is_proxy(obj); 8533 + 8534 + ant_value_t as_obj = (vtype(obj) == T_FUNC) ? js_func_obj(obj) : obj; 8535 + ant_object_t *ptr = js_obj_ptr(as_obj); 8536 + if (!ptr) return false; 8537 + if (ptr->is_exotic) return true; 8538 + 8539 + if (include_dense && vtype(as_obj) == T_ARR) { 8540 + ant_offset_t doff = get_dense_buf(as_obj); 8541 + if (doff) { 8542 + ant_offset_t dense_len = dense_capacity(doff); 8543 + for (ant_offset_t i = 0; i < dense_len; i++) 8544 + if (!is_empty_slot(dense_get(doff, i))) return true; 8545 + } 8546 + } 8547 + 8548 + if (!ptr->shape) return false; 8549 + uint32_t shape_count = ant_shape_count(ptr->shape); 8550 + for (uint32_t i = 0; i < shape_count; i++) { 8551 + const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i); 8552 + if (!prop || prop->type == ANT_SHAPE_KEY_SYMBOL) continue; 8553 + if (i >= ptr->prop_count) continue; 8554 + 8555 + const char *key = prop->key.interned; 8556 + size_t key_len = strlen(key); 8557 + if (is_array_index(key, (ant_offset_t)key_len)) return true; 8558 + } 8559 + 8560 + return false; 8561 + } 8562 + 8563 + static bool array_includes_can_skip_hole_lookups(ant_t *js, ant_value_t arr) { 8564 + if (array_includes_object_may_have_indexed_props(js, arr, false)) return false; 8565 + 8566 + ant_value_t proto = get_proto(js, arr); 8567 + for (int depth = 0; is_object_type(proto) && depth < MAX_PROTO_CHAIN_DEPTH; depth++) { 8568 + if (array_includes_object_may_have_indexed_props(js, proto, true)) return false; 8569 + proto = get_proto(js, proto); 8570 + } 8571 + 8572 + return true; 8573 + } 8574 + 8491 8575 static ant_value_t array_includes_dense_fast( 8492 8576 ant_t *js, ant_value_t arr, const array_includes_query_t *query, ant_offset_t len, ant_offset_t start 8493 8577 ) { ··· 8520 8604 static ant_value_t array_includes_array_slow( 8521 8605 ant_t *js, ant_value_t arr, const array_includes_query_t *query, ant_offset_t len, ant_offset_t start 8522 8606 ) { 8607 + bool skip_hole_lookups = 8608 + query->search_type != T_UNDEF && 8609 + array_includes_can_skip_hole_lookups(js, arr); 8610 + 8611 + ant_value_t *dense = NULL; 8612 + ant_offset_t dense_len = 0; 8613 + 8614 + if (skip_hole_lookups) { 8615 + if (!array_may_have_dense_elements(arr)) return mkval(T_BOOL, 0); 8616 + 8617 + ant_offset_t doff = get_dense_buf(arr); 8618 + if (doff) { 8619 + dense = dense_data(doff); 8620 + dense_len = dense_iterable_length(js, arr); 8621 + } 8622 + 8623 + if (dense) for (ant_offset_t i = start; i < dense_len; i++) { 8624 + ant_value_t val = dense[i]; 8625 + if (is_empty_slot(val)) continue; 8626 + if (array_includes_matches(js, query, val)) return mkval(T_BOOL, 1); 8627 + } 8628 + 8629 + return mkval(T_BOOL, 0); 8630 + } 8631 + 8523 8632 for (ant_offset_t i = start; i < len; i++) { 8633 + ant_value_t val; 8634 + 8524 8635 char idxstr[16]; 8525 8636 size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i); 8526 - 8527 - ant_value_t val = array_includes_get_array_index_value(js, arr, i, idxstr, idxlen); 8637 + 8638 + val = array_includes_get_array_index_value(js, arr, i, idxstr, idxlen); 8528 8639 if (is_err(val)) return val; 8640 + 8529 8641 if (array_includes_matches(js, query, val)) return mkval(T_BOOL, 1); 8530 8642 } 8531 8643 ··· 8556 8668 return mkval(T_BOOL, 0); 8557 8669 } 8558 8670 8559 - static ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs) { 8560 - ant_value_t arr = js->this_val; 8561 - 8671 + ant_value_t js_array_includes_call(ant_t *js, ant_value_t arr, ant_value_t *args, int nargs) { 8562 8672 if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) 8563 8673 return js_mkerr(js, "includes called on non-array"); 8564 8674 ··· 8580 8690 } 8581 8691 8582 8692 return array_includes_generic(js, arr, &query, args, nargs); 8693 + } 8694 + 8695 + bool js_is_array_includes_builtin(ant_value_t func) { 8696 + return vtype(func) == T_CFUNC && js_cfunc_same_entrypoint(func, builtin_array_includes); 8697 + } 8698 + 8699 + ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs) { 8700 + return js_array_includes_call(js, js->this_val, args, nargs); 8583 8701 } 8584 8702 8585 8703 static ant_value_t builtin_array_every(ant_t *js, ant_value_t *args, int nargs) {
+25
src/silver/compiler.c
··· 2215 2215 return true; 2216 2216 } 2217 2217 2218 + static bool compile_call_array_includes_intrinsic( 2219 + sv_compiler_t *c, sv_ast_t *node, bool has_spread 2220 + ) { 2221 + if (!node || has_spread || node->args.count > UINT16_MAX) return false; 2222 + sv_ast_t *callee = node->left; 2223 + 2224 + if (!callee || callee->type != N_MEMBER) return false; 2225 + if ((callee->flags & 1) || !callee->right || !callee->right->str) return false; 2226 + if (is_ident_name(callee->left, "super")) return false; 2227 + if (!is_ident_str(callee->right->str, callee->right->len, "includes", 8)) 2228 + return false; 2229 + 2230 + compile_expr(c, callee->left); 2231 + compile_receiver_property_get(c, callee); 2232 + for (int i = 0; i < node->args.count; i++) 2233 + compile_expr(c, node->args.items[i]); 2234 + emit_op(c, OP_CALL_ARRAY_INCLUDES); 2235 + emit_u16(c, (uint16_t)node->args.count); 2236 + 2237 + return true; 2238 + } 2239 + 2218 2240 static bool compile_regexp_exec_truthy_intrinsic( 2219 2241 sv_compiler_t *c, sv_ast_t *node 2220 2242 ) { ··· 2310 2332 } 2311 2333 2312 2334 if (compile_call_is_proto_intrinsic(c, node, has_spread)) 2335 + return; 2336 + 2337 + if (compile_call_array_includes_intrinsic(c, node, has_spread)) 2313 2338 return; 2314 2339 2315 2340 if (
+20
src/silver/engine.c
··· 1408 1408 NEXT(3); 1409 1409 } 1410 1410 1411 + L_CALL_ARRAY_INCLUDES: { 1412 + uint16_t call_argc = sv_get_u16(ip + 1); 1413 + ant_value_t *call_args = &vm->stack[vm->sp - call_argc]; 1414 + ant_value_t call_func = vm->stack[vm->sp - call_argc - 1]; 1415 + ant_value_t call_this = vm->stack[vm->sp - call_argc - 2]; 1416 + ant_value_t call_result; 1417 + 1418 + frame->ip = ip; 1419 + if (js_is_array_includes_builtin(call_func)) { 1420 + call_result = js_array_includes_call(js, call_this, call_args, call_argc); 1421 + } else call_result = sv_vm_call(vm, js, call_func, call_this, call_args, call_argc, NULL, false); 1422 + sv_sync_frame_locals(vm, &frame, &func, &bp, &lp); 1423 + 1424 + vm->sp -= call_argc + 2; 1425 + if (is_err(call_result)) { sv_err = call_result; goto sv_throw; } 1426 + vm->stack[vm->sp++] = call_result; 1427 + 1428 + NEXT(3); 1429 + } 1430 + 1411 1431 L_CALL_IS_PROTO: { 1412 1432 ant_value_t call_arg = vm->stack[vm->sp - 1]; 1413 1433 ant_value_t call_func = vm->stack[vm->sp - 2];
+10
src/silver/glue.c
··· 534 534 return sv_vm_call(vm, js, call_func, call_this, args, 1, NULL, false); 535 535 } 536 536 537 + ant_value_t jit_helper_call_array_includes( 538 + sv_vm_t *vm, ant_t *js, 539 + ant_value_t call_func, ant_value_t call_this, 540 + ant_value_t *args, int argc 541 + ) { 542 + if (js_is_array_includes_builtin(call_func)) 543 + return js_array_includes_call(js, call_this, args, argc); 544 + return sv_vm_call(vm, js, call_func, call_this, args, argc, NULL, false); 545 + } 546 + 537 547 // TODO: dont bail out 538 548 ant_value_t jit_helper_typeof(sv_vm_t *vm, ant_t *js, ant_value_t v) { 539 549 return SV_JIT_BAILOUT;
+74 -2
src/silver/swarm.c
··· 60 60 LOAD_EXT(jit_helper_ge); 61 61 LOAD_EXT(jit_helper_call); 62 62 LOAD_EXT(jit_helper_call_method); 63 + LOAD_EXT(jit_helper_call_array_includes); 63 64 LOAD_EXT(jit_helper_apply); 64 65 LOAD_EXT(jit_helper_rest); 65 66 LOAD_EXT(jit_helper_special_obj); ··· 1919 1920 case OP_POST_INC: 1920 1921 f.needs_inc_local = true; 1921 1922 break; 1922 - case OP_CALL: case OP_CALL_METHOD: 1923 + case OP_CALL: case OP_CALL_METHOD: case OP_CALL_ARRAY_INCLUDES: 1923 1924 case OP_TAIL_CALL: case OP_TAIL_CALL_METHOD: 1924 1925 case OP_ARRAY: case OP_NEW: 1925 1926 case OP_APPLY: case OP_NEW_APPLY: ··· 1991 1992 case OP_JMP_TRUE: case OP_JMP_TRUE8: 1992 1993 case OP_JMP_FALSE_PEEK: case OP_JMP_TRUE_PEEK: 1993 1994 case OP_CALL: case OP_CALL_METHOD: 1994 - case OP_CALL_IS_PROTO: 1995 + case OP_CALL_IS_PROTO: case OP_CALL_ARRAY_INCLUDES: 1995 1996 case OP_TAIL_CALL: case OP_TAIL_CALL_METHOD: 1996 1997 case OP_APPLY: case OP_NEW_APPLY: 1997 1998 case OP_GET_GLOBAL: case OP_GET_GLOBAL_UNDEF: ··· 2401 2402 MIR_item_t imp_ge = MIR_new_import(ctx, "jit_helper_ge"); 2402 2403 MIR_item_t imp_call = MIR_new_import(ctx, "jit_helper_call"); 2403 2404 MIR_item_t imp_call_method = MIR_new_import(ctx, "jit_helper_call_method"); 2405 + MIR_item_t imp_call_array_includes = MIR_new_import(ctx, "jit_helper_call_array_includes"); 2404 2406 MIR_item_t imp_apply = MIR_new_import(ctx, "jit_helper_apply"); 2405 2407 MIR_item_t imp_rest = MIR_new_import(ctx, "jit_helper_rest"); 2406 2408 MIR_item_t imp_special_obj = MIR_new_import(ctx, "jit_helper_special_obj"); ··· 7521 7523 } else { 7522 7524 MIR_append_insn(ctx, jit_func, 7523 7525 MIR_new_ret_insn(ctx, 1, MIR_new_reg_op(ctx, r_new_res))); 7526 + } 7527 + MIR_append_insn(ctx, jit_func, no_err); 7528 + break; 7529 + } 7530 + 7531 + case OP_CALL_ARRAY_INCLUDES: { 7532 + vstack_flush_to_boxed(&vs, ctx, jit_func, r_d_slot); 7533 + uint16_t call_argc = sv_get_u16(ip + 1); 7534 + if (call_argc > 16 || vs.sp < (int)call_argc + 2) { ok = false; break; } 7535 + 7536 + MIR_reg_t r_arg_arr = r_args_buf; 7537 + for (int i = (int)call_argc - 1; i >= 0; i--) { 7538 + MIR_reg_t areg = vstack_pop(&vs); 7539 + MIR_append_insn(ctx, jit_func, 7540 + MIR_new_insn(ctx, MIR_MOV, 7541 + MIR_new_mem_op(ctx, MIR_JSVAL, 7542 + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), 7543 + r_arg_arr, 0, 1), 7544 + MIR_new_reg_op(ctx, areg))); 7545 + } 7546 + 7547 + MIR_reg_t r_call_func = vstack_pop(&vs); 7548 + MIR_reg_t r_call_this = vstack_pop(&vs); 7549 + MIR_reg_t r_call_res = vstack_push(&vs); 7550 + 7551 + MIR_append_insn(ctx, jit_func, 7552 + MIR_new_call_insn(ctx, 9, 7553 + MIR_new_ref_op(ctx, call_proto), 7554 + MIR_new_ref_op(ctx, imp_call_array_includes), 7555 + MIR_new_reg_op(ctx, r_call_res), 7556 + MIR_new_reg_op(ctx, r_vm), 7557 + MIR_new_reg_op(ctx, r_js), 7558 + MIR_new_reg_op(ctx, r_call_func), 7559 + MIR_new_reg_op(ctx, r_call_this), 7560 + MIR_new_reg_op(ctx, r_arg_arr), 7561 + MIR_new_int_op(ctx, (int64_t)call_argc))); 7562 + 7563 + if (has_captures) { 7564 + for (int i = 0; i < n_locals; i++) 7565 + if (captured_locals[i]) 7566 + MIR_append_insn(ctx, jit_func, 7567 + MIR_new_insn(ctx, MIR_MOV, 7568 + MIR_new_reg_op(ctx, local_regs[i]), 7569 + MIR_new_mem_op(ctx, MIR_T_I64, 7570 + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_lbuf, 0, 1))); 7571 + } 7572 + 7573 + MIR_label_t no_err = MIR_new_label(ctx); 7574 + MIR_append_insn(ctx, jit_func, 7575 + MIR_new_insn(ctx, MIR_URSH, 7576 + MIR_new_reg_op(ctx, r_bool), 7577 + MIR_new_reg_op(ctx, r_call_res), 7578 + MIR_new_int_op(ctx, NANBOX_TYPE_SHIFT))); 7579 + MIR_append_insn(ctx, jit_func, 7580 + MIR_new_insn(ctx, MIR_BNE, 7581 + MIR_new_label_op(ctx, no_err), 7582 + MIR_new_reg_op(ctx, r_bool), 7583 + MIR_new_uint_op(ctx, JIT_ERR_TAG))); 7584 + if (jit_try_depth > 0) { 7585 + jit_try_entry_t *h = &jit_try_stack[jit_try_depth - 1]; 7586 + MIR_append_insn(ctx, jit_func, 7587 + MIR_new_insn(ctx, MIR_MOV, 7588 + MIR_new_reg_op(ctx, vs.regs[h->saved_sp]), 7589 + MIR_new_reg_op(ctx, r_call_res))); 7590 + MIR_append_insn(ctx, jit_func, 7591 + MIR_new_insn(ctx, MIR_JMP, 7592 + MIR_new_label_op(ctx, h->catch_label))); 7593 + } else { 7594 + MIR_append_insn(ctx, jit_func, 7595 + MIR_new_ret_insn(ctx, 1, MIR_new_reg_op(ctx, r_call_res))); 7524 7596 } 7525 7597 MIR_append_insn(ctx, jit_func, no_err); 7526 7598 break;
+25
tests/test_array_includes_regression_big.cjs
··· 22 22 grown.length = 3; 23 23 assert.strictEqual(grown.includes(undefined), true); 24 24 25 + try { 26 + Array.prototype[1] = "from-array-proto"; 27 + assert.strictEqual(Array(3).includes("from-array-proto"), true); 28 + assert.strictEqual(Array(3).includes("missing"), false); 29 + } finally { 30 + delete Array.prototype[1]; 31 + } 32 + 33 + const customIncludes = { 34 + includes(value) { 35 + return value === "custom"; 36 + } 37 + }; 38 + assert.strictEqual(customIncludes.includes("custom"), true); 39 + 40 + const originalIncludes = Array.prototype.includes; 41 + try { 42 + Array.prototype.includes = function(value) { 43 + return value === "patched"; 44 + }; 45 + assert.strictEqual([1, 2, 3].includes("patched"), true); 46 + } finally { 47 + Array.prototype.includes = originalIncludes; 48 + } 49 + 25 50 let steps = 0; 26 51 const generic = { 27 52 get length() {