From 4a5631e5c399563894e96ba86dcfd71e29eba778 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Sun, 12 Apr 2026 00:53:03 -0700 Subject: [PATCH] rework Silver string building and add node:net client shims - replace deep rope-heavy string appends with a builder-backed concat path - teach the VM, compiler, JIT glue, and GC to read, flush, and preserve builder-backed strings - keep arguments/object slot syncing and related frame semantics aligned with the new slot behavior - finish fetch-backed node:http/node:https client shims and expose node:url.parse - add regression tests for string append loops, HTTP(S) shims, and node:url exports --- include/internal.h | 51 ++- include/pool.h | 2 +- include/silver/engine.h | 35 +- include/silver/glue.h | 11 + include/silver/opcode.h | 4 + include/types.h | 1 + src/ant.c | 311 ++++++++++++- src/builtins/node/http.mjs | 431 +++++++++++++++++- src/builtins/node/https.mjs | 46 +- src/gc/gc.c | 42 +- src/modules/url.c | 1 + src/silver/compiler.c | 153 ++++++- src/silver/engine.c | 329 ++++++++++++- src/silver/glue.c | 35 ++ src/silver/ops/arithmetic.h | 33 +- src/silver/ops/coercion.h | 28 +- src/silver/ops/iteration.h | 4 +- src/silver/ops/locals.h | 130 +++++- src/silver/ops/objects.h | 6 +- src/silver/ops/upvalues.h | 57 ++- src/silver/swarm.c | 260 +++++++++++ ...test_http_request_host_port_fetch_shim.mjs | 38 ++ tests/test_https_get_fetch_shim.mjs | 77 ++++ tests/test_node_url_parse_named_export.mjs | 19 + tests/test_string_small_append_loop.cjs | 77 ++++ 25 files changed, 2043 insertions(+), 138 deletions(-) create mode 100644 tests/test_http_request_host_port_fetch_shim.mjs create mode 100644 tests/test_https_get_fetch_shim.mjs create mode 100644 tests/test_node_url_parse_named_export.mjs create mode 100644 tests/test_string_small_append_loop.cjs diff --git a/include/internal.h b/include/internal.h index 5f35afb..c6b4600 100644 --- a/include/internal.h +++ b/include/internal.h @@ -70,9 +70,15 @@ #define PROTO_WALK_F_OBJECT_ONLY (1u << 0) #define PROTO_WALK_F_LOOKUP (1u << 1) -#define ROPE_MAX_DEPTH 255 +#define ROPE_MAX_DEPTH 4096 #define ROPE_FLATTEN_THRESHOLD (512 * 1024) +#define STR_BUILDER_TAIL_CAP 256u +#define STR_HEAP_TAG_MASK 0x3ULL +#define STR_HEAP_TAG_FLAT 0x0ULL +#define STR_HEAP_TAG_ROPE 0x1ULL +#define STR_HEAP_TAG_BUILDER 0x2ULL + #define T_EMPTY (NANBOX_PREFIX | ((ant_value_t)T_SENTINEL << NANBOX_TYPE_SHIFT) | 0xDEADULL) #define T_SPECIAL_OBJECT_MASK (JS_TPFLG(T_OBJ) | JS_TPFLG(T_ARR)) #define T_NEEDS_PROTO_FALLBACK (JS_TPFLG(T_FUNC) | JS_TPFLG(T_ARR) | JS_TPFLG(T_PROMISE) | JS_TPFLG(T_GENERATOR)) @@ -265,6 +271,21 @@ typedef struct { char bytes[]; } ant_flat_string_t; +typedef struct ant_builder_chunk { + struct ant_builder_chunk *next; + ant_value_t value; +} ant_builder_chunk_t; + +typedef struct { + ant_offset_t len; + ant_builder_chunk_t *head; + ant_builder_chunk_t *chunk_tail; + ant_value_t cached; + uint16_t tail_len; + uint8_t ascii_state; + char tail[STR_BUILDER_TAIL_CAP]; +} ant_string_builder_t; + typedef struct { const char *ptr; size_t len; @@ -344,6 +365,10 @@ ant_value_t js_get_module_import_binding(ant_t *js); ant_value_t js_builtin_import(ant_t *js, ant_value_t *args, int nargs); ant_value_t js_create_import_meta(ant_t *js, const char *filename, bool is_main); ant_value_t js_create_module_context(ant_t *js, const char *filename, bool is_main); +ant_value_t js_create_arguments_object(ant_t *js, ant_value_t callee, sv_frame_t *frame, int argc, int mapped_count, bool is_strict); + +void js_arguments_detach(ant_t *js, ant_value_t obj); +void js_arguments_sync_slot(ant_t *js, ant_value_t obj, uint32_t idx, ant_value_t value); ant_value_t coerce_to_str(ant_t *js, ant_value_t v); ant_value_t coerce_to_str_concat(ant_t *js, ant_value_t v); @@ -373,6 +398,7 @@ ant_value_t mkval(uint8_t type, uint64_t data); ant_value_t mkobj(ant_t *js, ant_offset_t parent); ant_value_t js_mkobj_with_inobj_limit(ant_t *js, uint8_t inobj_limit); ant_value_t rope_flatten(ant_t *js, ant_value_t rope); +ant_value_t str_materialize(ant_t *js, ant_value_t value); ant_value_t js_for_in_keys(ant_t *js, ant_value_t obj); ant_value_t js_delete_prop(ant_t *js, ant_value_t obj, const char *key, size_t len); @@ -441,8 +467,29 @@ static inline bool is_length_key(const char *key, size_t len) { return len == 6 && !memcmp(key, "length", 6); } +// TODO: move strings helpers to strings.h static inline bool str_is_heap_rope(ant_value_t value) { - return vtype(value) == T_STR && ((vdata(value) & 1ULL) != 0); + return vtype(value) == T_STR && ((vdata(value) & STR_HEAP_TAG_MASK) == STR_HEAP_TAG_ROPE); +} + +static inline bool str_is_heap_builder(ant_value_t value) { + return vtype(value) == T_STR && ((vdata(value) & STR_HEAP_TAG_MASK) == STR_HEAP_TAG_BUILDER); +} + +static inline ant_rope_heap_t *ant_str_rope_ptr(ant_value_t value) { + return (ant_rope_heap_t *)(uintptr_t)(vdata(value) & ~STR_HEAP_TAG_MASK); +} + +static inline ant_string_builder_t *ant_str_builder_ptr(ant_value_t value) { + return (ant_string_builder_t *)(uintptr_t)(vdata(value) & ~STR_HEAP_TAG_MASK); +} + +static inline ant_value_t ant_mkrope_value(ant_rope_heap_t *rope) { + return mkval(T_STR, ((uintptr_t)rope) | STR_HEAP_TAG_ROPE); +} + +static inline ant_value_t ant_mkbuilder_value(ant_string_builder_t *builder) { + return mkval(T_STR, ((uintptr_t)builder) | STR_HEAP_TAG_BUILDER); } static inline int js_brand_id(ant_value_t obj) { diff --git a/include/pool.h b/include/pool.h index e13d0cd..7a673de 100644 --- a/include/pool.h +++ b/include/pool.h @@ -58,7 +58,7 @@ typedef struct ant_large_string_alloc { typedef struct { ant_offset_t len; - uint8_t depth; + uint16_t depth; ant_value_t left; ant_value_t right; ant_value_t cached; diff --git a/include/silver/engine.h b/include/silver/engine.h index 0355d43..db40999 100644 --- a/include/silver/engine.h +++ b/include/silver/engine.h @@ -211,7 +211,7 @@ typedef enum { typedef struct sv_upvalue sv_upvalue_t; -typedef struct { +typedef struct sv_frame { uint8_t *ip; ant_value_t *bp; ant_value_t *lp; @@ -230,7 +230,9 @@ typedef struct { sv_completion_t completion; sv_upvalue_t **upvalues; int upvalue_count; + ant_value_t with_obj; + ant_value_t arguments_obj; } sv_frame_t; typedef enum { @@ -255,6 +257,8 @@ static inline sv_upvalue_t *js_upvalue_alloc(void) { return (sv_upvalue_t *)fixed_arena_alloc(&rt->js->upvalue_arena); } +bool sv_slot_has_open_upvalue(sv_vm_t *vm, ant_value_t *slot); + #define SV_CALL_HAS_BOUND_ARGS (1u << 0) #define SV_CALL_HAS_SUPER (1u << 1) #define SV_CALL_IS_ARROW (1u << 2) @@ -458,10 +462,12 @@ static inline ant_value_t sv_frame_get_arg_value(const sv_frame_t *frame, uint16 return frame->bp[idx]; } -static inline void sv_frame_set_arg_value(sv_frame_t *frame, uint16_t idx, ant_value_t val) { +static inline void sv_frame_set_arg_value(ant_t *js, sv_frame_t *frame, uint16_t idx, ant_value_t val) { int arg_slots = sv_frame_arg_slots(frame); if (!frame || !frame->bp || (int)idx >= arg_slots) return; frame->bp[idx] = val; + if (vtype(frame->arguments_obj) != T_UNDEF) + js_arguments_sync_slot(js, frame->arguments_obj, idx, val); } static inline ant_value_t *sv_frame_slot_ptr(sv_frame_t *frame, uint16_t slot_idx) { @@ -476,11 +482,36 @@ static inline ant_value_t *sv_frame_slot_ptr(sv_frame_t *frame, uint16_t slot_id return &frame->lp[slot_idx - param_count]; } +static inline uint16_t sv_frame_total_slots(const sv_frame_t *frame) { + if (!frame || !frame->func) return 0; + int total = frame->func->param_count + frame->func->max_locals; + return total > 0 ? (uint16_t)total : 0; +} + static inline void sv_vm_maybe_checkpoint_microtasks(ant_t *js) { if (!js || js->microtasks_draining || js->vm_exec_depth != 0) return; js_maybe_drain_microtasks(js); } +ant_value_t sv_string_builder_read_value( + ant_t *js, ant_value_t value +); + +ant_value_t sv_string_builder_flush_slot( + sv_vm_t *vm, ant_t *js, + sv_frame_t *frame, uint16_t slot_idx +); + +ant_value_t sv_string_builder_append_slot( + sv_vm_t *vm, ant_t *js, sv_frame_t *frame, + sv_func_t *func, uint16_t slot_idx, ant_value_t rhs +); + +ant_value_t sv_string_builder_append_snapshot_slot( + sv_vm_t *vm, ant_t *js, sv_frame_t *frame, + sv_func_t *func, uint16_t slot_idx, ant_value_t lhs, ant_value_t rhs +); + typedef struct { ant_value_t this_val; ant_value_t super_val; diff --git a/include/silver/glue.h b/include/silver/glue.h index f2b2dde..530d0fc 100644 --- a/include/silver/glue.h +++ b/include/silver/glue.h @@ -159,5 +159,16 @@ ant_value_t jit_helper_new( ant_value_t *args, int argc ); +ant_value_t jit_helper_str_append_local( + sv_vm_t *vm, ant_t *js, sv_func_t *func, + ant_value_t *locals, uint16_t local_idx, ant_value_t rhs +); + +ant_value_t jit_helper_str_append_local_snapshot( + sv_vm_t *vm, ant_t *js, sv_func_t *func, + ant_value_t *locals, uint16_t local_idx, + ant_value_t lhs, ant_value_t rhs +); + #endif #endif diff --git a/include/silver/opcode.h b/include/silver/opcode.h index 7822cc0..21ec9f2 100644 --- a/include/silver/opcode.h +++ b/include/silver/opcode.h @@ -63,6 +63,7 @@ OP_DEF( SET_LOCAL8, 2, 1, 1, loc8) OP_DEF( SET_LOCAL_UNDEF, 3, 0, 0, loc) /* mark TDZ uninitialized */ OP_DEF( GET_LOCAL_CHK, 7, 0, 1, loc_atom) /* get + TDZ check (u16 slot, u32 atom) */ OP_DEF( PUT_LOCAL_CHK, 7, 1, 0, loc_atom) /* put + TDZ check (u16 slot, u32 atom) */ +OP_DEF( GET_SLOT_RAW, 3, 0, 1, loc) /* push raw frame slot value (no builder read) */ OP_DEF( GET_ARG, 3, 0, 1, arg) OP_DEF( PUT_ARG, 3, 1, 0, arg) @@ -117,6 +118,9 @@ OP_DEF( POST_DEC, 1, 1, 2, none) /* -> old new */ OP_DEF( INC_LOCAL, 2, 0, 0, loc8) /* locals[i]++ in-place */ OP_DEF( DEC_LOCAL, 2, 0, 0, loc8) /* locals[i]-- in-place */ OP_DEF( ADD_LOCAL, 2, 1, 0, loc8) /* locals[i] += TOS */ +OP_DEF( STR_APPEND_LOCAL, 3, 1, 0, loc) /* logical slot += TOS via builder */ +OP_DEF( STR_ALC_SNAPSHOT, 3, 2, 0, loc) /* logical slot = snapshot + TOS */ +OP_DEF( STR_FLUSH_LOCAL, 3, 0, 0, loc) /* materialize logical slot builder */ OP_DEF( EQ, 1, 2, 1, none) /* == (abstract) */ OP_DEF( NE, 1, 2, 1, none) /* != */ diff --git a/include/types.h b/include/types.h index 0fd69ea..5a8167f 100644 --- a/include/types.h +++ b/include/types.h @@ -20,6 +20,7 @@ typedef struct ant_shape ant_shape_t; typedef struct sv_vm sv_vm_t; typedef struct sv_func sv_func_t; typedef struct sv_closure sv_closure_t; +typedef struct sv_frame sv_frame_t; typedef size_t ant_handle_t; typedef uint64_t ant_offset_t; diff --git a/src/ant.c b/src/ant.c index 47c372d..9f1a512 100644 --- a/src/ant.c +++ b/src/ant.c @@ -6,6 +6,7 @@ #include "ant.h" #include "utf8.h" +#include "ptr.h" #include "debug.h" #include "tokens.h" #include "common.h" @@ -238,18 +239,11 @@ void js_mark_constructor(ant_value_t value, bool is_constructor) { } static inline ant_flat_string_t *str_flat_ptr(ant_value_t value) { - if (vtype(value) != T_STR || str_is_heap_rope(value)) return NULL; + if (vtype(value) != T_STR) return NULL; + if ((vdata(value) & STR_HEAP_TAG_MASK) != STR_HEAP_TAG_FLAT) return NULL; return (ant_flat_string_t *)(uintptr_t)vdata(value); } -static inline ant_rope_heap_t *str_rope_ptr(ant_value_t value) { - return (ant_rope_heap_t *)(uintptr_t)(vdata(value) & ~1ULL); -} - -static inline ant_value_t mkrope_value(ant_rope_heap_t *rope) { - return mkval(T_STR, ((uintptr_t)rope) | 1ULL); -} - static inline ant_extra_slot_t *obj_extra_slots(ant_object_t *obj) { return (ant_extra_slot_t *)obj->extra_slots; } @@ -562,9 +556,13 @@ static size_t strkey(ant_t *js, ant_value_t value, char *buf, size_t len); ant_offset_t vstrlen(ant_t *js, ant_value_t v) { if (str_is_heap_rope(v)) { - ant_rope_heap_t *rope = str_rope_ptr(v); + ant_rope_heap_t *rope = ant_str_rope_ptr(v); return rope ? rope->len : 0; } + if (str_is_heap_builder(v)) { + ant_string_builder_t *builder = ant_str_builder_ptr(v); + return builder ? builder->len : 0; + } ant_flat_string_t *flat = str_flat_ptr(v); return flat ? flat->len : 0; } @@ -1636,7 +1634,7 @@ static inline ant_offset_t assert_flat_string_len(ant_t *js, ant_value_t value, static inline ant_rope_heap_t *assert_rope_ptr(ant_value_t value) { assert(vtype(value) == T_STR); assert(str_is_heap_rope(value)); - ant_rope_heap_t *ptr = str_rope_ptr(value); + ant_rope_heap_t *ptr = ant_str_rope_ptr(value); assert(ptr != NULL); return ptr; } @@ -1646,7 +1644,7 @@ static inline ant_offset_t rope_len(ant_value_t value) { return ptr->len; } -static inline uint8_t rope_depth(ant_value_t value) { +static inline uint16_t rope_depth(ant_value_t value) { ant_rope_heap_t *ptr = assert_rope_ptr(value); return ptr->depth; } @@ -1671,6 +1669,31 @@ static inline void rope_set_cached_flat(ant_value_t rope, ant_value_t flat) { ptr->cached = flat; } +static inline ant_string_builder_t *assert_builder_ptr(ant_value_t value) { + assert(vtype(value) == T_STR); + assert(str_is_heap_builder(value)); + + ant_string_builder_t *ptr = ant_str_builder_ptr(value); + assert(ptr != NULL); + + return ptr; +} + +static inline ant_offset_t builder_len(ant_value_t value) { + ant_string_builder_t *ptr = assert_builder_ptr(value); + return ptr->len; +} + +static inline ant_value_t builder_cached_flat(ant_value_t value) { + ant_string_builder_t *ptr = assert_builder_ptr(value); + return ptr->cached; +} + +static inline void builder_set_cached_flat(ant_value_t builder, ant_value_t flat) { + ant_string_builder_t *ptr = assert_builder_ptr(builder); + ptr->cached = flat; +} + static void rope_flatten_into(ant_t *js, ant_value_t str, char *dest, ant_offset_t *pos) { assert(vtype(str) == T_STR); @@ -1754,9 +1777,71 @@ ant_value_t rope_flatten(ant_t *js, ant_value_t rope) { return flat; } +static ant_value_t builder_flatten(ant_t *js, ant_value_t builder) { + assert(vtype(builder) == T_STR); + if (!str_is_heap_builder(builder)) return builder; + + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, builder); + + ant_value_t cached = builder_cached_flat(builder); + GC_ROOT_PIN(js, cached); + + if (vtype(cached) == T_STR && !str_is_heap_rope(cached) && !str_is_heap_builder(cached)) { + GC_ROOT_RESTORE(js, root_mark); + return cached; + } + + ant_string_builder_t *ptr = assert_builder_ptr(builder); + ant_value_t flat = js_mkstr(js, NULL, (size_t)ptr->len); + + GC_ROOT_PIN(js, flat); + if (is_err(flat)) { + GC_ROOT_RESTORE(js, root_mark); + return flat; + } + + ant_flat_string_t *flat_ptr = (ant_flat_string_t *)(uintptr_t)vdata(flat); + size_t cursor = 0; + + for (ant_builder_chunk_t *chunk = ptr->head; chunk; chunk = chunk->next) { + ant_value_t chunk_value = chunk->value; + if (str_is_heap_rope(chunk_value) || str_is_heap_builder(chunk_value)) { + chunk_value = str_materialize(js, chunk_value); + if (is_err(chunk_value)) { + GC_ROOT_RESTORE(js, root_mark); + return chunk_value; + }} + + ant_flat_string_t *chunk_flat = str_flat_ptr(chunk_value); + if (!chunk_flat || chunk_flat->len == 0) continue; + memcpy(flat_ptr->bytes + cursor, chunk_flat->bytes, (size_t)chunk_flat->len); + cursor += (size_t)chunk_flat->len; + } + + if (ptr->tail_len > 0) { + memcpy(flat_ptr->bytes + cursor, ptr->tail, ptr->tail_len); + cursor += ptr->tail_len; + } + + flat_ptr->bytes[cursor] = '\0'; + flat_ptr->is_ascii = ptr->ascii_state; + builder_set_cached_flat(builder, flat); + GC_ROOT_RESTORE(js, root_mark); + + return flat; +} + +ant_value_t str_materialize(ant_t *js, ant_value_t value) { + if (vtype(value) != T_STR) return value; + if (str_is_heap_rope(value)) return rope_flatten(js, value); + if (str_is_heap_builder(value)) return builder_flatten(js, value); + return value; +} + ant_offset_t vstr(ant_t *js, ant_value_t value, ant_offset_t *len) { - if (str_is_heap_rope(value)) { - ant_value_t flat = rope_flatten(js, value); + if (str_is_heap_rope(value) || str_is_heap_builder(value)) { + ant_value_t flat = str_materialize(js, value); assert(!is_err(flat)); value = flat; } @@ -2268,6 +2353,169 @@ static inline bool arr_has(ant_t *js, ant_value_t arr, ant_offset_t idx) { return lkp(js, arr, idxstr, idxlen) != 0; } +enum { ANT_ARGUMENTS_NATIVE_TAG = 0x41524753u }; // ARGS + +typedef struct { + sv_vm_t *vm; + int frame_index; + uint32_t mapped_count; + uint8_t in_setter; + uint8_t deleted[]; +} ant_arguments_state_t; + +static inline ant_arguments_state_t *js_arguments_state(ant_value_t obj) { + if (!js_check_native_tag(obj, ANT_ARGUMENTS_NATIVE_TAG)) return NULL; + return (ant_arguments_state_t *)js_get_native_ptr(obj); +} + +static ant_value_t js_arguments_getter(ant_t *js, ant_value_t obj, const char *key, size_t key_len) { + ant_offset_t arr_len = get_array_length(js, obj); + unsigned long idx = 0; + + if (!parse_array_index(key, key_len, arr_len, &idx)) return js_mkundef(); + if ((ant_offset_t)idx >= arr_len) return js_mkundef(); + + ant_arguments_state_t *state = js_arguments_state(obj); + if ( + state && state->frame_index >= 0 && + (uint32_t)idx < state->mapped_count && + !state->deleted[idx] + ) { + sv_frame_t *frame = &state->vm->frames[state->frame_index]; + return frame->bp[idx]; + } + + return arr_get(js, obj, (ant_offset_t)idx); +} + +static bool js_arguments_setter( + ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t value +) { + unsigned long idx = 0; + if (!parse_array_index(key, key_len, (ant_offset_t)-1, &idx)) return false; + + ant_arguments_state_t *state = js_arguments_state(obj); + if (state) state->in_setter = 1; + arr_set(js, obj, (ant_offset_t)idx, value); + + if (state) state->in_setter = 0; + if ( + state && state->frame_index >= 0 && + (uint32_t)idx < state->mapped_count && + !state->deleted[idx] + ) { + sv_frame_t *frame = &state->vm->frames[state->frame_index]; + frame->bp[idx] = value; + } + + return true; +} + +static bool js_arguments_deleter(ant_t *js, ant_value_t obj, const char *key, size_t key_len) { + unsigned long idx = 0; + if (!parse_array_index(key, key_len, (ant_offset_t)-1, &idx)) return false; + + ant_arguments_state_t *state = js_arguments_state(obj); + if (state && (uint32_t)idx < state->mapped_count) state->deleted[idx] = 1; + return true; +} + +static void js_arguments_finalizer(ant_t *js, ant_object_t *obj) { + if (!obj || obj->native.tag != ANT_ARGUMENTS_NATIVE_TAG) return; + free(obj->native.ptr); + obj->native.ptr = NULL; + obj->native.tag = 0; +} + +ant_value_t js_create_arguments_object( + ant_t *js, + ant_value_t callee, + sv_frame_t *frame, + int argc, + int mapped_count, + bool is_strict +) { + GC_ROOT_SAVE(root_mark, js); + + ant_value_t arr = js_mkarr(js); + if (is_err(arr)) { + GC_ROOT_RESTORE(js, root_mark); + return arr; + } GC_ROOT_PIN(js, arr); + + if (frame && frame->bp && argc > 0) { + for (int i = 0; i < argc; i++) js_arr_push(js, arr, frame->bp[i]); + } + + if (is_strict) js_set_slot(arr, SLOT_STRICT_ARGS, js_true); + else if (vtype(callee) == T_FUNC) setprop_cstr(js, arr, "callee", 6, callee); + js_set_sym(js, arr, get_toStringTag_sym(), js_mkstr(js, "Arguments", 9)); + + if (is_object_type(js->sym.array_proto)) { + ant_value_t iter_fn = js_get_sym(js, js->sym.array_proto, get_iterator_sym()); + if (vtype(iter_fn) == T_FUNC || vtype(iter_fn) == T_CFUNC) + js_set_sym(js, arr, get_iterator_sym(), iter_fn); + } + + if (!is_strict && mapped_count > 0 && frame && js->vm) { + ant_arguments_state_t *state = calloc( + 1, sizeof(*state) + (size_t)mapped_count * sizeof(state->deleted[0])); + if (!state) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "oom"); + } + + state->vm = js->vm; + state->frame_index = (int)(frame - js->vm->frames); + state->mapped_count = (uint32_t)mapped_count; + + js_set_native_ptr(arr, state); + js_set_native_tag(arr, ANT_ARGUMENTS_NATIVE_TAG); + js_set_finalizer(arr, js_arguments_finalizer); + js_set_getter(arr, js_arguments_getter); + js_set_setter(arr, js_arguments_setter); + js_set_deleter(arr, js_arguments_deleter); + } + + GC_ROOT_RESTORE(js, root_mark); + return arr; +} + +void js_arguments_detach(ant_t *js, ant_value_t obj) { + ant_arguments_state_t *state = js_arguments_state(obj); + if (!state || state->frame_index < 0) return; + + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + + sv_frame_t *frame = &state->vm->frames[state->frame_index]; + ant_offset_t arr_len = get_array_length(js, obj); + ant_offset_t limit = (ant_offset_t)state->mapped_count; + if (arr_len < limit) limit = arr_len; + + for (ant_offset_t i = 0; i < limit; i++) { + if (state->deleted[i]) continue; + state->in_setter = 1; + arr_set(js, obj, i, frame->bp[i]); + state->in_setter = 0; + } + + state->frame_index = -1; + GC_ROOT_RESTORE(js, root_mark); +} + +void js_arguments_sync_slot(ant_t *js, ant_value_t obj, uint32_t idx, ant_value_t value) { + ant_arguments_state_t *state = js_arguments_state(obj); + if (!state || state->frame_index < 0 || + idx >= state->mapped_count || state->deleted[idx]) { + return; + } + + state->in_setter = 1; + arr_set(js, obj, (ant_offset_t)idx, value); + state->in_setter = 0; +} + static inline void arr_del(ant_t *js, ant_value_t arr, ant_offset_t idx) { ant_offset_t semantic_len = get_array_length(js, arr); if (idx >= semantic_len) return; @@ -2322,7 +2570,7 @@ ant_value_t js_mkstr_permanent(ant_t *js, const void *ptr, size_t len) { return mkval(T_STR, (uintptr_t)flat); } -static ant_value_t js_mkrope(ant_t *js, ant_value_t left, ant_value_t right, ant_offset_t total_len, uint8_t depth) { +static ant_value_t js_mkrope(ant_t *js, ant_value_t left, ant_value_t right, ant_offset_t total_len, uint16_t depth) { ant_rope_heap_t *rope = (ant_rope_heap_t *)js_type_alloc( js, ANT_ALLOC_ROPE, sizeof(*rope), _Alignof(ant_rope_heap_t) ); @@ -2332,7 +2580,7 @@ static ant_value_t js_mkrope(ant_t *js, ant_value_t left, ant_value_t right, ant rope->left = left; rope->right = right; rope->cached = js_mkundef(); - return mkrope_value(rope); + return ant_mkrope_value(rope); } @@ -3230,6 +3478,11 @@ ant_value_t js_setprop(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v) ant_offset_t klen; ant_offset_t koff = vstr(js, k, &klen); const char *key = (char *)(uintptr_t)(koff); + if (array_obj_ptr(obj) && klen > 0 && key[0] >= '0' && key[0] <= '9') { + ant_arguments_state_t *args_state = js_arguments_state(obj); + if (args_state && !args_state->in_setter && js_arguments_setter(js, obj, key, (size_t)klen, v)) return v; + } + if (array_obj_ptr(obj) && !is_proxy(obj) && klen > 0 && key[0] >= '0' && key[0] <= '9') { ant_value_t result = js_setprop_array_fast(js, obj, k, v, klen, key); if (vtype(result) != T_UNDEF) return result; @@ -4057,11 +4310,22 @@ static ant_value_t string_builder_finalize(ant_t *js, string_builder_t *sb) { ant_offset_t str_len_fast(ant_t *js, ant_value_t str) { if (vtype(str) != T_STR) return 0; if (str_is_heap_rope(str)) return rope_len(str); + if (str_is_heap_builder(str)) return builder_len(str); return assert_flat_string_len(js, str, NULL); } ant_value_t do_string_op(ant_t *js, uint8_t op, ant_value_t l, ant_value_t r) { if (op == TOK_PLUS) { + if (str_is_heap_builder(l)) { + l = builder_flatten(js, l); + if (is_err(l)) return l; + } + + if (str_is_heap_builder(r)) { + r = builder_flatten(js, r); + if (is_err(r)) return r; + } + ant_offset_t n1 = str_len_fast(js, l); ant_offset_t n2 = str_len_fast(js, r); ant_offset_t total_len = n1 + n2; @@ -4069,8 +4333,8 @@ ant_value_t do_string_op(ant_t *js, uint8_t op, ant_value_t l, ant_value_t r) { if (n2 == 0) return l; if (n1 == 0) return r; - uint8_t left_depth = (vtype(l) == T_STR && str_is_heap_rope(l)) ? rope_depth(l) : 0; - uint8_t right_depth = (vtype(r) == T_STR && str_is_heap_rope(r)) ? rope_depth(r) : 0; + uint16_t left_depth = (vtype(l) == T_STR && str_is_heap_rope(l)) ? rope_depth(l) : 0; + uint16_t right_depth = (vtype(r) == T_STR && str_is_heap_rope(r)) ? rope_depth(r) : 0; unsigned int new_depth = (unsigned int)(left_depth > right_depth ? left_depth : right_depth) + 1u; if (new_depth >= ROPE_MAX_DEPTH || total_len >= ROPE_FLATTEN_THRESHOLD) { @@ -4096,7 +4360,7 @@ ant_value_t do_string_op(ant_t *js, uint8_t op, ant_value_t l, ant_value_t r) { return string_builder_finalize(js, &sb); } - return js_mkrope(js, l, r, total_len, (uint8_t)new_depth); + return js_mkrope(js, l, r, total_len, (uint16_t)new_depth); } ant_offset_t n1, off1 = vstr(js, l, &n1); @@ -8586,7 +8850,10 @@ static ant_value_t builtin_Array_from(ant_t *js, ant_value_t *args, int nargs) { ant_value_t iter_sym = get_iterator_sym(); if (vtype(src) == T_STR) { - if (str_is_heap_rope(src)) { src = rope_flatten(js, src); if (is_err(src)) return src; } + if (str_is_heap_rope(src) || str_is_heap_builder(src)) { + src = str_materialize(js, src); + if (is_err(src)) return src; + } ant_offset_t slen = str_len_fast(js, src); array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 }; for (ant_offset_t i = 0; i < slen; ) { @@ -13627,6 +13894,10 @@ static bool js_try_get(ant_t *js, ant_value_t obj, const char *key, ant_value_t ant_offset_t arr_len = get_array_length(js, obj); if (parse_array_index(key, key_len, arr_len, &idx)) { + if (js_arguments_state(obj)) { + *out = js_arguments_getter(js, obj, key, key_len); + return true; + } if (arr_has(js, obj, (ant_offset_t)idx)) { *out = arr_get(js, obj, (ant_offset_t)idx); return true; diff --git a/src/builtins/node/http.mjs b/src/builtins/node/http.mjs index 2f194e6..58d2379 100644 --- a/src/builtins/node/http.mjs +++ b/src/builtins/node/http.mjs @@ -110,6 +110,138 @@ function clientNotImplemented() { throw new Error('node:http client transport is not implemented yet'); } +function isURLLike(value) { + return !!value && typeof value === 'object' && typeof value.href === 'string'; +} + +function cloneOptionObject(value) { + return value && typeof value === 'object' ? { ...value } : {}; +} + +function normalizeRequestArgs(input, options, callback) { + let resolvedInput = input; + let resolvedOptions = options; + let resolvedCallback = callback; + + if (typeof resolvedOptions === 'function') { + resolvedCallback = resolvedOptions; + resolvedOptions = undefined; + } + + if (typeof resolvedInput === 'function' || resolvedInput === undefined || resolvedInput === null) { + resolvedCallback = typeof resolvedInput === 'function' ? resolvedInput : resolvedCallback; + resolvedInput = undefined; + } + + return { + input: resolvedInput, + options: cloneOptionObject(resolvedOptions), + callback: resolvedCallback + }; +} + +function defaultPortForProtocol(protocol) { + return protocol === 'https:' ? 443 : 80; +} + +function hostIncludesExplicitPort(host) { + if (host === undefined || host === null) return false; + const value = String(host); + const bracketEnd = value.lastIndexOf(']'); + const colonIndex = value.lastIndexOf(':'); + return colonIndex > bracketEnd; +} + +function buildRequestOptions(input, options) { + const requestOptions = cloneOptionObject(options); + + if (typeof input === 'string' || isURLLike(input)) { + const url = new URL(String(input)); + if (requestOptions.protocol === undefined) requestOptions.protocol = url.protocol; + if (requestOptions.hostname === undefined && requestOptions.host === undefined) { + requestOptions.hostname = url.hostname; + } + if (requestOptions.port === undefined && url.port) requestOptions.port = url.port; + if (requestOptions.path === undefined) requestOptions.path = `${url.pathname}${url.search}`; + if (requestOptions.auth === undefined && url.username) { + requestOptions.auth = url.password ? `${url.username}:${url.password}` : url.username; + } + } else if (input && typeof input === 'object') { + Object.assign(requestOptions, input); + } + + if (!requestOptions.protocol) requestOptions.protocol = 'http:'; + if (!requestOptions.method) requestOptions.method = 'GET'; + if (!requestOptions.path) requestOptions.path = '/'; + if ( + (requestOptions.port === undefined || requestOptions.port === null || requestOptions.port === '') && + !hostIncludesExplicitPort(requestOptions.host) + ) { + requestOptions.port = defaultPortForProtocol(requestOptions.protocol); + } + + return requestOptions; +} + +function buildRequestUrl(options) { + const protocol = options.protocol || 'http:'; + const base = new URL(`${protocol}//localhost/`); + + if (options.hostname !== undefined && options.hostname !== null) base.hostname = String(options.hostname); + else if (options.host !== undefined && options.host !== null) base.host = String(options.host); + + if (options.port !== undefined && options.port !== null && options.port !== '') { + base.port = String(options.port); + } + + if (options.auth) { + const auth = String(options.auth); + const sep = auth.indexOf(':'); + if (sep === -1) base.username = auth; + else { + base.username = auth.slice(0, sep); + base.password = auth.slice(sep + 1); + } + } + + return new URL(String(options.path || '/'), base).toString(); +} + +function createAbortError(message) { + const error = new Error(message || 'The operation was aborted'); + error.name = 'AbortError'; + return error; +} + +function buildRequestHeadersObject(headers) { + const normalized = createHeadersObject(); + + if (!headers) return normalized; + Object.keys(headers).forEach(name => { + const value = headers[name]; + normalized[name] = Array.isArray(value) ? value.join(', ') : String(value); + }); + return normalized; +} + +function buildHeadersFromFetch(response) { + const headers = createHeadersObject(); + const rawHeaders = []; + + for (const [name, value] of response.headers.entries()) { + rawHeaders.push(name, value); + appendHeaderValue(headers, normalizeHeaderName(name), value); + } + + return { headers, rawHeaders }; +} + +function getFetchBody(chunks) { + if (!chunks || chunks.length === 0) return undefined; + if (chunks.length === 1) return chunks[0]; + return Buffer.concat(chunks); +} + // compatibility stub only function createAgentState() { return Object.create(null); @@ -117,10 +249,293 @@ function createAgentState() { export class OutgoingMessage extends EventEmitter {} +class FetchIncomingMessage extends EventEmitter { + constructor(response) { + super(); + + const { headers, rawHeaders } = buildHeadersFromFetch(response); + + this.socket = null; + this.connection = null; + this.statusCode = response.status; + this.statusMessage = response.statusText || STATUS_CODES[response.status] || ''; + this.headers = headers; + this.rawHeaders = rawHeaders; + this.httpVersion = '1.1'; + this.httpVersionMajor = 1; + this.httpVersionMinor = 1; + this.complete = false; + this.aborted = false; + this.destroyed = false; + this.readableEnded = false; + this.url = response.url; + this._reader = response.body && typeof response.body.getReader === 'function' ? response.body.getReader() : null; + this._encoding = null; + this._decoder = null; + this._pumpStarted = false; + this._closeEmitted = false; + } + + _emitClose() { + if (this._closeEmitted) return; + this._closeEmitted = true; + this.emit('close'); + } + + async _pumpBody() { + if (this._pumpStarted) return; + this._pumpStarted = true; + + if (!this._reader) { + this.complete = true; + this.readableEnded = true; + this.emit('end'); + this._emitClose(); + return; + } + + try { + for (;;) { + const { done, value } = await this._reader.read(); + if (done) break; + if (!value || value.byteLength === 0) continue; + + const chunk = Buffer.from(value); + if (this._decoder) { + const text = this._decoder.decode(chunk, { stream: true }); + if (text.length > 0) this.emit('data', text); + } else if (this._encoding) { + this.emit('data', chunk.toString(this._encoding)); + } else { + this.emit('data', chunk); + } + } + + if (this._decoder) { + const finalChunk = this._decoder.decode(); + if (finalChunk.length > 0) this.emit('data', finalChunk); + } + + this.complete = true; + this.readableEnded = true; + this.emit('end'); + this._emitClose(); + } catch (error) { + if (this.destroyed) return; + this.destroyed = true; + this.aborted = true; + this.emit('error', error); + this._emitClose(); + } + } + + setEncoding(encoding) { + this._encoding = encoding || 'utf8'; + if (typeof TextDecoder === 'function') { + try { + this._decoder = new TextDecoder(this._encoding === 'utf8' ? 'utf-8' : this._encoding); + } catch { + this._decoder = null; + } + } + return this; + } + + resume() { + queueMicrotask(() => { + this._pumpBody(); + }); + return this; + } + + destroy(error) { + if (this.destroyed) return this; + this.destroyed = true; + this.aborted = true; + if (this._reader && typeof this._reader.cancel === 'function') { + Promise.resolve(this._reader.cancel(error)).catch(() => {}); + } + if (error) this.emit('error', error); + this._emitClose(); + return this; + } +} + export class ClientRequest extends OutgoingMessage { - constructor() { + constructor(options = {}, callback) { super(); - clientNotImplemented(); + + this.agent = options.agent ?? globalAgent; + this.method = String(options.method || 'GET').toUpperCase(); + this.protocol = options.protocol || 'http:'; + this.host = options.host ?? options.hostname ?? 'localhost'; + this.hostname = options.hostname ?? options.host ?? 'localhost'; + this.port = options.port ?? defaultPortForProtocol(this.protocol); + this.path = options.path || '/'; + this.socket = null; + this.connection = null; + this.destroyed = false; + this.aborted = false; + this.finished = false; + this.reusedSocket = false; + this._headers = createHeadersObject(); + this._bodyChunks = []; + this._controller = typeof AbortController === 'function' ? new AbortController() : null; + this._timeout = 0; + this._timeoutHandle = null; + this._dispatchStarted = false; + this._requestUrl = buildRequestUrl(options); + this._closeEmitted = false; + this._timedOut = false; + + if (options.headers) { + Object.keys(options.headers).forEach(name => { + this.setHeader(name, options.headers[name]); + }); + } + + if (typeof callback === 'function') this.once('response', callback); + if (options.timeout !== undefined) this.setTimeout(options.timeout); + } + + _emitClose() { + if (this._closeEmitted) return; + this._closeEmitted = true; + this.emit('close'); + } + + _clearTimeoutTimer() { + if (!this._timeoutHandle) return; + clearTimeout(this._timeoutHandle); + this._timeoutHandle = null; + } + + _armTimeoutTimer() { + this._clearTimeoutTimer(); + if (!(this._timeout > 0)) return; + + this._timeoutHandle = setTimeout(() => { + if (this.destroyed) return; + this._timedOut = true; + this.emit('timeout'); + if (this._controller) this._controller.abort(createAbortError('Request timed out')); + }, this._timeout); + } + + _dispatch() { + if (this._dispatchStarted || this.destroyed) return; + this._dispatchStarted = true; + this._armTimeoutTimer(); + + Promise.resolve() + .then(async () => { + const response = await fetch(this._requestUrl, { + method: this.method, + headers: buildRequestHeadersObject(this._headers), + body: getFetchBody(this._bodyChunks), + signal: this._controller ? this._controller.signal : undefined + }); + + if (this.destroyed) return; + this._clearTimeoutTimer(); + + const incoming = new FetchIncomingMessage(response); + incoming.on('close', () => { + this._emitClose(); + }); + + this.emit('response', incoming); + queueMicrotask(() => { + incoming._pumpBody(); + }); + }) + .catch(error => { + this._clearTimeoutTimer(); + if (this.destroyed || this._timedOut) { + this._emitClose(); + return; + } + this.emit('error', error); + this._emitClose(); + }); + } + + setHeader(name, value) { + this._headers[normalizeHeaderName(name)] = value; + return this; + } + + getHeader(name) { + return this._headers[normalizeHeaderName(name)]; + } + + getHeaders() { + return { ...this._headers }; + } + + removeHeader(name) { + delete this._headers[normalizeHeaderName(name)]; + return this; + } + + setTimeout(msecs, callback) { + this._timeout = Number(msecs) || 0; + if (typeof callback === 'function') this.on('timeout', callback); + if (this._dispatchStarted && !this.destroyed) this._armTimeoutTimer(); + return this; + } + + setNoDelay() { + return this; + } + + setSocketKeepAlive() { + return this; + } + + write(chunk, encoding, callback) { + if (this.finished) throw new Error('write after end'); + this._bodyChunks.push(bufferFrom(chunk, typeof encoding === 'string' ? encoding : undefined)); + if (typeof encoding === 'function') callback = encoding; + if (typeof callback === 'function') callback(); + return true; + } + + end(chunk, encoding, callback) { + if (this.finished) return this; + if (typeof chunk === 'function') { + callback = chunk; + chunk = undefined; + encoding = undefined; + } else if (typeof encoding === 'function') { + callback = encoding; + encoding = undefined; + } + + if (chunk !== undefined && chunk !== null) { + this.write(chunk, encoding); + } + + this.finished = true; + this.emit('finish'); + if (typeof callback === 'function') callback(); + this._dispatch(); + return this; + } + + abort() { + return this.destroy(createAbortError('Request aborted')); + } + + destroy(error) { + if (this.destroyed) return this; + this.destroyed = true; + this.aborted = true; + this._clearTimeoutTimer(); + if (this._controller) this._controller.abort(error || createAbortError('Request destroyed')); + if (error) this.emit('error', error); + this._emitClose(); + return this; } } @@ -525,12 +940,16 @@ export function createServer(options, requestListener) { return new Server(options, requestListener); } -export function request() { - clientNotImplemented(); +export function request(input, options, callback) { + const normalized = normalizeRequestArgs(input, options, callback); + const requestOptions = buildRequestOptions(normalized.input, normalized.options); + return new ClientRequest(requestOptions, normalized.callback); } -export function get() { - clientNotImplemented(); +export function get(input, options, callback) { + const req = request(input, options, callback); + req.end(); + return req; } export { METHODS, STATUS_CODES } from 'ant:internal/http_metadata'; diff --git a/src/builtins/node/https.mjs b/src/builtins/node/https.mjs index f23a8b4..839471c 100644 --- a/src/builtins/node/https.mjs +++ b/src/builtins/node/https.mjs @@ -94,6 +94,42 @@ function clientNotImplemented() { throw new Error('node:https client transport is not implemented yet'); } +function createInvalidProtocolError(protocol) { + const error = new TypeError(`Protocol "${protocol}" not supported. Expected "https:"`); + error.code = 'ERR_INVALID_PROTOCOL'; + return error; +} + +function getProtocolFromInput(input) { + if (typeof input === 'string') return new URL(input).protocol; + if (!input || typeof input !== 'object') return undefined; + if (typeof input.href === 'string') return new URL(String(input)).protocol; + if (input.protocol === undefined || input.protocol === null) return undefined; + return String(input.protocol); +} + +function assertHttpsProtocol(protocol) { + if (protocol === undefined || protocol === null || protocol === '') return; + if (String(protocol) !== 'https:') throw createInvalidProtocolError(String(protocol)); +} + +function applyDefaultHttpsProtocol(input, options) { + const optionProtocol = options && typeof options === 'object' ? options.protocol : undefined; + assertHttpsProtocol(optionProtocol); + + if (typeof input === 'string' || (input && typeof input === 'object' && typeof input.href === 'string')) { + if (optionProtocol === undefined) assertHttpsProtocol(getProtocolFromInput(input)); + return [input, options]; + } + + if (typeof input === 'function' || input === undefined || input === null) { + return [{ protocol: 'https:' }, options]; + } + + assertHttpsProtocol(getProtocolFromInput(input)); + return [{ ...input, protocol: input.protocol ?? 'https:' }, options]; +} + // compatibility stub only export class Server extends http.Server { constructor(options, requestListener) { @@ -133,12 +169,14 @@ export function createServer(options, requestListener) { return new Server(options, requestListener); } -export function request() { - clientNotImplemented(); +export function request(input, options, callback) { + const [resolvedInput, resolvedOptions] = applyDefaultHttpsProtocol(input, options); + return http.request(resolvedInput, resolvedOptions, callback); } -export function get() { - clientNotImplemented(); +export function get(input, options, callback) { + const [resolvedInput, resolvedOptions] = applyDefaultHttpsProtocol(input, options); + return http.get(resolvedInput, resolvedOptions, callback); } export { METHODS, STATUS_CODES } from 'ant:internal/http_metadata'; diff --git a/src/gc/gc.c b/src/gc/gc.c index 1c3708e..63fd64e 100644 --- a/src/gc/gc.c +++ b/src/gc/gc.c @@ -53,23 +53,45 @@ static void gc_adapt_major_interval(size_t live_before, size_t live_after) { } static void gc_mark_str(ant_t *js, ant_value_t v) { + static const void *dispatch[] = { + [STR_HEAP_TAG_FLAT] = &&l_flat, + [STR_HEAP_TAG_ROPE] = &&l_rope, + [STR_HEAP_TAG_BUILDER] = &&l_builder, + }; + if (v <= NANBOX_PREFIX) return; uint8_t t = (v >> NANBOX_TYPE_SHIFT) & NANBOX_TYPE_MASK; - if (t != T_STR) return; + uintptr_t data = (uintptr_t)(v & NANBOX_DATA_MASK); - - if (data & 1ULL) { - ant_rope_heap_t *rope = (ant_rope_heap_t *)(data & ~1ULL); - - if (!rope) return; - if (!gc_ropes_mark(rope)) return; - + uintptr_t tag = data & STR_HEAP_TAG_MASK; + + if (tag < sizeof(dispatch) / sizeof(*dispatch) && dispatch[tag]) + goto *dispatch[tag]; + goto l_flat; + + l_rope: { + ant_rope_heap_t *rope = (ant_rope_heap_t *)(data & ~STR_HEAP_TAG_MASK); + if (!rope || !gc_ropes_mark(rope)) return; gc_mark_str(js, rope->left); gc_mark_str(js, rope->right); gc_mark_str(js, rope->cached); - - } else if (data) gc_strings_mark(js, (const void *)data); + return; + } + + l_builder: { + ant_string_builder_t *builder = (ant_string_builder_t *)(data & ~STR_HEAP_TAG_MASK); + if (!builder || !gc_ropes_mark(builder)) return; + gc_mark_value(js, builder->cached); + for (ant_builder_chunk_t *chunk = builder->head; chunk; chunk = chunk->next) { + if (gc_ropes_mark(chunk)) gc_mark_value(js, chunk->value); + } + return; + } + + l_flat: + if (data) gc_strings_mark(js, (const void *)data); + return; } void gc_run(ant_t *js) { diff --git a/src/modules/url.c b/src/modules/url.c index 59f35b9..0d9a60c 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -1602,6 +1602,7 @@ ant_value_t url_library(ant_t *js) { js_set(js, lib, "URLSearchParams",js_get(js, glob, "URLSearchParams")); js_set(js, lib, "fileURLToPath", js_mkfun(builtin_fileURLToPath)); js_set(js, lib, "pathToFileURL", js_mkfun(builtin_pathToFileURL)); + js_set(js, lib, "parse", js_mkfun(url_parse)); js_set(js, lib, "format", js_mkfun(builtin_url_format)); js_set(js, lib, "default", lib); diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 073fb0b..c8af6b3 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -883,16 +883,125 @@ static void emit_put_local_typed(sv_compiler_t *c, int local_idx, uint8_t type) set_local_inferred_type(c, local_idx, type); } +static inline void emit_slot_op(sv_compiler_t *c, sv_op_t op, uint16_t slot) { + emit_op(c, op); + emit_u16(c, slot); +} + static void emit_put_local(sv_compiler_t *c, int local_idx) { emit_put_local_typed(c, local_idx, SV_TI_UNKNOWN); } +static uint8_t infer_expr_type(sv_compiler_t *c, sv_ast_t *node); + static void emit_get_local(sv_compiler_t *c, int local_idx) { int slot = local_idx - c->param_locals; if (slot <= 255) { emit_op(c, OP_GET_LOCAL8); emit(c, (uint8_t)slot); } else { emit_op(c, OP_GET_LOCAL); emit_u16(c, (uint16_t)slot); } } +static bool match_self_append_local( + sv_compiler_t *c, sv_ast_t *node, + int *out_local_idx, uint16_t *out_slot, sv_ast_t **out_rhs +) { + if (!c || !node || node->type != N_ASSIGN || !node->left || node->left->type != N_IDENT) + return false; + if (c->with_depth > 0) return false; + + int local = resolve_local(c, node->left->str, node->left->len); + if (local < 0 || c->locals[local].is_const) return false; + if (c->locals[local].is_tdz) return false; + if (c->locals[local].depth == -1 && has_implicit_arguments_obj(c)) return false; + + sv_ast_t *rhs = NULL; + if (node->op == TOK_PLUS_ASSIGN) rhs = node->right; + else if ( + node->op == TOK_ASSIGN && + node->right && node->right->type == N_BINARY && node->right->op == TOK_PLUS && + node->right->left && node->right->left->type == N_IDENT && + node->right->left->len == node->left->len && + memcmp(node->right->left->str, node->left->str, node->left->len) == 0 + ) rhs = node->right->right; + + if (!rhs) return false; + uint8_t local_type = get_local_inferred_type(c, local); + uint8_t rhs_type = infer_expr_type(c, rhs); + if (local_type != SV_TI_STR && rhs_type != SV_TI_STR) + return false; + + if (out_local_idx) *out_local_idx = local; + if (out_slot) *out_slot = (uint16_t)local_to_frame_slot(c, local); + if (out_rhs) *out_rhs = rhs; + + return true; +} + +static bool is_self_append_inplace_safe_ident(sv_compiler_t *c, sv_ast_t *node) { + if (!c || !node || node->type != N_IDENT) return false; + + if (resolve_local(c, node->str, node->len) != -1) return true; + if (resolve_upvalue(c, node->str, node->len) != -1) return true; + + if (is_ident_str(node->str, node->len, "arguments", 9)) { + if (has_implicit_arguments_obj(c)) return true; + if (c->is_arrow && resolve_arguments_upvalue(c) != -1) return true; + } + + if (c->is_arrow && is_ident_str(node->str, node->len, "super", 5)) + return resolve_super_upvalue(c) != -1; + + if (has_module_import_binding(c) && is_ident_str(node->str, node->len, "import", 6)) + return true; + + return false; +} + +static bool is_self_append_inplace_safe_expr(sv_compiler_t *c, sv_ast_t *node) { + if (!node) return false; + + switch (node->type) { + case N_NUMBER: + case N_STRING: + case N_BIGINT: + case N_BOOL: + case N_NULL: + case N_UNDEF: + return true; + + case N_IDENT: + return is_self_append_inplace_safe_ident(c, node); + + case N_BINARY: return + is_self_append_inplace_safe_expr(c, node->left) && + is_self_append_inplace_safe_expr(c, node->right); + + case N_UNARY: + case N_TYPEOF: + case N_VOID: + return is_self_append_inplace_safe_expr(c, node->left); + + default: + return false; + } +} + +static bool compile_self_append_stmt(sv_compiler_t *c, sv_ast_t *node) { + int local = -1; + uint16_t slot = 0; + sv_ast_t *rhs = NULL; + if (!match_self_append_local(c, node, &local, &slot, &rhs)) return false; + if (is_self_append_inplace_safe_expr(c, rhs)) { + compile_expr(c, rhs); + emit_slot_op(c, OP_STR_APPEND_LOCAL, slot); + } else { + emit_slot_op(c, OP_GET_SLOT_RAW, slot); + compile_expr(c, rhs); + emit_slot_op(c, OP_STR_ALC_SNAPSHOT, slot); + } + set_local_inferred_type(c, local, SV_TI_UNKNOWN); + return true; +} + static inline bool is_ident_name(sv_ast_t *node, const char *name) { size_t n = strlen(name); @@ -1612,6 +1721,14 @@ void compile_update(sv_compiler_t *c, sv_ast_t *node) { void compile_assign(sv_compiler_t *c, sv_ast_t *node) { sv_ast_t *target = node->left; uint8_t op = node->op; + int append_local = -1; + uint16_t append_slot = 0; + sv_ast_t *append_rhs = NULL; + + bool can_append_builder = match_self_append_local( + c, node, &append_local, + &append_slot, &append_rhs + ); if (op == TOK_ASSIGN) { if (target->type == N_MEMBER && !(target->flags & 1)) { @@ -1622,7 +1739,7 @@ void compile_assign(sv_compiler_t *c, sv_ast_t *node) { emit_atom_idx_op(c, OP_PUT_FIELD, (uint32_t)atom); return; } - + if (target->type == N_MEMBER && (target->flags & 1)) { compile_expr(c, target->left); compile_expr(c, target->right); @@ -1631,6 +1748,20 @@ void compile_assign(sv_compiler_t *c, sv_ast_t *node) { emit_op(c, OP_PUT_ELEM); return; } + + if (can_append_builder) { + if (is_self_append_inplace_safe_expr(c, append_rhs)) { + compile_expr(c, append_rhs); + emit_slot_op(c, OP_STR_APPEND_LOCAL, append_slot); + } else { + emit_slot_op(c, OP_GET_SLOT_RAW, append_slot); + compile_expr(c, append_rhs); + emit_slot_op(c, OP_STR_ALC_SNAPSHOT, append_slot); + } + emit_get_var(c, target->str, target->len); + set_local_inferred_type(c, append_local, SV_TI_UNKNOWN); + return; + } compile_expr(c, node->right); compile_lhs_set(c, target, true); @@ -1642,6 +1773,20 @@ void compile_assign(sv_compiler_t *c, sv_ast_t *node) { uint8_t lhs_type = (lhs_local >= 0) ? get_local_inferred_type(c, lhs_local) : SV_TI_UNKNOWN; uint8_t rhs_type = infer_expr_type(c, node->right); + if (can_append_builder) { + if (is_self_append_inplace_safe_expr(c, append_rhs)) { + compile_expr(c, append_rhs); + emit_slot_op(c, OP_STR_APPEND_LOCAL, append_slot); + } else { + emit_slot_op(c, OP_GET_SLOT_RAW, append_slot); + compile_expr(c, append_rhs); + emit_slot_op(c, OP_STR_ALC_SNAPSHOT, append_slot); + } + emit_get_var(c, target->str, target->len); + set_local_inferred_type(c, append_local, SV_TI_UNKNOWN); + return; + } + if (op == TOK_PLUS_ASSIGN) { int slot = resolve_local_slot(c, target->str, target->len); if (slot >= 0 && !c->locals[slot + c->param_locals].is_const) { @@ -2727,6 +2872,12 @@ void compile_stmt(sv_compiler_t *c, sv_ast_t *node) { compile_label(c, node); break; + case N_ASSIGN: + if (compile_self_append_stmt(c, node)) break; + compile_expr(c, node); + emit_op(c, OP_POP); + break; + case N_FUNC: if (node->str && !(node->flags & FN_ARROW)) break; compile_expr(c, node); diff --git a/src/silver/engine.c b/src/silver/engine.c index 26585ab..e486101 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -176,7 +176,270 @@ void js_set_error_site_from_vm_top(ant_t *js) { js_set_error_site_from_bc(js, func, bc_off, func->filename); } +// TODO: move to strings.c +static inline bool sv_builder_has_cached_value(const ant_string_builder_t *builder) { + return builder && builder->cached != js_mkundef(); +} + +static inline ant_flat_string_t *sv_string_builder_flat_ptr(ant_value_t value) { + if (vtype(value) != T_STR || str_is_heap_rope(value) || str_is_heap_builder(value)) return NULL; + return (ant_flat_string_t *)(uintptr_t)vdata(value); +} + +static inline ant_string_builder_t *sv_string_builder_heap_ptr(ant_value_t value) { + if (vtype(value) != T_STR || !str_is_heap_builder(value)) return NULL; + return ant_str_builder_ptr(value); +} + +static inline uint8_t sv_builder_chunk_ascii_state(ant_flat_string_t *flat) { + if (!flat) return STR_ASCII_UNKNOWN; + if (flat->is_ascii == STR_ASCII_UNKNOWN) + flat->is_ascii = str_detect_ascii_bytes(flat->bytes, (size_t)flat->len); + return flat->is_ascii; +} + +static inline void sv_builder_note_ascii(ant_string_builder_t *builder, uint8_t state) { + if (!builder) return; + if (state == STR_ASCII_NO) builder->ascii_state = STR_ASCII_NO; + else if (builder->ascii_state != STR_ASCII_NO && state == STR_ASCII_YES) + builder->ascii_state = STR_ASCII_YES; +} + +static inline void sv_builder_record_flat(ant_string_builder_t *builder, ant_flat_string_t *flat) { + if (!builder || !flat) return; + builder->len += flat->len; + sv_builder_note_ascii(builder, sv_builder_chunk_ascii_state(flat)); +} + +static ant_value_t sv_builder_normalize_chunk(ant_t *js, ant_value_t value) { + if (vtype(value) != T_STR) return js_mkerr(js, "string builder expects string chunk"); + return str_materialize(js, value); +} + +static ant_value_t sv_builder_push_chunk_value( + ant_t *js, ant_string_builder_t *builder, ant_value_t chunk +) { + if (!builder) return js_mkerr(js, "string builder chunk allocation failed"); + ant_builder_chunk_t *node = (ant_builder_chunk_t *)js_type_alloc( + js, ANT_ALLOC_ROPE, sizeof(*node), _Alignof(ant_builder_chunk_t) + ); + if (!node) return js_mkerr(js, "string builder chunk allocation failed"); + node->next = NULL; + node->value = chunk; + if (builder->chunk_tail) builder->chunk_tail->next = node; + else builder->head = node; + builder->chunk_tail = node; + return js_mkundef(); +} + +static ant_value_t sv_builder_flush_tail( + ant_t *js, ant_string_builder_t *builder +) { + if (!builder || builder->tail_len == 0) return js_mkundef(); + ant_value_t tail = js_mkstr(js, builder->tail, builder->tail_len); + if (is_err(tail)) return tail; + ant_value_t push = sv_builder_push_chunk_value(js, builder, tail); + if (is_err(push)) return push; + builder->tail_len = 0; + return js_mkundef(); +} + +static ant_value_t sv_builder_append_flat( + ant_t *js, ant_string_builder_t *builder, ant_value_t chunk +) { + ant_flat_string_t *flat = sv_string_builder_flat_ptr(chunk); + if (!flat) return js_mkerr(js, "string builder received non-flat string"); + if (flat->len == 0) return js_mkundef(); + + if ( + flat->len <= STR_BUILDER_TAIL_CAP && + builder->tail_len + flat->len <= STR_BUILDER_TAIL_CAP + ) { + memcpy(builder->tail + builder->tail_len, flat->bytes, (size_t)flat->len); + builder->tail_len = (uint16_t)(builder->tail_len + flat->len); + sv_builder_record_flat(builder, flat); + return js_mkundef(); + } + + ant_value_t flush = sv_builder_flush_tail(js, builder); + if (is_err(flush)) return flush; + ant_value_t push = sv_builder_push_chunk_value(js, builder, chunk); + + if (is_err(push)) return push; + sv_builder_record_flat(builder, flat); + + return js_mkundef(); +} + +static ant_value_t sv_string_builder_new(ant_t *js) { + ant_string_builder_t *builder = (ant_string_builder_t *)js_type_alloc( + js, ANT_ALLOC_ROPE, sizeof(*builder), _Alignof(ant_string_builder_t) + ); + if (!builder) return js_mkerr(js, "string builder allocation failed"); + memset(builder, 0, sizeof(*builder)); + builder->cached = js_mkundef(); + builder->ascii_state = STR_ASCII_YES; + return ant_mkbuilder_value(builder); +} + +static inline void sv_record_slot_feedback( + sv_frame_t *frame, sv_func_t *func, uint16_t slot_idx, ant_value_t value +) { + if (!frame || !func) return; + if ((int)slot_idx < func->param_count) return; + sv_tfb_record_local(func, (int)(slot_idx - func->param_count), value); +} + +bool sv_slot_has_open_upvalue(sv_vm_t *vm, ant_value_t *slot) { + if (!vm || !slot) return false; + for (sv_upvalue_t *uv = vm->open_upvalues; uv; uv = uv->next) + if (uv->location == slot) return true; + return false; +} + +ant_value_t sv_string_builder_read_value(ant_t *js, ant_value_t value) { + if (vtype(value) == T_STR && str_is_heap_builder(value)) + return str_materialize(js, value); + return value; +} + +static ant_value_t sv_slot_generic_add_store( + sv_vm_t *vm, ant_t *js, ant_value_t *slot, ant_value_t lhs, ant_value_t rhs +) { + vm->stack[vm->sp++] = lhs; + vm->stack[vm->sp++] = rhs; + ant_value_t err = sv_op_add(vm, js); + if (is_err(err)) return err; + *slot = vm->stack[--vm->sp]; + return js_mkundef(); +} + +ant_value_t sv_string_builder_flush_slot( + sv_vm_t *vm, ant_t *js, sv_frame_t *frame, uint16_t slot_idx +) { + ant_value_t *slot = sv_frame_slot_ptr(frame, slot_idx); + if (!slot || vtype(*slot) != T_STR || !str_is_heap_builder(*slot)) return js_mkundef(); + + ant_value_t out = str_materialize(js, *slot); + if (is_err(out)) return out; + *slot = out; + sv_record_slot_feedback(frame, frame->func, slot_idx, out); + return out; +} + +ant_value_t sv_string_builder_append_slot( + sv_vm_t *vm, ant_t *js, sv_frame_t *frame, + sv_func_t *func, uint16_t slot_idx, ant_value_t rhs +) { + ant_value_t *slot = sv_frame_slot_ptr(frame, slot_idx); + if (!slot) return js_mkerr(js, "invalid string builder slot"); + + ant_value_t lhs = *slot; + ant_string_builder_t *builder = sv_string_builder_heap_ptr(lhs); + + if (builder) { + ant_value_t rhs_str = coerce_to_str_concat(js, rhs); + if (is_err(rhs_str)) return rhs_str; + rhs_str = sv_builder_normalize_chunk(js, rhs_str); + if (is_err(rhs_str)) return rhs_str; + builder->cached = js_mkundef(); + ant_value_t append_err = sv_builder_append_flat(js, builder, rhs_str); + if (is_err(append_err)) return append_err; + sv_record_slot_feedback(frame, func, slot_idx, lhs); + return js_mkundef(); + } + + if (vtype(lhs) == T_NUM && vtype(rhs) == T_NUM) { + *slot = tov(tod(lhs) + tod(rhs)); + sv_record_slot_feedback(frame, func, slot_idx, *slot); + return js_mkundef(); + } + + ant_value_t lu = unwrap_primitive(js, lhs); + ant_value_t ru = unwrap_primitive(js, rhs); + bool string_concat = is_non_numeric(lu) || is_non_numeric(ru); + if (!string_concat) { + ant_value_t add_err = sv_slot_generic_add_store(vm, js, slot, lhs, rhs); + if (is_err(add_err)) return add_err; + sv_record_slot_feedback(frame, func, slot_idx, *slot); + return js_mkundef(); + } + + ant_value_t lhs_str = coerce_to_str_concat(js, lhs); + if (is_err(lhs_str)) return lhs_str; + ant_value_t rhs_str = coerce_to_str_concat(js, rhs); + if (is_err(rhs_str)) return rhs_str; + lhs_str = sv_builder_normalize_chunk(js, lhs_str); + if (is_err(lhs_str)) return lhs_str; + rhs_str = sv_builder_normalize_chunk(js, rhs_str); + if (is_err(rhs_str)) return rhs_str; + + ant_value_t builder_value = sv_string_builder_new(js); + if (is_err(builder_value)) return builder_value; + builder = sv_string_builder_heap_ptr(builder_value); + + ant_value_t append_lhs = sv_builder_append_flat(js, builder, lhs_str); + if (is_err(append_lhs)) return append_lhs; + ant_value_t append_rhs = sv_builder_append_flat(js, builder, rhs_str); + if (is_err(append_rhs)) return append_rhs; + *slot = builder_value; + sv_record_slot_feedback(frame, func, slot_idx, *slot); + + return js_mkundef(); +} +ant_value_t sv_string_builder_append_snapshot_slot( + sv_vm_t *vm, ant_t *js, sv_frame_t *frame, + sv_func_t *func, uint16_t slot_idx, ant_value_t lhs, ant_value_t rhs +) { + ant_value_t *slot = sv_frame_slot_ptr(frame, slot_idx); + if (!slot) return js_mkerr(js, "invalid string builder slot"); + + // the snapshot path is only semantically required if the slot changed while + // evaluating the RHS. when it did not, delegate to the normal append path so + // active builders can keep appending in place instead of rebuilding. + if (*slot == lhs) + return sv_string_builder_append_slot(vm, js, frame, func, slot_idx, rhs); + + if (vtype(lhs) == T_NUM && vtype(rhs) == T_NUM) { + *slot = tov(tod(lhs) + tod(rhs)); + sv_record_slot_feedback(frame, func, slot_idx, *slot); + return js_mkundef(); + } + + ant_value_t lu = unwrap_primitive(js, lhs); + ant_value_t ru = unwrap_primitive(js, rhs); + + bool string_concat = is_non_numeric(lu) || is_non_numeric(ru); + if (!string_concat) { + ant_value_t add_err = sv_slot_generic_add_store(vm, js, slot, lhs, rhs); + if (is_err(add_err)) return add_err; + sv_record_slot_feedback(frame, func, slot_idx, *slot); + return js_mkundef(); + } + + ant_value_t lhs_str = coerce_to_str_concat(js, lhs); + if (is_err(lhs_str)) return lhs_str; + ant_value_t rhs_str = coerce_to_str_concat(js, rhs); + if (is_err(rhs_str)) return rhs_str; + lhs_str = sv_builder_normalize_chunk(js, lhs_str); + if (is_err(lhs_str)) return lhs_str; + rhs_str = sv_builder_normalize_chunk(js, rhs_str); + if (is_err(rhs_str)) return rhs_str; + + ant_value_t builder_value = sv_string_builder_new(js); + if (is_err(builder_value)) return builder_value; + ant_string_builder_t *builder = sv_string_builder_heap_ptr(builder_value); + + ant_value_t append_lhs = sv_builder_append_flat(js, builder, lhs_str); + if (is_err(append_lhs)) return append_lhs; + ant_value_t append_rhs = sv_builder_append_flat(js, builder, rhs_str); + if (is_err(append_rhs)) return append_rhs; + *slot = builder_value; + sv_record_slot_feedback(frame, func, slot_idx, *slot); + + return js_mkundef(); +} void sv_vm_visit_frame_funcs(sv_vm_t *vm, void (*visitor)(void *, sv_func_t *), void *ctx) { if (!vm) return; @@ -203,6 +466,12 @@ static inline void sv_sync_frame_locals( *bp = (*frame)->bp; *lp = (*frame)->lp; } +static inline void sv_drop_frame_runtime_state(ant_t *js, sv_frame_t *frame) { +if (frame && vtype(frame->arguments_obj) != T_UNDEF) { + js_arguments_detach(js, frame->arguments_obj); + frame->arguments_obj = js_mkundef(); +}} + static inline ant_value_t sv_stage_frame_args( sv_vm_t *vm, ant_t *js, sv_func_t *func, ant_value_t *args, int argc, ant_value_t **out_bp, ant_value_t **out_lp @@ -486,6 +755,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant // TODO: shorthand? sv_frame_t *frame = &vm->frames[vm->fp]; if (!resuming) { + sv_drop_frame_runtime_state(js, frame); frame->ip = ip; frame->func = func; frame->this = sv_normalize_this_for_frame(js, func, this); @@ -498,6 +768,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant frame->completion.kind = SV_COMPLETION_NONE; frame->completion.value = js_mkundef(); frame->with_obj = js_mkundef(); + frame->arguments_obj = js_mkundef(); } else { func = frame->func; ip = frame->ip; @@ -675,7 +946,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant L_ARRAY: { sv_op_array(vm, js, ip); NEXT(3); } L_REGEXP: { sv_op_regexp(vm, js); NEXT(1); } - L_CLOSURE: { sv_op_closure(vm, js, frame, func, ip); NEXT(5); } + L_CLOSURE: { VM_CHECK(sv_op_closure(vm, js, frame, func, ip)); NEXT(5); } L_POP: { sv_op_pop(vm); NEXT(1); } L_DUP: { sv_op_dup(vm); NEXT(1); } @@ -691,26 +962,27 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant L_SWAP_UNDER: { sv_op_swap_under(vm); NEXT(1); } L_ROT4_UNDER: { sv_op_rot4_under(vm); NEXT(1); } - L_GET_LOCAL: { sv_op_get_local(vm, lp, ip); NEXT(3); } - L_PUT_LOCAL: { sv_op_put_local(vm, lp, func, ip); NEXT(3); } - L_SET_LOCAL: { sv_op_set_local(vm, lp, func, ip); NEXT(3); } - L_GET_LOCAL8: { sv_op_get_local8(vm, lp, ip); NEXT(2); } - L_PUT_LOCAL8: { sv_op_put_local8(vm, lp, func, ip); NEXT(2); } - L_SET_LOCAL8: { sv_op_set_local8(vm, lp, func, ip); NEXT(2); } - L_SET_LOCAL_UNDEF: { sv_op_set_local_undef(lp, ip); NEXT(3); } + L_GET_LOCAL: { VM_CHECK(sv_op_get_local(vm, lp, js, frame, ip)); NEXT(3); } + L_PUT_LOCAL: { sv_op_put_local(vm, lp, frame, func, ip); NEXT(3); } + L_SET_LOCAL: { sv_op_set_local(vm, lp, frame, func, ip); NEXT(3); } + L_GET_LOCAL8: { VM_CHECK(sv_op_get_local8(vm, lp, js, frame, ip)); NEXT(2); } + L_PUT_LOCAL8: { sv_op_put_local8(vm, lp, frame, func, ip); NEXT(2); } + L_SET_LOCAL8: { sv_op_set_local8(vm, lp, frame, func, ip); NEXT(2); } + L_SET_LOCAL_UNDEF: { sv_op_set_local_undef(frame, lp, ip); NEXT(3); } - L_GET_LOCAL_CHK: { VM_CHECK(sv_op_get_local_chk(vm, lp, js, func, ip)); NEXT(7); } - L_PUT_LOCAL_CHK: { VM_CHECK(sv_op_put_local_chk(vm, lp, js, func, ip)); NEXT(7); } + L_GET_LOCAL_CHK: { VM_CHECK(sv_op_get_local_chk(vm, lp, js, frame, func, ip)); NEXT(7); } + L_PUT_LOCAL_CHK: { VM_CHECK(sv_op_put_local_chk(vm, lp, js, frame, func, ip)); NEXT(7); } + L_GET_SLOT_RAW: { VM_CHECK(sv_op_get_slot_raw(vm, js, frame, ip)); NEXT(3); } - L_GET_ARG: { sv_op_get_arg(vm, frame, ip); NEXT(3); } - L_PUT_ARG: { sv_op_put_arg(vm, frame, ip); NEXT(3); } - L_SET_ARG: { sv_op_set_arg(vm, frame, ip); NEXT(3); } - L_REST: { sv_op_rest(vm, frame, js, ip); NEXT(3); } + L_GET_ARG: { VM_CHECK(sv_op_get_arg(vm, js, frame, ip)); NEXT(3); } + L_PUT_ARG: { sv_op_put_arg(vm, js, frame, ip); NEXT(3); } + L_SET_ARG: { sv_op_set_arg(vm, js, frame, ip); NEXT(3); } + L_REST: { sv_op_rest(vm, frame, js, ip); NEXT(3); } L_GET_UPVAL: { VM_CHECK(sv_op_get_upval(vm, frame, js, ip)); NEXT(3); } L_PUT_UPVAL: { sv_op_put_upval(vm, frame, ip); NEXT(3); } L_SET_UPVAL: { sv_op_set_upval(vm, frame, ip); NEXT(3); } - L_CLOSE_UPVAL: { sv_op_close_upval(vm, frame, ip); NEXT(3); } + L_CLOSE_UPVAL: { VM_CHECK(sv_op_close_upval(vm, frame, ip)); NEXT(3); } L_GET_GLOBAL: { VM_CHECK(sv_op_get_global(vm, js, func, ip)); NEXT(7); } L_GET_GLOBAL_UNDEF: { sv_op_get_global_undef(vm, js, func, ip); NEXT(7); } @@ -782,11 +1054,15 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant NEXT(1); } - L_MUL: { sv_tfb_record2(func, ip, vm->stack[vm->sp-2], vm->stack[vm->sp-1]); VM_CHECK(sv_op_mul(vm, js)); NEXT(1); } - L_DIV: { sv_tfb_record2(func, ip, vm->stack[vm->sp-2], vm->stack[vm->sp-1]); VM_CHECK(sv_op_div(vm, js)); NEXT(1); } - L_MOD: { sv_tfb_record2(func, ip, vm->stack[vm->sp-2], vm->stack[vm->sp-1]); VM_CHECK(sv_op_mod(vm, js)); NEXT(1); } - L_NEG: { sv_tfb_record1(func, ip, vm->stack[vm->sp-1]); VM_CHECK(sv_op_neg(vm, js)); NEXT(1); } + L_MUL: { sv_tfb_record2(func, ip, vm->stack[vm->sp-2], vm->stack[vm->sp-1]); VM_CHECK(sv_op_mul(vm, js)); NEXT(1); } + L_DIV: { sv_tfb_record2(func, ip, vm->stack[vm->sp-2], vm->stack[vm->sp-1]); VM_CHECK(sv_op_div(vm, js)); NEXT(1); } + L_MOD: { sv_tfb_record2(func, ip, vm->stack[vm->sp-2], vm->stack[vm->sp-1]); VM_CHECK(sv_op_mod(vm, js)); NEXT(1); } + L_NEG: { sv_tfb_record1(func, ip, vm->stack[vm->sp-1]); VM_CHECK(sv_op_neg(vm, js)); NEXT(1); } L_ADD_LOCAL: { sv_tfb_record2(func, ip, lp[sv_get_u8(ip+1)], vm->stack[vm->sp-1]); VM_CHECK(sv_op_add_local(vm, lp, js, func, ip)); NEXT(2); } + + L_STR_APPEND_LOCAL: { VM_CHECK(sv_op_str_append_local(vm, js, frame, func, ip)); NEXT(3); } + L_STR_ALC_SNAPSHOT: { VM_CHECK(sv_op_str_append_local_snapshot(vm, js, frame, func, ip)); NEXT(3); } + L_STR_FLUSH_LOCAL: { VM_CHECK(sv_op_str_flush_local(vm, js, frame, ip)); NEXT(3); } L_EXP: { VM_CHECK(sv_op_exp(vm, js)); NEXT(1); } L_UPLUS: { VM_CHECK(sv_op_uplus(vm, js)); NEXT(1); } @@ -794,8 +1070,9 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant L_DEC: { sv_op_dec(vm); NEXT(1); } L_POST_INC: { sv_op_post_inc(vm); NEXT(1); } L_POST_DEC: { sv_op_post_dec(vm); NEXT(1); } - L_INC_LOCAL: { sv_op_inc_local(vm, lp, func, ip); NEXT(2); } - L_DEC_LOCAL: { sv_op_dec_local(vm, lp, func, ip); NEXT(2); } + + L_INC_LOCAL: { VM_CHECK(sv_op_inc_local(lp, js, func, ip)); NEXT(2); } + L_DEC_LOCAL: { VM_CHECK(sv_op_dec_local(lp, js, func, ip)); NEXT(2); } L_EQ: { sv_op_eq(vm, js); NEXT(1); } L_NE: { sv_op_ne(vm, js); NEXT(1); } @@ -984,6 +1261,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant frame->handler_base = vm->handler_depth; frame->handler_top = vm->handler_depth; frame->argc = call_argc; + frame->arguments_obj = js_mkundef(); ant_value_t *call_bp = NULL; ant_value_t *call_lp = NULL; ant_value_t call_stage_err = sv_stage_frame_args( @@ -1074,6 +1352,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant frame->handler_base = vm->handler_depth; frame->handler_top = vm->handler_depth; frame->argc = call_argc; + frame->arguments_obj = js_mkundef(); ant_value_t *call_bp = NULL; ant_value_t *call_lp = NULL; ant_value_t call_stage_err = sv_stage_frame_args( @@ -1198,6 +1477,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant ant_value_t call_func = vm->stack[vm->sp - call_argc - 1]; sv_closure_t *closure = js_func_closure(call_func); if (frame->bp && vm->open_upvalues) sv_close_upvalues_from_slot(vm, frame->bp); + sv_drop_frame_runtime_state(js, frame); vm->sp = frame->prev_sp; int arg_slots = ( (int)call_argc > closure->func->param_count) @@ -1218,6 +1498,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant frame->argc = call_argc; frame->handler_base = vm->handler_depth; frame->handler_top = vm->handler_depth; + frame->arguments_obj = js_mkundef(); frame->bp = base; frame->lp = new_lp; frame->upvalues = closure->upvalues; @@ -1255,6 +1536,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vm_result = r; goto sv_leave; } + sv_drop_frame_runtime_state(js, frame); vm->fp--; frame = &vm->frames[vm->fp]; func = frame->func; @@ -1285,6 +1567,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vm_result = r; goto sv_leave; } + sv_drop_frame_runtime_state(js, frame); vm->fp--; frame = &vm->frames[vm->fp]; func = frame->func; @@ -1315,6 +1598,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vm_result = r; goto sv_leave; } + sv_drop_frame_runtime_state(js, frame); vm->fp--; frame = &vm->frames[vm->fp]; func = frame->func; @@ -1357,6 +1641,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vm_result = completion; goto sv_leave; } + sv_drop_frame_runtime_state(js, frame); vm->fp--; frame = &vm->frames[vm->fp]; func = frame->func; @@ -1543,6 +1828,8 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant ant_value_t *drop_bp = vm->frames[f].bp; if (drop_bp) sv_close_upvalues_from_slot(vm, drop_bp); }} + for (int f = vm->fp; f >= entry_fp; f--) + sv_drop_frame_runtime_state(js, &vm->frames[f]); vm->fp = entry_fp; vm->sp = vm->frames[entry_fp].prev_sp; vm->handler_depth = vm->frames[entry_fp].handler_base; diff --git a/src/silver/glue.c b/src/silver/glue.c index 0bc3fa9..01f71c8 100644 --- a/src/silver/glue.c +++ b/src/silver/glue.c @@ -51,6 +51,41 @@ ant_value_t jit_helper_mod(sv_vm_t *vm, ant_t *js, ant_value_t l, ant_value_t r) return SV_JIT_BAILOUT; } +ant_value_t jit_helper_str_append_local( + sv_vm_t *vm, ant_t *js, sv_func_t *func, + ant_value_t *locals, uint16_t local_idx, ant_value_t rhs +) { + if (!func || !locals || local_idx >= (uint16_t)func->max_locals) + return SV_JIT_BAILOUT; + + sv_frame_t frame = { + .func = func, + .lp = locals, + .argc = func->param_count, + }; + + uint16_t slot_idx = (uint16_t)(func->param_count + local_idx); + return sv_string_builder_append_slot(vm, js, &frame, func, slot_idx, rhs); +} + +ant_value_t jit_helper_str_append_local_snapshot( + sv_vm_t *vm, ant_t *js, sv_func_t *func, + ant_value_t *locals, uint16_t local_idx, + ant_value_t lhs, ant_value_t rhs +) { + if (!func || !locals || local_idx >= (uint16_t)func->max_locals) + return SV_JIT_BAILOUT; + + sv_frame_t frame = { + .func = func, + .lp = locals, + .argc = func->param_count, + }; + + uint16_t slot_idx = (uint16_t)(func->param_count + local_idx); + return sv_string_builder_append_snapshot_slot(vm, js, &frame, func, slot_idx, lhs, rhs); +} + ant_value_t jit_helper_lt(sv_vm_t *vm, ant_t *js, ant_value_t l, ant_value_t r) { if (vtype(l) == T_NUM && vtype(r) == T_NUM) return js_bool(tod(l) < tod(r)); return SV_JIT_BAILOUT; diff --git a/src/silver/ops/arithmetic.h b/src/silver/ops/arithmetic.h index 6a5c928..7a0b488 100644 --- a/src/silver/ops/arithmetic.h +++ b/src/silver/ops/arithmetic.h @@ -187,36 +187,63 @@ static inline void sv_op_post_dec(sv_vm_t *vm) { vm->stack[vm->sp++] = tov(tod(old) - 1.0); } -static inline void sv_op_inc_local(sv_vm_t *vm, ant_value_t *lp, sv_func_t *func, uint8_t *ip) { +static inline ant_value_t sv_op_inc_local(ant_value_t *lp, ant_t *js, sv_func_t *func, uint8_t *ip) { uint8_t idx = sv_get_u8(ip + 1); ant_value_t *slot = &lp[idx]; + + if (vtype(*slot) == T_STR && str_is_heap_builder(*slot)) { + ant_value_t out = str_materialize(js, *slot); + if (is_err(out)) return out; + *slot = out; + } + *slot = tov(tod(*slot) + 1.0); sv_tfb_record_local(func, (int)idx, *slot); + + return js_mkundef(); } -static inline void sv_op_dec_local(sv_vm_t *vm, ant_value_t *lp, sv_func_t *func, uint8_t *ip) { +static inline ant_value_t sv_op_dec_local(ant_value_t *lp, ant_t *js, sv_func_t *func, uint8_t *ip) { uint8_t idx = sv_get_u8(ip + 1); ant_value_t *slot = &lp[idx]; + + if (vtype(*slot) == T_STR && str_is_heap_builder(*slot)) { + ant_value_t out = str_materialize(js, *slot); + if (is_err(out)) return out; + *slot = out; + } + *slot = tov(tod(*slot) - 1.0); sv_tfb_record_local(func, (int)idx, *slot); - (void)vm; + + return js_mkundef(); } static inline ant_value_t sv_op_add_local(sv_vm_t *vm, ant_value_t *lp, ant_t *js, sv_func_t *func, uint8_t *ip) { uint8_t idx = sv_get_u8(ip + 1); ant_value_t *slot = &lp[idx]; + + if (vtype(*slot) == T_STR && str_is_heap_builder(*slot)) { + ant_value_t out = str_materialize(js, *slot); + if (is_err(out)) return out; + *slot = out; + } + ant_value_t val = vm->stack[--vm->sp]; if (vtype(*slot) == T_NUM && vtype(val) == T_NUM) { *slot = tov(tod(*slot) + tod(val)); sv_tfb_record_local(func, (int)idx, *slot); return tov(0); } + vm->stack[vm->sp++] = *slot; vm->stack[vm->sp++] = val; + ant_value_t err = sv_op_add(vm, js); if (is_err(err)) return err; *slot = vm->stack[--vm->sp]; sv_tfb_record_local(func, (int)idx, *slot); + return tov(0); } diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index 44aefdc..ca21c6e 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -2,8 +2,6 @@ #define SV_COERCION_H #include "silver/engine.h" -#include "modules/symbol.h" - #include "esm/loader.h" #include @@ -230,7 +228,7 @@ static inline void sv_with_fallback_put( if (frame->lp) frame->lp[idx] = val; break; case WITH_FB_ARG: - sv_frame_set_arg_value(frame, idx, val); + sv_frame_set_arg_value(js, frame, idx, val); break; case WITH_FB_UPVAL: if (frame->upvalues && (int)idx < frame->upvalue_count) { @@ -360,24 +358,16 @@ static inline void sv_op_special_obj( return; } - ant_value_t arr = js_mkarr(js); - if (frame->bp && frame->argc > 0) { - for (int i = 0; i < frame->argc; i++) js_arr_push(js, arr, frame->bp[i]); - } - - if (sv_frame_is_strict(frame)) js_set_slot(arr, SLOT_STRICT_ARGS, js_true); - else if (vtype(frame->callee) == T_FUNC) setprop_cstr(js, arr, "callee", 6, frame->callee); - - js_set_sym(js, arr, get_toStringTag_sym(), js_mkstr(js, "Arguments", 9)); - ant_value_t array_proto = js_get_ctor_proto(js, "Array", 5); - - if (is_object_type(array_proto)) { - ant_value_t iter_fn = js_get_sym(js, array_proto, get_iterator_sym()); - if (vtype(iter_fn) == T_FUNC || vtype(iter_fn) == T_CFUNC) - js_set_sym(js, arr, get_iterator_sym(), iter_fn); + if (vtype(frame->arguments_obj) == T_UNDEF) { + int mapped_count = sv_frame_is_strict(frame) || !frame->func ? 0 : frame->func->param_count; + if (mapped_count > frame->argc) mapped_count = frame->argc; + frame->arguments_obj = js_create_arguments_object( + js, frame->callee, frame, frame->argc, + mapped_count, sv_frame_is_strict(frame) + ); } - vm->stack[vm->sp++] = arr; + vm->stack[vm->sp++] = frame->arguments_obj; } #endif diff --git a/src/silver/ops/iteration.h b/src/silver/ops/iteration.h index f293afd..b2635b8 100644 --- a/src/silver/ops/iteration.h +++ b/src/silver/ops/iteration.h @@ -74,8 +74,8 @@ static inline ant_value_t sv_op_for_of(sv_vm_t *vm, ant_t *js) { } if (vtype(iterable) == T_STR) { - if (str_is_heap_rope(iterable)) { - iterable = rope_flatten(js, iterable); + if (str_is_heap_rope(iterable) || str_is_heap_builder(iterable)) { + iterable = str_materialize(js, iterable); if (is_err(iterable)) return iterable; } vm->stack[vm->sp++] = iterable; diff --git a/src/silver/ops/locals.h b/src/silver/ops/locals.h index b6c1709..ba8580a 100644 --- a/src/silver/ops/locals.h +++ b/src/silver/ops/locals.h @@ -4,48 +4,78 @@ #include "silver/engine.h" #include "errors.h" -static inline void sv_op_get_local(sv_vm_t *vm, ant_value_t *lp, uint8_t *ip) { +static inline ant_value_t sv_op_get_local( + sv_vm_t *vm, ant_value_t *lp, + ant_t *js, sv_frame_t *frame, uint8_t *ip +) { uint16_t idx = sv_get_u16(ip + 1); - vm->stack[vm->sp++] = lp[idx]; + ant_value_t value = lp[idx]; + if (vtype(value) == T_STR && str_is_heap_builder(value)) { + value = str_materialize(js, value); + if (is_err(value)) return value; + } + vm->stack[vm->sp++] = value; + return js_mkundef(); } -static inline void sv_op_put_local(sv_vm_t *vm, ant_value_t *lp, sv_func_t *func, uint8_t *ip) { +static inline void sv_op_put_local( + sv_vm_t *vm, ant_value_t *lp, + sv_frame_t *frame, sv_func_t *func, uint8_t *ip +) { uint16_t idx = sv_get_u16(ip + 1); lp[idx] = vm->stack[--vm->sp]; sv_tfb_record_local(func, (int)idx, lp[idx]); } -static inline void sv_op_set_local(sv_vm_t *vm, ant_value_t *lp, sv_func_t *func, uint8_t *ip) { +static inline void sv_op_set_local( + sv_vm_t *vm, ant_value_t *lp, + sv_frame_t *frame, sv_func_t *func, uint8_t *ip +) { uint16_t idx = sv_get_u16(ip + 1); lp[idx] = vm->stack[vm->sp - 1]; sv_tfb_record_local(func, (int)idx, lp[idx]); } -static inline void sv_op_get_local8(sv_vm_t *vm, ant_value_t *lp, uint8_t *ip) { +static inline ant_value_t sv_op_get_local8( + sv_vm_t *vm, ant_value_t *lp, + ant_t *js, sv_frame_t *frame, uint8_t *ip +) { uint8_t idx = sv_get_u8(ip + 1); - vm->stack[vm->sp++] = lp[idx]; + ant_value_t value = lp[idx]; + if (vtype(value) == T_STR && str_is_heap_builder(value)) { + value = str_materialize(js, value); + if (is_err(value)) return value; + } + vm->stack[vm->sp++] = value; + return js_mkundef(); } -static inline void sv_op_put_local8(sv_vm_t *vm, ant_value_t *lp, sv_func_t *func, uint8_t *ip) { +static inline void sv_op_put_local8( + sv_vm_t *vm, ant_value_t *lp, + sv_frame_t *frame, sv_func_t *func, uint8_t *ip +) { uint8_t idx = sv_get_u8(ip + 1); lp[idx] = vm->stack[--vm->sp]; sv_tfb_record_local(func, (int)idx, lp[idx]); } -static inline void sv_op_set_local8(sv_vm_t *vm, ant_value_t *lp, sv_func_t *func, uint8_t *ip) { +static inline void sv_op_set_local8( + sv_vm_t *vm, ant_value_t *lp, + sv_frame_t *frame, sv_func_t *func, uint8_t *ip +) { uint8_t idx = sv_get_u8(ip + 1); lp[idx] = vm->stack[vm->sp - 1]; sv_tfb_record_local(func, (int)idx, lp[idx]); } -static inline void sv_op_set_local_undef(ant_value_t *lp, uint8_t *ip) { +static inline void sv_op_set_local_undef(sv_frame_t *frame, ant_value_t *lp, uint8_t *ip) { uint16_t idx = sv_get_u16(ip + 1); lp[idx] = SV_TDZ; } static inline ant_value_t sv_op_get_local_chk( sv_vm_t *vm, ant_value_t *lp, - ant_t *js, sv_func_t *func, uint8_t *ip + ant_t *js, sv_frame_t *frame, sv_func_t *func, uint8_t *ip ) { uint16_t idx = sv_get_u16(ip + 1); ant_value_t val = lp[idx]; @@ -55,19 +85,39 @@ static inline ant_value_t sv_op_get_local_chk( sv_atom_t *a = &func->atoms[ai]; return js_mkerr_typed( js, JS_ERR_REFERENCE, - "Cannot access '%.*s' before initialization", (int)a->len, a->str); + "Cannot access '%.*s' before initialization", + (int)a->len, a->str + ); } return js_mkerr_typed( js, JS_ERR_REFERENCE, - "Cannot access variable before initialization"); + "Cannot access variable before initialization" + ); + } + if (vtype(val) == T_STR && str_is_heap_builder(val)) { + val = str_materialize(js, val); + if (is_err(val)) return val; } vm->stack[vm->sp++] = val; return val; } +static inline ant_value_t sv_op_get_slot_raw( + sv_vm_t *vm, ant_t *js, sv_frame_t *frame, uint8_t *ip +) { + uint16_t slot_idx = sv_get_u16(ip + 1); + ant_value_t *slot = sv_frame_slot_ptr(frame, slot_idx); + if (!slot) return js_mkerr(js, "invalid frame slot"); + if (*slot == SV_TDZ) return js_mkerr_typed(js, JS_ERR_REFERENCE, + "Cannot access variable before initialization" + ); + vm->stack[vm->sp++] = *slot; + return js_mkundef(); +} + static inline ant_value_t sv_op_put_local_chk( sv_vm_t *vm, ant_value_t *lp, - ant_t *js, sv_func_t *func, uint8_t *ip + ant_t *js, sv_frame_t *frame, sv_func_t *func, uint8_t *ip ) { uint16_t idx = sv_get_u16(ip + 1); ant_value_t *slot = &lp[idx]; @@ -77,29 +127,38 @@ static inline ant_value_t sv_op_put_local_chk( sv_atom_t *a = &func->atoms[ai]; return js_mkerr_typed( js, JS_ERR_REFERENCE, - "Cannot access '%.*s' before initialization", (int)a->len, a->str); + "Cannot access '%.*s' before initialization", + (int)a->len, a->str + ); } return js_mkerr_typed( js, JS_ERR_REFERENCE, - "Cannot access variable before initialization"); + "Cannot access variable before initialization" + ); } *slot = vm->stack[--vm->sp]; return *slot; } -static inline void sv_op_get_arg(sv_vm_t *vm, sv_frame_t *frame, uint8_t *ip) { +static inline ant_value_t sv_op_get_arg(sv_vm_t *vm, ant_t *js, sv_frame_t *frame, uint8_t *ip) { uint16_t idx = sv_get_u16(ip + 1); - vm->stack[vm->sp++] = sv_frame_get_arg_value(frame, idx); + ant_value_t value = sv_frame_get_arg_value(frame, idx); + if (vtype(value) == T_STR && str_is_heap_builder(value)) { + value = str_materialize(js, value); + if (is_err(value)) return value; + } + vm->stack[vm->sp++] = value; + return js_mkundef(); } -static inline void sv_op_put_arg(sv_vm_t *vm, sv_frame_t *frame, uint8_t *ip) { +static inline void sv_op_put_arg(sv_vm_t *vm, ant_t *js, sv_frame_t *frame, uint8_t *ip) { uint16_t idx = sv_get_u16(ip + 1); - sv_frame_set_arg_value(frame, idx, vm->stack[--vm->sp]); + sv_frame_set_arg_value(js, frame, idx, vm->stack[--vm->sp]); } -static inline void sv_op_set_arg(sv_vm_t *vm, sv_frame_t *frame, uint8_t *ip) { +static inline void sv_op_set_arg(sv_vm_t *vm, ant_t *js, sv_frame_t *frame, uint8_t *ip) { uint16_t idx = sv_get_u16(ip + 1); - sv_frame_set_arg_value(frame, idx, vm->stack[vm->sp - 1]); + sv_frame_set_arg_value(js, frame, idx, vm->stack[vm->sp - 1]); } static inline void sv_op_rest( @@ -115,4 +174,33 @@ static inline void sv_op_rest( vm->stack[vm->sp++] = arr; } +static inline ant_value_t sv_op_str_append_local( + sv_vm_t *vm, ant_t *js, + sv_frame_t *frame, sv_func_t *func, uint8_t *ip +) { + uint16_t idx = sv_get_u16(ip + 1); + ant_value_t rhs = vm->stack[--vm->sp]; + return sv_string_builder_append_slot(vm, js, frame, func, idx, rhs); +} + +static inline ant_value_t sv_op_str_append_local_snapshot( + sv_vm_t *vm, ant_t *js, + sv_frame_t *frame, sv_func_t *func, uint8_t *ip +) { + uint16_t idx = sv_get_u16(ip + 1); + ant_value_t rhs = vm->stack[--vm->sp]; + ant_value_t lhs = vm->stack[--vm->sp]; + return sv_string_builder_append_snapshot_slot(vm, js, frame, func, idx, lhs, rhs); +} + +static inline ant_value_t sv_op_str_flush_local( + sv_vm_t *vm, ant_t *js, + sv_frame_t *frame, uint8_t *ip +) { + uint16_t idx = sv_get_u16(ip + 1); + ant_value_t flush = sv_string_builder_flush_slot(vm, js, frame, idx); + if (is_err(flush)) return flush; + return js_mkundef(); +} + #endif diff --git a/src/silver/ops/objects.h b/src/silver/ops/objects.h index 1068438..90da9ba 100644 --- a/src/silver/ops/objects.h +++ b/src/silver/ops/objects.h @@ -150,10 +150,11 @@ static inline ant_value_t sv_op_spread(sv_vm_t *vm, ant_t *js) { } if (vtype(iterable) == T_STR) { - if (str_is_heap_rope(iterable)) { - iterable = rope_flatten(js, iterable); + if (str_is_heap_rope(iterable) || str_is_heap_builder(iterable)) { + iterable = str_materialize(js, iterable); if (is_err(iterable)) return iterable; } + ant_offset_t slen = str_len_fast(js, iterable); for (ant_offset_t i = 0; i < slen; ) { ant_offset_t off = vstr(js, iterable, NULL); @@ -165,6 +166,7 @@ static inline ant_value_t sv_op_spread(sv_vm_t *vm, ant_t *js) { js_arr_push(js, arr, js_mkstr(js, (const void *)(uintptr_t)(off + i), cb_len)); i += cb_len; } + return tov(0); } diff --git a/src/silver/ops/upvalues.h b/src/silver/ops/upvalues.h index 82ee493..b8d5935 100644 --- a/src/silver/ops/upvalues.h +++ b/src/silver/ops/upvalues.h @@ -36,9 +36,14 @@ static inline ant_value_t sv_op_get_upval( uint16_t idx = sv_get_u16(ip + 1); sv_upvalue_t *uv = frame->upvalues[idx]; ant_value_t val = *uv->location; - if (val == SV_TDZ) - return js_mkerr_typed(js, JS_ERR_REFERENCE, - "Cannot access variable before initialization"); + if (val == SV_TDZ) return js_mkerr_typed(js, + JS_ERR_REFERENCE, + "Cannot access variable before initialization" + ); + if (vtype(val) == T_STR && str_is_heap_builder(val)) { + val = str_materialize(js, val); + if (is_err(val)) return val; + } vm->stack[vm->sp++] = val; return js_mkundef(); } @@ -55,22 +60,25 @@ static inline void sv_op_set_upval(sv_vm_t *vm, sv_frame_t *frame, uint8_t *ip) *uv->location = vm->stack[vm->sp - 1]; } -static inline void sv_op_close_upval(sv_vm_t *vm, sv_frame_t *frame, uint8_t *ip) { +static inline ant_value_t sv_op_close_upval(sv_vm_t *vm, sv_frame_t *frame, uint8_t *ip) { uint16_t idx = sv_get_u16(ip + 1); ant_value_t *slot = sv_frame_slot_ptr(frame, idx); - if (!slot) return; + if (!slot) return js_mkundef(); sv_upvalue_t **pp = &vm->open_upvalues; while (*pp) { - sv_upvalue_t *uv = *pp; - ant_value_t *loc = uv->location; - if (sv_slot_in_vm_stack(vm, loc) && loc >= slot) { - uv->closed = *loc; - uv->location = &uv->closed; - *pp = uv->next; + sv_upvalue_t *uv = *pp; + ant_value_t *loc = uv->location; + if (sv_slot_in_vm_stack(vm, loc) && loc >= slot) { + uv->closed = *loc; + uv->location = &uv->closed; + *pp = uv->next; + } + else pp = &uv->next; } - else pp = &uv->next; -}} + + return js_mkundef(); +} static inline sv_upvalue_t *sv_capture_upvalue(sv_vm_t *vm, ant_value_t *slot) { sv_upvalue_t **pp = &vm->open_upvalues; @@ -84,7 +92,7 @@ static inline sv_upvalue_t *sv_capture_upvalue(sv_vm_t *vm, ant_value_t *slot) { return uv; } -static inline void sv_op_closure( +static inline ant_value_t sv_op_closure( sv_vm_t *vm, ant_t *js, sv_frame_t *frame, sv_func_t *func, uint8_t *ip ) { @@ -99,16 +107,15 @@ static inline void sv_op_closure( closure->call_flags = child->is_arrow ? SV_CALL_IS_ARROW : 0; if (child->upvalue_count > 0) { - closure->upvalues = calloc((size_t)child->upvalue_count, sizeof(sv_upvalue_t *)); - for (int i = 0; i < child->upvalue_count; i++) { - sv_upval_desc_t *desc = &child->upval_descs[i]; - if (desc->is_local) { - ant_value_t *slot = sv_frame_slot_ptr(frame, desc->index); - if (!slot) slot = frame->bp; - closure->upvalues[i] = sv_capture_upvalue(vm, slot); - } else closure->upvalues[i] = frame->upvalues[desc->index]; - } - } + closure->upvalues = calloc((size_t)child->upvalue_count, sizeof(sv_upvalue_t *)); + for (int i = 0; i < child->upvalue_count; i++) { + sv_upval_desc_t *desc = &child->upval_descs[i]; + if (desc->is_local) { + ant_value_t *slot = sv_frame_slot_ptr(frame, desc->index); + if (!slot) slot = frame->bp; + closure->upvalues[i] = sv_capture_upvalue(vm, slot); + } else closure->upvalues[i] = frame->upvalues[desc->index]; + }} ant_value_t func_val = mkval(T_FUNC, (uintptr_t)closure); vm->stack[vm->sp++] = func_val; @@ -138,6 +145,8 @@ static inline void sv_op_closure( ant_value_t func_proto = js_get_slot(js->global, SLOT_FUNC_PROTO); if (vtype(func_proto) == T_FUNC) js_set_proto_init(func_obj, func_proto); } + + return js_mkundef(); } #endif diff --git a/src/silver/swarm.c b/src/silver/swarm.c index 2646ff3..0afad95 100644 --- a/src/silver/swarm.c +++ b/src/silver/swarm.c @@ -51,6 +51,8 @@ static void jit_load_externals_once(sv_jit_ctx_t *jc) { LOAD_EXT(jit_helper_mul); LOAD_EXT(jit_helper_div); LOAD_EXT(jit_helper_mod); + LOAD_EXT(jit_helper_str_append_local); + LOAD_EXT(jit_helper_str_append_local_snapshot); LOAD_EXT(jit_helper_lt); LOAD_EXT(jit_helper_le); LOAD_EXT(jit_helper_gt); @@ -1819,6 +1821,9 @@ static jit_features_t jit_prescan_features(sv_func_t *func) { case OP_SHL: case OP_SHR: case OP_USHR: case OP_TYPEOF: case OP_ADD_LOCAL: + case OP_STR_APPEND_LOCAL: + case OP_STR_ALC_SNAPSHOT: + case OP_STR_FLUSH_LOCAL: f.needs_bailout = true; break; case OP_INC_LOCAL: case OP_DEC_LOCAL: @@ -1869,6 +1874,7 @@ static bool jit_is_eligible(sv_func_t *func) { case OP_GET_LOCAL: case OP_PUT_LOCAL: case OP_SET_LOCAL: case OP_GET_LOCAL8: case OP_PUT_LOCAL8: case OP_SET_LOCAL8: case OP_SET_LOCAL_UNDEF: + case OP_GET_SLOT_RAW: case OP_GET_UPVAL: case OP_PUT_UPVAL: case OP_SET_UPVAL: case OP_CLOSE_UPVAL: case OP_REST: @@ -1903,6 +1909,8 @@ static bool jit_is_eligible(sv_func_t *func) { case OP_IN: case OP_GET_LENGTH: case OP_DEFINE_FIELD: case OP_SEQ: case OP_EQ: case OP_INC_LOCAL: case OP_DEC_LOCAL: case OP_ADD_LOCAL: + case OP_STR_APPEND_LOCAL: + case OP_STR_ALC_SNAPSHOT: case OP_TO_PROPKEY: case OP_RETURN: case OP_RETURN_UNDEF: case OP_SET_NAME: @@ -2060,6 +2068,27 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_T_I64, "js", MIR_JSVAL, "v"); + MIR_type_t sal_ret = MIR_JSVAL; + MIR_item_t str_append_local_proto = MIR_new_proto(ctx, "sal_proto", + 1, &sal_ret, 6, + MIR_T_I64, "vm", + MIR_T_I64, "js", + MIR_T_P, "func", + MIR_T_P, "locals", + MIR_T_I32, "local_idx", + MIR_JSVAL, "rhs"); + + MIR_type_t sals_ret = MIR_JSVAL; + MIR_item_t str_append_local_snapshot_proto = MIR_new_proto(ctx, "sals_proto", + 1, &sals_ret, 7, + MIR_T_I64, "vm", + MIR_T_I64, "js", + MIR_T_P, "func", + MIR_T_P, "locals", + MIR_T_I32, "local_idx", + MIR_JSVAL, "lhs", + MIR_JSVAL, "rhs"); + MIR_type_t truthy_ret = MIR_T_I64; MIR_item_t truthy_proto = MIR_new_proto(ctx, "truthy_proto", 1, &truthy_ret, 2, @@ -2200,6 +2229,10 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_item_t imp_mul = MIR_new_import(ctx, "jit_helper_mul"); MIR_item_t imp_div = MIR_new_import(ctx, "jit_helper_div"); MIR_item_t imp_mod = MIR_new_import(ctx, "jit_helper_mod"); + MIR_item_t imp_str_append_local = + MIR_new_import(ctx, "jit_helper_str_append_local"); + MIR_item_t imp_str_append_local_snapshot = + MIR_new_import(ctx, "jit_helper_str_append_local_snapshot"); MIR_item_t imp_lt = MIR_new_import(ctx, "jit_helper_lt"); MIR_item_t imp_le = MIR_new_import(ctx, "jit_helper_le"); MIR_item_t imp_gt = MIR_new_import(ctx, "jit_helper_gt"); @@ -2834,6 +2867,65 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos break; } + case OP_GET_SLOT_RAW: { + uint16_t slot_idx = sv_get_u16(ip + 1); + if ((int)slot_idx < param_count) { + uint16_t idx = slot_idx; + MIR_reg_t dst = vstack_push(&vs); + if (has_captured_params && captured_params && idx < (uint16_t)param_count && + captured_params[idx]) { + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, dst), + MIR_new_mem_op(ctx, MIR_JSVAL, + (MIR_disp_t)(idx * (int)sizeof(ant_value_t)), + r_slotbuf, 0, 1))); + } else { + MIR_label_t arg_in_range = MIR_new_label(ctx); + MIR_label_t arg_done = MIR_new_label(ctx); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_UBGT, + MIR_new_label_op(ctx, arg_in_range), + MIR_new_reg_op(ctx, r_argc), + MIR_new_int_op(ctx, (int64_t)idx))); + mir_load_imm(ctx, jit_func, dst, mkval(T_UNDEF, 0)); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_JMP, MIR_new_label_op(ctx, arg_done))); + MIR_append_insn(ctx, jit_func, arg_in_range); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, dst), + MIR_new_mem_op(ctx, MIR_JSVAL, + (MIR_disp_t)(idx * (int)sizeof(ant_value_t)), + r_args, 0, 1))); + MIR_append_insn(ctx, jit_func, arg_done); + } + } else { + uint16_t idx = (uint16_t)(slot_idx - (uint16_t)param_count); + if (idx >= (uint16_t)n_locals) { ok = false; break; } + if (has_captures && captured_locals && captured_locals[idx]) + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, local_regs[idx]), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((int)idx * (int)sizeof(ant_value_t)), r_lbuf, 0, 1))); + MIR_reg_t dst = vstack_push(&vs); + if (known_func_locals) vs.known_func[vs.sp - 1] = known_func_locals[idx]; + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, dst), + MIR_new_reg_op(ctx, local_regs[idx]))); + if (known_type_locals && known_type_locals[idx] == SV_TI_NUM) { + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_DMOV, + MIR_new_reg_op(ctx, vs.d_regs[vs.sp - 1]), + MIR_new_reg_op(ctx, local_d_regs[idx]))); + if (vs.slot_type) vs.slot_type[vs.sp - 1] = SLOT_NUM; + } + } + break; + } + case OP_PUT_LOCAL: { uint16_t idx = sv_get_u16(ip + 1); if (idx >= (uint16_t)n_locals) { ok = false; break; } @@ -5156,6 +5248,171 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos break; } + case OP_STR_APPEND_LOCAL: { + uint16_t slot_idx = sv_get_u16(ip + 1); + int pre_op_sp = vs.sp; + if ((int)slot_idx < param_count) { + mir_load_imm(ctx, jit_func, r_bailout_val, (uint64_t)SV_JIT_BAILOUT); + mir_emit_bailout_check(ctx, jit_func, r_bailout_val, + 0, r_bailout_off, bc_off, + r_bailout_sp, pre_op_sp, bailout_tramp, + r_args_buf, &vs, local_regs, n_locals, r_lbuf, r_d_slot); + break; + } + + uint16_t local_idx = (uint16_t)(slot_idx - (uint16_t)param_count); + if (local_idx >= (uint16_t)n_locals) { ok = false; break; } + + vstack_ensure_boxed(&vs, vs.sp - 1, ctx, jit_func, r_d_slot); + MIR_reg_t rhs = vstack_pop(&vs); + + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 9, + MIR_new_ref_op(ctx, str_append_local_proto), + MIR_new_ref_op(ctx, imp_str_append_local), + MIR_new_reg_op(ctx, r_err_tmp), + MIR_new_reg_op(ctx, r_vm), + MIR_new_reg_op(ctx, r_js), + MIR_new_uint_op(ctx, (uint64_t)(uintptr_t)func), + MIR_new_reg_op(ctx, r_lbuf), + MIR_new_int_op(ctx, (int64_t)local_idx), + MIR_new_reg_op(ctx, rhs))); + + mir_emit_bailout_check(ctx, jit_func, r_err_tmp, + 0, r_bailout_off, bc_off, + r_bailout_sp, pre_op_sp, bailout_tramp, + r_args_buf, &vs, local_regs, n_locals, r_lbuf, r_d_slot); + + 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_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, local_regs[local_idx]), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((int)local_idx * (int)sizeof(ant_value_t)), r_lbuf, 0, 1))); + if (known_func_locals) known_func_locals[local_idx] = NULL; + if (known_type_locals) known_type_locals[local_idx] = SV_TI_UNKNOWN; + + 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_err_tmp), + 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_err_tmp))); + 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_err_tmp))); + } + MIR_append_insn(ctx, jit_func, no_err); + break; + } + + case OP_STR_ALC_SNAPSHOT: { + uint16_t slot_idx = sv_get_u16(ip + 1); + int pre_op_sp = vs.sp; + if ((int)slot_idx < param_count) { + mir_load_imm(ctx, jit_func, r_bailout_val, (uint64_t)SV_JIT_BAILOUT); + mir_emit_bailout_check(ctx, jit_func, r_bailout_val, + 0, r_bailout_off, bc_off, + r_bailout_sp, pre_op_sp, bailout_tramp, + r_args_buf, &vs, local_regs, n_locals, r_lbuf, r_d_slot); + break; + } + + uint16_t local_idx = (uint16_t)(slot_idx - (uint16_t)param_count); + if (local_idx >= (uint16_t)n_locals) { ok = false; break; } + + vstack_ensure_boxed(&vs, vs.sp - 1, ctx, jit_func, r_d_slot); + vstack_ensure_boxed(&vs, vs.sp - 2, ctx, jit_func, r_d_slot); + MIR_reg_t rhs = vstack_pop(&vs); + MIR_reg_t lhs = vstack_pop(&vs); + + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 10, + MIR_new_ref_op(ctx, str_append_local_snapshot_proto), + MIR_new_ref_op(ctx, imp_str_append_local_snapshot), + MIR_new_reg_op(ctx, r_err_tmp), + MIR_new_reg_op(ctx, r_vm), + MIR_new_reg_op(ctx, r_js), + MIR_new_uint_op(ctx, (uint64_t)(uintptr_t)func), + MIR_new_reg_op(ctx, r_lbuf), + MIR_new_int_op(ctx, (int64_t)local_idx), + MIR_new_reg_op(ctx, lhs), + MIR_new_reg_op(ctx, rhs))); + + mir_emit_bailout_check(ctx, jit_func, r_err_tmp, + 0, r_bailout_off, bc_off, + r_bailout_sp, pre_op_sp, bailout_tramp, + r_args_buf, &vs, local_regs, n_locals, r_lbuf, r_d_slot); + + 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_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, local_regs[local_idx]), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((int)local_idx * (int)sizeof(ant_value_t)), r_lbuf, 0, 1))); + if (known_func_locals) known_func_locals[local_idx] = NULL; + if (known_type_locals) known_type_locals[local_idx] = SV_TI_UNKNOWN; + + 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_err_tmp), + 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_err_tmp))); + 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_err_tmp))); + } + MIR_append_insn(ctx, jit_func, no_err); + break; + } + case OP_TO_PROPKEY: { vstack_ensure_boxed(&vs, vs.sp - 1, ctx, jit_func, r_d_slot); MIR_reg_t src = vstack_pop(&vs); @@ -7002,6 +7259,9 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos case OP_COL_NUM: case OP_LABEL: break; + case OP_STR_FLUSH_LOCAL: + ok = false; + break; case OP_SET_NAME: { uint32_t atom_idx = sv_get_u32(ip + 1); diff --git a/tests/test_http_request_host_port_fetch_shim.mjs b/tests/test_http_request_host_port_fetch_shim.mjs new file mode 100644 index 0000000..50e185e --- /dev/null +++ b/tests/test_http_request_host_port_fetch_shim.mjs @@ -0,0 +1,38 @@ +import { request } from 'node:http'; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const originalFetch = globalThis.fetch; +const seenUrls = []; + +try { + globalThis.fetch = async url => { + seenUrls.push(String(url)); + return new Response('ok', { status: 200 }); + }; + + await new Promise((resolve, reject) => { + const req = request({ + host: 'example.com:8080', + path: '/resource', + timeout: 500 + }, response => { + response.resume(); + response.on('end', resolve); + }); + + req.on('error', reject); + req.on('timeout', () => reject(new Error('request timed out unexpectedly'))); + req.end(); + }); + + assert(seenUrls.length === 1, `expected one fetch call, got ${seenUrls.length}`); + assert(seenUrls[0] === 'http://example.com:8080/resource', + `unexpected request URL: ${seenUrls[0]}`); + + console.log('http.request host port fetch shim test passed'); +} finally { + globalThis.fetch = originalFetch; +} diff --git a/tests/test_https_get_fetch_shim.mjs b/tests/test_https_get_fetch_shim.mjs new file mode 100644 index 0000000..9e0b8f5 --- /dev/null +++ b/tests/test_https_get_fetch_shim.mjs @@ -0,0 +1,77 @@ +import { get } from 'node:https'; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const originalFetch = globalThis.fetch; +let seenUrl = null; + +try { + globalThis.fetch = async (url, init) => { + seenUrl = String(url); + assert(seenUrl === 'https://registry.npmjs.org/serve', `unexpected request URL: ${seenUrl}`); + assert(init.method === 'GET', `expected GET method, got ${init.method}`); + assert(init.headers.accept.includes('application/json'), + `unexpected accept header: ${init.headers.accept}`); + + return new Response('{"ok":true}', { + status: 200, + headers: { + 'content-type': 'application/json' + } + }); + }; + + const body = await new Promise((resolve, reject) => { + const req = get({ + host: 'registry.npmjs.org', + path: '/serve', + timeout: 500, + headers: { + accept: 'application/json' + } + }, response => { + assert(response.statusCode === 200, `expected 200 status, got ${response.statusCode}`); + assert(response.headers['content-type'] === 'application/json', + `unexpected content-type: ${response.headers['content-type']}`); + + response.setEncoding('utf8'); + + let rawData = ''; + response.on('data', chunk => { + rawData += chunk; + }); + response.on('end', () => { + resolve(rawData); + }); + }); + + req.on('error', reject); + req.on('timeout', () => reject(new Error('request timed out unexpectedly'))); + }); + + assert(body === '{"ok":true}', `unexpected body: ${body}`); + + for (const invalidInput of [ + 'http://registry.npmjs.org/serve', + { protocol: 'http:', host: 'registry.npmjs.org', path: '/serve' } + ]) { + let invalidError = null; + try { + get(invalidInput); + } catch (error) { + invalidError = error; + } + + assert(invalidError, `expected invalid protocol error for ${String(invalidInput)}`); + assert(invalidError.name === 'TypeError', + `expected TypeError for invalid protocol, got ${invalidError && invalidError.name}`); + assert(invalidError.code === 'ERR_INVALID_PROTOCOL', + `expected ERR_INVALID_PROTOCOL, got ${invalidError && invalidError.code}`); + } + + console.log('https.get fetch shim test passed'); +} finally { + globalThis.fetch = originalFetch; +} diff --git a/tests/test_node_url_parse_named_export.mjs b/tests/test_node_url_parse_named_export.mjs new file mode 100644 index 0000000..3e08df9 --- /dev/null +++ b/tests/test_node_url_parse_named_export.mjs @@ -0,0 +1,19 @@ +import { parse } from 'node:url'; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const tcp = parse('tcp://localhost:4321'); +assert(tcp, 'expected parse() to return a value for tcp:// URLs'); +assert(tcp.protocol === 'tcp:', `expected tcp protocol, got ${tcp && tcp.protocol}`); +assert(tcp.hostname === 'localhost', `expected localhost hostname, got ${tcp && tcp.hostname}`); +assert(tcp.port === '4321', `expected 4321 port, got ${tcp && tcp.port}`); + +const unix = parse('unix:/tmp/ant-test.sock'); +assert(unix, 'expected parse() to return a value for unix: URLs'); +assert(unix.protocol === 'unix:', `expected unix protocol, got ${unix && unix.protocol}`); +assert(unix.pathname === '/tmp/ant-test.sock', + `expected unix pathname, got ${unix && unix.pathname}`); + +console.log('node:url parse named export test passed'); diff --git a/tests/test_string_small_append_loop.cjs b/tests/test_string_small_append_loop.cjs new file mode 100644 index 0000000..78025b7 --- /dev/null +++ b/tests/test_string_small_append_loop.cjs @@ -0,0 +1,77 @@ +function assert(cond, msg) { + if (!cond) throw new Error(msg); +} + +let parserLike = ""; +const parserLikeIters = 600000; +for (let i = 0; i < parserLikeIters; i++) { + let ch = (i & 1) === 0 ? "a" : "\t"; + if (ch === "\t") ch = " "; + parserLike += ch; +} + +assert(parserLike.length === parserLikeIters, "parser-like append length mismatch"); +assert(parserLike.substring(0, 6) === "a a a ", "parser-like append prefix mismatch"); +assert(parserLike.substring(parserLikeIters - 6) === "a a a ", "parser-like append suffix mismatch"); + +let chunked = ""; +for (let i = 0; i < 200000; i++) chunked += "xyz"; + +assert(chunked.length === 600000, "chunked append length mismatch"); +assert(chunked.substring(0, 9) === "xyzxyzxyz", "chunked append prefix mismatch"); +assert(chunked.substring(chunked.length - 9) === "xyzxyzxyz", "chunked append suffix mismatch"); +assert(chunked.charCodeAt(3) === 120, "chunked append charCodeAt mismatch"); + +for (const count of [1000, 10000, 100000]) { + let s = ""; + for (let i = 0; i < count; i++) s += "a"; + assert(s.length === count, `single-char append mismatch @ ${count}`); + assert(s.charCodeAt(0) === 97, `charCodeAt mismatch @ ${count}`); + assert(s.substring(count - 4) === "aaaa", `substring mismatch @ ${count}`); +} + +function takesString(s) { + return s.length; +} + +let escaped = ""; +for (let i = 0; i < 5000; i++) escaped = escaped + "xyz"; +assert(takesString(escaped) === 15000, "call escape length mismatch"); +assert(escaped.substring(0, 6) === "xyzxyz", "call escape prefix mismatch"); + +let assigned = ""; +for (let i = 0; i < 4096; i++) assigned += (i & 1) ? "b" : "a"; +const box = { value: assigned }; +assert(box.value.length === 4096, "object assignment length mismatch"); +assert(box.value.substring(0, 4) === "abab", "object assignment prefix mismatch"); + +const arr = []; +arr.push(assigned); +assert(arr[0].length === 4096, "array assignment length mismatch"); +assert(arr[0].substring(0, 4) === "abab", "array assignment prefix mismatch"); + +let branchy = ""; +for (let i = 0; i < 20000; i++) { + if ((i & 3) === 0) branchy += "x"; + else branchy = branchy + "yz"; +} +assert(branchy.length === 35000, "branchy append length mismatch"); +assert(branchy.substring(0, 5) === "xyzyz", "branchy append prefix mismatch"); +assert(branchy.charCodeAt(0) === 120, "branchy append charCodeAt mismatch"); + +let mixed = ""; +for (let i = 0; i < 30000; i++) mixed += (i & 1) === 0 ? "m" : "tiny"; +assert(mixed.length === 75000, "mixed append length mismatch"); +assert(mixed.substring(0, 9) === "mtinymtin", "mixed append prefix mismatch"); +assert(mixed.charCodeAt(1) === 116, "mixed append charCodeAt mismatch"); + +let numeric = 0; +for (let i = 0; i < 10000; i++) numeric += 3; +assert(numeric === 30000, "numeric += changed semantics"); + +const obj = { value: "" }; +for (let i = 0; i < 2048; i++) obj.value += "q"; +assert(obj.value.length === 2048, "non-local append length mismatch"); +assert(obj.value.substring(0, 4) === "qqqq", "non-local append prefix mismatch"); + +console.log("small append loop test passed"); -- 2.51.2