diff --git a/include/internal.h b/include/internal.h index c309222..38b9963 100644 --- a/include/internal.h +++ b/include/internal.h @@ -469,6 +469,10 @@ bool js_is_prototype_of(ant_t *js, ant_value_t proto_obj, ant_value_t obj); ant_value_t builtin_object_isPrototypeOf(ant_t *js, ant_value_t *args, int nargs); ant_value_t builtin_object_freeze(ant_t *js, ant_value_t *args, int nargs); +bool js_is_array_includes_builtin(ant_value_t func); +ant_value_t js_array_includes_call(ant_t *js, ant_value_t this_val, ant_value_t *args, int nargs); +ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs); + void js_module_eval_ctx_push(ant_t *js, ant_module_t *ctx); void js_module_eval_ctx_pop(ant_t *js, ant_module_t *ctx); diff --git a/include/object.h b/include/object.h index add3163..2f32cac 100644 --- a/include/object.h +++ b/include/object.h @@ -87,6 +87,7 @@ typedef struct ant_object { uint8_t is_constructor: 1; uint8_t fast_array: 1; uint8_t may_have_holes: 1; + uint8_t may_have_dense_elements: 1; uint8_t gc_permanent: 1; uint8_t generation: 1; uint8_t in_remember_set: 1; diff --git a/include/silver/glue.h b/include/silver/glue.h index dc93339..68d0e70 100644 --- a/include/silver/glue.h +++ b/include/silver/glue.h @@ -75,6 +75,12 @@ ant_value_t jit_helper_call_method( ant_value_t *out_this ); +ant_value_t jit_helper_call_array_includes( + sv_vm_t *vm, ant_t *js, + ant_value_t call_func, ant_value_t call_this, + ant_value_t *args, int argc +); + ant_value_t jit_helper_apply( sv_vm_t *vm, ant_t *js, ant_value_t func, ant_value_t this_val, diff --git a/include/silver/opcode.h b/include/silver/opcode.h index 6983764..ec74bae 100644 --- a/include/silver/opcode.h +++ b/include/silver/opcode.h @@ -162,6 +162,7 @@ OP_DEF( JMP_TRUE8, 2, 1, 0, label8) /* short conditional */ OP_DEF( CALL, 3, 1, 1, npop) /* func args... -> result */ OP_DEF( CALL_METHOD, 3, 2, 1, npop) /* this func args... -> result */ OP_DEF( CALL_IS_PROTO, 3, 3, 1, u16) /* this func arg -> bool (ic_idx:u16) */ +OP_DEF( CALL_ARRAY_INCLUDES, 3, 2, 1, npop) /* this func args... -> bool */ OP_DEF( RE_EXEC_TRUTHY, 1, 3, 1, none) /* this func arg -> bool */ OP_DEF( TAIL_CALL, 3, 1, 0, npop) /* tail-position call */ OP_DEF( TAIL_CALL_METHOD, 3, 2, 0, npop) diff --git a/src/ant.c b/src/ant.c index 4b1d4a8..12acb30 100644 --- a/src/ant.c +++ b/src/ant.c @@ -912,6 +912,11 @@ static inline bool array_may_have_holes(ant_value_t obj) { return ptr ? ptr->may_have_holes : true; } +static inline bool array_may_have_dense_elements(ant_value_t obj) { + ant_object_t *ptr = array_obj_ptr(obj); + return ptr ? ptr->may_have_dense_elements : true; +} + static inline void array_mark_may_have_holes(ant_value_t obj) { ant_object_t *ptr = array_obj_ptr(obj); if (ptr) ptr->may_have_holes = 1; @@ -2653,6 +2658,7 @@ static inline void dense_set(ant_t *js, ant_offset_t doff, ant_offset_t idx, ant ant_object_t *ptr = dense_obj(doff); if (!ptr || idx >= ptr->u.array.cap) return; ptr->u.array.data[idx] = val; + if (!is_empty_slot(val)) ptr->may_have_dense_elements = 1; gc_write_barrier(js, ptr, val); } @@ -3020,11 +3026,13 @@ static ant_value_t alloc_array_with_proto(ant_t *js, ant_value_t proto) { for (uint32_t i = 0; i < obj->u.array.cap; i++) obj->u.array.data[i] = T_EMPTY; obj->fast_array = 1; obj->may_have_holes = 0; + obj->may_have_dense_elements = 0; } else { obj->u.array.cap = 0; obj->u.array.len = 0; obj->fast_array = 0; obj->may_have_holes = 1; + obj->may_have_dense_elements = 1; } return arr; @@ -8478,16 +8486,92 @@ static ant_value_t array_includes_get_index_value( return js_getprop_super(js, get_proto(js, arr), arr, idxstr); } +static bool array_includes_get_dense_index_value( + ant_value_t obj, ant_offset_t idx, ant_value_t *out +) { + ant_value_t arr = (vtype(obj) == T_FUNC) ? js_func_obj(obj) : obj; + if (vtype(arr) != T_ARR) return false; + + ant_offset_t doff = get_dense_buf(arr); + if (!doff || idx >= dense_capacity(doff)) return false; + + ant_value_t val = dense_get(doff, idx); + if (is_empty_slot(val)) return false; + + *out = val; + return true; +} + +static bool array_includes_get_proto_dense_index_value( + ant_t *js, ant_value_t arr, ant_offset_t idx, ant_value_t *out +) { + ant_value_t proto = get_proto(js, arr); + for (int depth = 0; is_object_type(proto) && depth < MAX_PROTO_CHAIN_DEPTH; depth++) { + if (array_includes_get_dense_index_value(proto, idx, out)) return true; + proto = get_proto(js, proto); + } + + return false; +} + static ant_value_t array_includes_get_array_index_value( - ant_t *js, ant_value_t arr, ant_offset_t - idx, char *idxstr, size_t idxlen + ant_t *js, ant_value_t arr, ant_offset_t idx, char *idxstr, size_t idxlen ) { if (arr_has(js, arr, idx)) return arr_get(js, arr, idx); + ant_value_t proto_dense_val = js_mkundef(); + if (array_includes_get_proto_dense_index_value(js, arr, idx, &proto_dense_val)) + return proto_dense_val; if (lkp_proto(js, arr, idxstr, idxlen) == 0) return js_mkundef(); idxstr[idxlen] = '\0'; return js_getprop_super(js, get_proto(js, arr), arr, idxstr); } +static bool array_includes_object_may_have_indexed_props( + ant_t *js, ant_value_t obj, bool include_dense +) { + if (!is_object_type(obj) || is_proxy(obj)) return is_proxy(obj); + + ant_value_t as_obj = (vtype(obj) == T_FUNC) ? js_func_obj(obj) : obj; + ant_object_t *ptr = js_obj_ptr(as_obj); + if (!ptr) return false; + if (ptr->is_exotic) return true; + + if (include_dense && vtype(as_obj) == T_ARR) { + ant_offset_t doff = get_dense_buf(as_obj); + if (doff) { + ant_offset_t dense_len = dense_capacity(doff); + for (ant_offset_t i = 0; i < dense_len; i++) + if (!is_empty_slot(dense_get(doff, i))) return true; + } + } + + if (!ptr->shape) return false; + uint32_t shape_count = ant_shape_count(ptr->shape); + for (uint32_t i = 0; i < shape_count; i++) { + const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i); + if (!prop || prop->type == ANT_SHAPE_KEY_SYMBOL) continue; + if (i >= ptr->prop_count) continue; + + const char *key = prop->key.interned; + size_t key_len = strlen(key); + if (is_array_index(key, (ant_offset_t)key_len)) return true; + } + + return false; +} + +static bool array_includes_can_skip_hole_lookups(ant_t *js, ant_value_t arr) { + if (array_includes_object_may_have_indexed_props(js, arr, false)) return false; + + ant_value_t proto = get_proto(js, arr); + for (int depth = 0; is_object_type(proto) && depth < MAX_PROTO_CHAIN_DEPTH; depth++) { + if (array_includes_object_may_have_indexed_props(js, proto, true)) return false; + proto = get_proto(js, proto); + } + + return true; +} + static ant_value_t array_includes_dense_fast( ant_t *js, ant_value_t arr, const array_includes_query_t *query, ant_offset_t len, ant_offset_t start ) { @@ -8520,12 +8604,40 @@ static ant_value_t array_includes_dense_fast( static ant_value_t array_includes_array_slow( ant_t *js, ant_value_t arr, const array_includes_query_t *query, ant_offset_t len, ant_offset_t start ) { + bool skip_hole_lookups = + query->search_type != T_UNDEF && + array_includes_can_skip_hole_lookups(js, arr); + + ant_value_t *dense = NULL; + ant_offset_t dense_len = 0; + + if (skip_hole_lookups) { + if (!array_may_have_dense_elements(arr)) return mkval(T_BOOL, 0); + + ant_offset_t doff = get_dense_buf(arr); + if (doff) { + dense = dense_data(doff); + dense_len = dense_iterable_length(js, arr); + } + + if (dense) for (ant_offset_t i = start; i < dense_len; i++) { + ant_value_t val = dense[i]; + if (is_empty_slot(val)) continue; + if (array_includes_matches(js, query, val)) return mkval(T_BOOL, 1); + } + + return mkval(T_BOOL, 0); + } + for (ant_offset_t i = start; i < len; i++) { + ant_value_t val; + char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i); - - ant_value_t val = array_includes_get_array_index_value(js, arr, i, idxstr, idxlen); + + val = array_includes_get_array_index_value(js, arr, i, idxstr, idxlen); if (is_err(val)) return val; + if (array_includes_matches(js, query, val)) return mkval(T_BOOL, 1); } @@ -8556,9 +8668,7 @@ static ant_value_t array_includes_generic( return mkval(T_BOOL, 0); } -static ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs) { - ant_value_t arr = js->this_val; - +ant_value_t js_array_includes_call(ant_t *js, ant_value_t arr, ant_value_t *args, int nargs) { if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) return js_mkerr(js, "includes called on non-array"); @@ -8582,6 +8692,14 @@ static ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int narg return array_includes_generic(js, arr, &query, args, nargs); } +bool js_is_array_includes_builtin(ant_value_t func) { + return vtype(func) == T_CFUNC && js_cfunc_same_entrypoint(func, builtin_array_includes); +} + +ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs) { + return js_array_includes_call(js, js->this_val, args, nargs); +} + static ant_value_t builtin_array_every(ant_t *js, ant_value_t *args, int nargs) { ant_value_t arr = js->this_val; diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 18ffef5..555e72a 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -2215,6 +2215,28 @@ static bool compile_call_is_proto_intrinsic( return true; } +static bool compile_call_array_includes_intrinsic( + sv_compiler_t *c, sv_ast_t *node, bool has_spread +) { + if (!node || has_spread || node->args.count > UINT16_MAX) return false; + sv_ast_t *callee = node->left; + + if (!callee || callee->type != N_MEMBER) return false; + if ((callee->flags & 1) || !callee->right || !callee->right->str) return false; + if (is_ident_name(callee->left, "super")) return false; + if (!is_ident_str(callee->right->str, callee->right->len, "includes", 8)) + return false; + + compile_expr(c, callee->left); + compile_receiver_property_get(c, callee); + for (int i = 0; i < node->args.count; i++) + compile_expr(c, node->args.items[i]); + emit_op(c, OP_CALL_ARRAY_INCLUDES); + emit_u16(c, (uint16_t)node->args.count); + + return true; +} + static bool compile_regexp_exec_truthy_intrinsic( sv_compiler_t *c, sv_ast_t *node ) { @@ -2312,6 +2334,9 @@ void compile_call(sv_compiler_t *c, sv_ast_t *node) { if (compile_call_is_proto_intrinsic(c, node, has_spread)) return; + if (compile_call_array_includes_intrinsic(c, node, has_spread)) + return; + if ( !has_spread && node->args.count >= 2 && callee->type == N_MEMBER && diff --git a/src/silver/engine.c b/src/silver/engine.c index 275cfeb..9008014 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -1408,6 +1408,26 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant NEXT(3); } + L_CALL_ARRAY_INCLUDES: { + uint16_t call_argc = sv_get_u16(ip + 1); + ant_value_t *call_args = &vm->stack[vm->sp - call_argc]; + ant_value_t call_func = vm->stack[vm->sp - call_argc - 1]; + ant_value_t call_this = vm->stack[vm->sp - call_argc - 2]; + ant_value_t call_result; + + frame->ip = ip; + if (js_is_array_includes_builtin(call_func)) { + call_result = js_array_includes_call(js, call_this, call_args, call_argc); + } else call_result = sv_vm_call(vm, js, call_func, call_this, call_args, call_argc, NULL, false); + sv_sync_frame_locals(vm, &frame, &func, &bp, &lp); + + vm->sp -= call_argc + 2; + if (is_err(call_result)) { sv_err = call_result; goto sv_throw; } + vm->stack[vm->sp++] = call_result; + + NEXT(3); + } + L_CALL_IS_PROTO: { ant_value_t call_arg = vm->stack[vm->sp - 1]; ant_value_t call_func = vm->stack[vm->sp - 2]; diff --git a/src/silver/glue.c b/src/silver/glue.c index afdaca1..171189a 100644 --- a/src/silver/glue.c +++ b/src/silver/glue.c @@ -534,6 +534,16 @@ ant_value_t jit_helper_call_is_proto( return sv_vm_call(vm, js, call_func, call_this, args, 1, NULL, false); } +ant_value_t jit_helper_call_array_includes( + sv_vm_t *vm, ant_t *js, + ant_value_t call_func, ant_value_t call_this, + ant_value_t *args, int argc +) { + if (js_is_array_includes_builtin(call_func)) + return js_array_includes_call(js, call_this, args, argc); + return sv_vm_call(vm, js, call_func, call_this, args, argc, NULL, false); +} + // TODO: dont bail out ant_value_t jit_helper_typeof(sv_vm_t *vm, ant_t *js, ant_value_t v) { return SV_JIT_BAILOUT; diff --git a/src/silver/swarm.c b/src/silver/swarm.c index a52cfef..f2d6cbd 100644 --- a/src/silver/swarm.c +++ b/src/silver/swarm.c @@ -60,6 +60,7 @@ static void jit_load_externals_once(sv_jit_ctx_t *jc) { LOAD_EXT(jit_helper_ge); LOAD_EXT(jit_helper_call); LOAD_EXT(jit_helper_call_method); + LOAD_EXT(jit_helper_call_array_includes); LOAD_EXT(jit_helper_apply); LOAD_EXT(jit_helper_rest); LOAD_EXT(jit_helper_special_obj); @@ -1919,7 +1920,7 @@ static jit_features_t jit_prescan_features(sv_func_t *func) { case OP_POST_INC: f.needs_inc_local = true; break; - case OP_CALL: case OP_CALL_METHOD: + case OP_CALL: case OP_CALL_METHOD: case OP_CALL_ARRAY_INCLUDES: case OP_TAIL_CALL: case OP_TAIL_CALL_METHOD: case OP_ARRAY: case OP_NEW: case OP_APPLY: case OP_NEW_APPLY: @@ -1991,7 +1992,7 @@ static bool jit_is_eligible(sv_func_t *func) { case OP_JMP_TRUE: case OP_JMP_TRUE8: case OP_JMP_FALSE_PEEK: case OP_JMP_TRUE_PEEK: case OP_CALL: case OP_CALL_METHOD: - case OP_CALL_IS_PROTO: + case OP_CALL_IS_PROTO: case OP_CALL_ARRAY_INCLUDES: case OP_TAIL_CALL: case OP_TAIL_CALL_METHOD: case OP_APPLY: case OP_NEW_APPLY: case OP_GET_GLOBAL: case OP_GET_GLOBAL_UNDEF: @@ -2401,6 +2402,7 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_item_t imp_ge = MIR_new_import(ctx, "jit_helper_ge"); MIR_item_t imp_call = MIR_new_import(ctx, "jit_helper_call"); MIR_item_t imp_call_method = MIR_new_import(ctx, "jit_helper_call_method"); + MIR_item_t imp_call_array_includes = MIR_new_import(ctx, "jit_helper_call_array_includes"); MIR_item_t imp_apply = MIR_new_import(ctx, "jit_helper_apply"); MIR_item_t imp_rest = MIR_new_import(ctx, "jit_helper_rest"); MIR_item_t imp_special_obj = MIR_new_import(ctx, "jit_helper_special_obj"); @@ -7526,6 +7528,76 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos break; } + case OP_CALL_ARRAY_INCLUDES: { + vstack_flush_to_boxed(&vs, ctx, jit_func, r_d_slot); + uint16_t call_argc = sv_get_u16(ip + 1); + if (call_argc > 16 || vs.sp < (int)call_argc + 2) { ok = false; break; } + + MIR_reg_t r_arg_arr = r_args_buf; + for (int i = (int)call_argc - 1; i >= 0; i--) { + MIR_reg_t areg = vstack_pop(&vs); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_JSVAL, + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), + r_arg_arr, 0, 1), + MIR_new_reg_op(ctx, areg))); + } + + MIR_reg_t r_call_func = vstack_pop(&vs); + MIR_reg_t r_call_this = vstack_pop(&vs); + MIR_reg_t r_call_res = vstack_push(&vs); + + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 9, + MIR_new_ref_op(ctx, call_proto), + MIR_new_ref_op(ctx, imp_call_array_includes), + MIR_new_reg_op(ctx, r_call_res), + MIR_new_reg_op(ctx, r_vm), + MIR_new_reg_op(ctx, r_js), + MIR_new_reg_op(ctx, r_call_func), + MIR_new_reg_op(ctx, r_call_this), + MIR_new_reg_op(ctx, r_arg_arr), + MIR_new_int_op(ctx, (int64_t)call_argc))); + + if (has_captures) { + for (int i = 0; i < n_locals; i++) + if (captured_locals[i]) + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, local_regs[i]), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_lbuf, 0, 1))); + } + + MIR_label_t no_err = MIR_new_label(ctx); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_URSH, + MIR_new_reg_op(ctx, r_bool), + MIR_new_reg_op(ctx, r_call_res), + MIR_new_int_op(ctx, NANBOX_TYPE_SHIFT))); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_BNE, + MIR_new_label_op(ctx, no_err), + MIR_new_reg_op(ctx, r_bool), + MIR_new_uint_op(ctx, JIT_ERR_TAG))); + if (jit_try_depth > 0) { + jit_try_entry_t *h = &jit_try_stack[jit_try_depth - 1]; + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, vs.regs[h->saved_sp]), + MIR_new_reg_op(ctx, r_call_res))); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_JMP, + MIR_new_label_op(ctx, h->catch_label))); + } else { + MIR_append_insn(ctx, jit_func, + MIR_new_ret_insn(ctx, 1, MIR_new_reg_op(ctx, r_call_res))); + } + MIR_append_insn(ctx, jit_func, no_err); + break; + } + case OP_TAIL_CALL_METHOD: case OP_CALL_METHOD: { vstack_flush_to_boxed(&vs, ctx, jit_func, r_d_slot); diff --git a/tests/test_array_includes_regression_big.cjs b/tests/test_array_includes_regression_big.cjs index 443ff49..4115654 100644 --- a/tests/test_array_includes_regression_big.cjs +++ b/tests/test_array_includes_regression_big.cjs @@ -22,6 +22,31 @@ function assertArrayIncludesRegressionSurface() { grown.length = 3; assert.strictEqual(grown.includes(undefined), true); + try { + Array.prototype[1] = "from-array-proto"; + assert.strictEqual(Array(3).includes("from-array-proto"), true); + assert.strictEqual(Array(3).includes("missing"), false); + } finally { + delete Array.prototype[1]; + } + + const customIncludes = { + includes(value) { + return value === "custom"; + } + }; + assert.strictEqual(customIncludes.includes("custom"), true); + + const originalIncludes = Array.prototype.includes; + try { + Array.prototype.includes = function(value) { + return value === "patched"; + }; + assert.strictEqual([1, 2, 3].includes("patched"), true); + } finally { + Array.prototype.includes = originalIncludes; + } + let steps = 0; const generic = { get length() {