From bea9f7943fc7e03c2210d67018b2fa75debc53ce Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 14 Apr 2026 12:52:43 -0700 Subject: [PATCH] thread more string operations in the jit --- include/gc.h | 2 +- include/modules/symbol.h | 1 + include/silver/ast.h | 1 + include/silver/glue.h | 35 +- src/silver/ast.c | 23 + src/silver/compiler.c | 4 +- src/silver/glue.c | 378 ++++++++++++++++- src/silver/swarm.c | 567 +++++++++++++++++++++---- tests/amp/mini_amp_screen_repro.js | 123 ++++++ tests/amp/mini_amp_state_repro.js | 198 +++++++++ tests/amp/mini_amp_tui_repro.js | 213 ++++++++++ tests/amp/mini_amp_widget_tui_repro.js | 380 +++++++++++++++++ 12 files changed, 1814 insertions(+), 111 deletions(-) create mode 100644 tests/amp/mini_amp_screen_repro.js create mode 100644 tests/amp/mini_amp_state_repro.js create mode 100644 tests/amp/mini_amp_tui_repro.js create mode 100644 tests/amp/mini_amp_widget_tui_repro.js diff --git a/include/gc.h b/include/gc.h index 19c4cab..0130c71 100644 --- a/include/gc.h +++ b/include/gc.h @@ -6,8 +6,8 @@ #include #define GC_MIN_TICK 1024 +#define GC_NURSERY_THRESHOLD 32768 #define GC_FORCE_INTERVAL_MS 50 -#define GC_NURSERY_THRESHOLD 4096 #define GC_MAJOR_EVERY_N_MINOR 8 #define GC_HEAP_GROWTH(n) ((n) + (n) / 2) diff --git a/include/modules/symbol.h b/include/modules/symbol.h index d036df6..8be1d07 100644 --- a/include/modules/symbol.h +++ b/include/modules/symbol.h @@ -37,6 +37,7 @@ ant_value_t maybe_call_symbol_method( #define WELLKNOWN_SYMBOLS(X) \ X(iterator, "Symbol.iterator") \ X(asyncIterator, "Symbol.asyncIterator") \ + X(inspect, "Symbol.inspect") \ X(toStringTag, "Symbol.toStringTag") \ X(hasInstance, "Symbol.hasInstance") \ X(match, "Symbol.match") \ diff --git a/include/silver/ast.h b/include/silver/ast.h index 40f0ffc..f8755aa 100644 --- a/include/silver/ast.h +++ b/include/silver/ast.h @@ -101,6 +101,7 @@ enum { FN_INVALID_COOKED = 1 << 11, FN_PARSE_STRICT = 1 << 12, FN_TEMPLATE_SEGMENT = 1 << 13, + FN_USES_NEW_TARGET = 1 << 14, }; enum { diff --git a/include/silver/glue.h b/include/silver/glue.h index 530d0fc..bb60172 100644 --- a/include/silver/glue.h +++ b/include/silver/glue.h @@ -42,7 +42,7 @@ ant_value_t jit_helper_stack_overflow_error(sv_vm_t *vm, ant_t *js); ant_value_t jit_helper_delete(sv_vm_t *vm, ant_t *js, ant_value_t obj, ant_value_t key); ant_value_t jit_helper_typeof(sv_vm_t *vm, ant_t *js, ant_value_t v); -ant_value_t jit_helper_special_obj(ant_t *js, uint32_t which); +ant_value_t jit_helper_special_obj(sv_vm_t *vm, ant_t *js, uint32_t which); ant_value_t jit_helper_get_global( ant_t *js, const char *str, @@ -108,6 +108,11 @@ void jit_helper_define_field( ant_value_t val, const char *str, uint32_t len ); +void jit_helper_define_method_comp( + ant_t *js, + ant_value_t obj, ant_value_t key, ant_value_t fn, uint8_t flags +); + void jit_helper_set_name( sv_vm_t *vm, ant_t *js, ant_value_t fn, const char *str, uint32_t len @@ -138,6 +143,21 @@ ant_value_t jit_helper_array( ant_value_t *elements, int count ); +ant_value_t jit_helper_for_of( + sv_vm_t *vm, ant_t *js, + ant_value_t iterable, ant_value_t *iter_buf +); + +void jit_helper_destructure_close( + sv_vm_t *vm, ant_t *js, + ant_value_t *iter_buf +); + +ant_value_t jit_helper_destructure_next( + sv_vm_t *vm, ant_t *js, + ant_value_t *iter_buf +); + ant_value_t jit_helper_throw_error( sv_vm_t *vm, ant_t *js, const char *str, uint32_t len, int err_type @@ -161,14 +181,23 @@ ant_value_t jit_helper_new( 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 *args, int argc, + ant_value_t *locals, uint16_t slot_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 *args, int argc, + ant_value_t *locals, uint16_t slot_idx, ant_value_t lhs, ant_value_t rhs ); +ant_value_t jit_helper_str_flush_local( + sv_vm_t *vm, ant_t *js, sv_func_t *func, + ant_value_t *args, int argc, + ant_value_t *locals, uint16_t slot_idx +); + #endif #endif diff --git a/src/silver/ast.c b/src/silver/ast.c index 5d39694..c2d48d1 100644 --- a/src/silver/ast.c +++ b/src/silver/ast.c @@ -1287,6 +1287,27 @@ bool ast_references_arguments(const sv_ast_t *node) { return false; } +static bool ast_references_new_target(const sv_ast_t *node) { + if (!node) return false; + if (node->type == N_NEW_TARGET) return true; + if (node->type == N_FUNC && !(node->flags & FN_ARROW)) return false; + + if (ast_references_new_target(node->left)) return true; + if (ast_references_new_target(node->right)) return true; + if (ast_references_new_target(node->cond)) return true; + if (ast_references_new_target(node->body)) return true; + if (ast_references_new_target(node->catch_body)) return true; + if (ast_references_new_target(node->finally_body)) return true; + if (ast_references_new_target(node->catch_param)) return true; + if (ast_references_new_target(node->init)) return true; + if (ast_references_new_target(node->update)) return true; + + for (int i = 0; i < node->args.count; i++) + if (ast_references_new_target(node->args.items[i])) return true; + + return false; +} + static sv_ast_t *parse_func(P) { sv_ast_t *fn = mk(N_FUNC); @@ -1330,6 +1351,8 @@ static sv_ast_t *parse_func(P) { fn->src_end = (uint32_t)(TOFF + TLEN); if (!(fn->flags & FN_ARROW) && ast_references_arguments(fn->body)) fn->flags |= FN_USES_ARGS; + if (!(fn->flags & FN_ARROW) && ast_references_new_target(fn->body)) + fn->flags |= FN_USES_NEW_TARGET; return fn; } diff --git a/src/silver/compiler.c b/src/silver/compiler.c index c8af6b3..fdea202 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -911,7 +911,7 @@ static bool match_self_append_local( 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; + if (c->locals[local].depth == -1 && c->strict_args_local >= 0) return false; sv_ast_t *rhs = NULL; if (node->op == TOK_PLUS_ASSIGN) rhs = node->right; @@ -4447,7 +4447,7 @@ sv_func_t *compile_function_body( emit_put_local(&comp, comp.strict_args_local); } - if (!comp.is_arrow && comp.enclosing) { + if (!comp.is_arrow && comp.enclosing && (node->flags & FN_USES_NEW_TARGET)) { static const char nt_name[] = "\x01new.target"; comp.new_target_local = add_local(&comp, nt_name, sizeof(nt_name) - 1, false, comp.scope_depth); emit_op(&comp, OP_SPECIAL_OBJ); diff --git a/src/silver/glue.c b/src/silver/glue.c index 01f71c8..5179915 100644 --- a/src/silver/glue.c +++ b/src/silver/glue.c @@ -9,6 +9,7 @@ #include "ops/globals.h" #include "ops/property.h" +#include "ops/iteration.h" #include "ops/upvalues.h" #include "ops/comparison.h" #include "ops/calls.h" @@ -22,6 +23,37 @@ bool jit_helper_stack_overflow(ant_t *js) { return used > js->cstk.limit; } +static bool jit_vm_ensure_stack(sv_vm_t *vm, int need) { + while (vm->sp + need > vm->stack_size) { + int new_size = vm->stack_size * 2; + if (new_size > SV_STACK_HARD_MAX) new_size = SV_STACK_HARD_MAX; + if (new_size <= vm->stack_size) return false; + + ant_value_t *old = vm->stack; + int old_size = vm->stack_size; + + ant_value_t *ns = realloc(vm->stack, (size_t)new_size * sizeof(ant_value_t)); + if (!ns) return false; + + ptrdiff_t delta = ns - old; + vm->stack = ns; + vm->stack_size = new_size; + + if (delta != 0) { + for (int i = 0; i <= vm->fp; i++) { + if (vm->frames[i].bp) vm->frames[i].bp += delta; + if (vm->frames[i].lp) vm->frames[i].lp += delta; + } + for (sv_upvalue_t *uv = vm->open_upvalues; uv; uv = uv->next) if ( + uv->location != &uv->closed && + sv_slot_in_range(old, (size_t)old_size, uv->location) + ) uv->location += delta; + } + } + + return true; +} + ant_value_t jit_helper_stack_overflow_error(sv_vm_t *vm, ant_t *js) { return js_mkerr_typed(js, JS_ERR_RANGE | JS_ERR_NO_STACK, "Maximum JIT call stack size exceeded"); } @@ -53,39 +85,65 @@ ant_value_t jit_helper_mod(sv_vm_t *vm, ant_t *js, ant_value_t l, ant_value_t r) 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 *args, int argc, + ant_value_t *locals, uint16_t slot_idx, + ant_value_t rhs ) { - if (!func || !locals || local_idx >= (uint16_t)func->max_locals) + if (!func) return SV_JIT_BAILOUT; - + sv_frame_t frame = { .func = func, + .bp = args, .lp = locals, - .argc = func->param_count, + .argc = argc, + .arguments_obj = js_mkundef(), }; - - 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 *args, int argc, + ant_value_t *locals, uint16_t slot_idx, ant_value_t lhs, ant_value_t rhs ) { - if (!func || !locals || local_idx >= (uint16_t)func->max_locals) + if (!func) return SV_JIT_BAILOUT; - + sv_frame_t frame = { .func = func, + .bp = args, .lp = locals, - .argc = func->param_count, + .argc = argc, + .arguments_obj = js_mkundef(), }; - - 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_str_flush_local( + sv_vm_t *vm, ant_t *js, sv_func_t *func, + ant_value_t *args, int argc, + ant_value_t *locals, uint16_t slot_idx +) { + if (!func) + return SV_JIT_BAILOUT; + + sv_frame_t frame = { + .func = func, + .bp = args, + .lp = locals, + .argc = argc, + .arguments_obj = js_mkundef(), + }; + + ant_value_t flush = sv_string_builder_flush_slot(vm, js, &frame, slot_idx); + if (is_err(flush)) return flush; + return js_mkundef(); +} + 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; @@ -137,11 +195,305 @@ ant_value_t jit_helper_get_global( return sv_global_get_interned_ic(js, str, func, ip); } -ant_value_t jit_helper_special_obj(ant_t *js, uint32_t which) { +ant_value_t jit_helper_special_obj(sv_vm_t *vm, ant_t *js, uint32_t which) { + if (which == 1) return sv_vm_get_new_target(vm, js); + if (which == 2) return sv_vm_get_super_val(vm); if (which == 3) return js_get_module_import_binding(js); return js_mkundef(); } +void jit_helper_define_method_comp( + ant_t *js, + ant_value_t obj, ant_value_t key, ant_value_t fn, uint8_t flags +) { + ant_value_t desc_obj = js_as_obj(obj); + bool is_getter = (flags & 1) != 0; + bool is_setter = (flags & 2) != 0; + if (vtype(key) == T_SYMBOL) { + if (is_getter) { js_set_sym_getter_desc(js, desc_obj, key, fn, JS_DESC_E | JS_DESC_C); return; } + if (is_setter) { js_set_sym_setter_desc(js, desc_obj, key, fn, JS_DESC_E | JS_DESC_C); return; } + js_set_sym(js, obj, key, fn); + return; + } + ant_value_t key_str = sv_key_to_propstr(js, key); + if ((is_getter || is_setter) && vtype(key_str) == T_STR) { + ant_offset_t klen = 0; + ant_offset_t koff = vstr(js, key_str, &klen); + const char *kptr = (const char *)(uintptr_t)(koff); + if (is_getter) js_set_getter_desc(js, desc_obj, kptr, klen, fn, JS_DESC_E | JS_DESC_C); + else js_set_setter_desc(js, desc_obj, kptr, klen, fn, JS_DESC_E | JS_DESC_C); + return; + } + if (vtype(key_str) == T_STR) { + ant_offset_t klen = 0; + ant_offset_t koff = vstr(js, key_str, &klen); + const char *kptr = (const char *)(uintptr_t)(koff); + js_define_own_prop(js, obj, kptr, (size_t)klen, fn); + } else mkprop(js, obj, key_str, fn, 0); +} + +static ant_value_t jit_iter_advance_from_buf( + sv_vm_t *vm, ant_t *js, ant_value_t *iter_buf, int hint, + ant_value_t *out_value, bool *out_done +) { + GC_ROOT_SAVE(root_mark, js); + + int tag = hint ? hint : (int)js_getnum(iter_buf[2]); + switch (tag) { + case SV_ITER_ARRAY: { + ant_value_t arr = iter_buf[0]; + GC_ROOT_PIN(js, arr); + int idx = (int)js_getnum(iter_buf[1]); + ant_offset_t len = js_arr_len(js, arr); + if (idx >= (int)len) { + *out_value = js_mkundef(); + *out_done = true; + } else { + *out_value = js_arr_get(js, arr, (ant_offset_t)idx); + *out_done = false; + iter_buf[1] = tov(idx + 1); + } + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + case SV_ITER_MAP: { + map_iterator_state_t *st = (map_iterator_state_t *)(uintptr_t)js_getnum(iter_buf[0]); + if (!st->current) { + *out_value = js_mkundef(); + *out_done = true; + } else { + map_entry_t *entry = st->current; + ant_value_t value; + switch (st->type) { + case ITER_TYPE_MAP_VALUES: + value = entry->value; + break; + case ITER_TYPE_MAP_KEYS: + value = entry->key_val; + break; + case ITER_TYPE_MAP_ENTRIES: { + ant_value_t pair = js_mkarr(js); + js_arr_push(js, pair, entry->key_val); + js_arr_push(js, pair, entry->value); + value = pair; + break; + } + default: + value = js_mkundef(); + } + st->current = entry->hh.next; + *out_value = value; + *out_done = false; + } + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + case SV_ITER_SET: { + set_iterator_state_t *st = (set_iterator_state_t *)(uintptr_t)js_getnum(iter_buf[0]); + if (!st->current) { + *out_value = js_mkundef(); + *out_done = true; + } else { + set_entry_t *entry = st->current; + ant_value_t value; + if (st->type == ITER_TYPE_SET_ENTRIES) { + ant_value_t pair = js_mkarr(js); + js_arr_push(js, pair, entry->value); + js_arr_push(js, pair, entry->value); + value = pair; + } else { + value = entry->value; + } + st->current = entry->hh.next; + *out_value = value; + *out_done = false; + } + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + case SV_ITER_STRING: { + ant_value_t str = iter_buf[0]; + GC_ROOT_PIN(js, str); + int idx = (int)js_getnum(iter_buf[1]); + ant_offset_t slen = str_len_fast(js, str); + if (idx >= (int)slen) { + *out_value = js_mkundef(); + *out_done = true; + } else { + ant_offset_t off = vstr(js, str, NULL); + utf8proc_int32_t cp; + ant_offset_t cb_len = (ant_offset_t)utf8_next( + (const utf8proc_uint8_t *)(uintptr_t)(off + idx), + (utf8proc_ssize_t)(slen - idx), + &cp + ); + *out_value = js_mkstr(js, (const void *)(uintptr_t)(off + idx), cb_len); + *out_done = false; + iter_buf[1] = tov(idx + (int)cb_len); + } + GC_ROOT_PIN(js, *out_value); + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + default: { + ant_value_t iterator = iter_buf[0]; + ant_value_t next_method = iter_buf[1]; + GC_ROOT_PIN(js, iterator); + GC_ROOT_PIN(js, next_method); + uint8_t ft = vtype(next_method); + if (ft != T_FUNC && ft != T_CFUNC) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "iterator.next is not a function"); + } + ant_value_t result = sv_vm_call(vm, js, next_method, iterator, NULL, 0, NULL, false); + if (is_err(result)) { + GC_ROOT_RESTORE(js, root_mark); + return result; + } + GC_ROOT_PIN(js, result); + if (!is_object_type(result)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator result is not an object"); + } + ant_value_t done = js_mkundef(); + GC_ROOT_PIN(js, done); + sv_iter_result_unpack(js, result, &done, out_value); + GC_ROOT_PIN(js, *out_value); + if (is_err(done)) { + GC_ROOT_RESTORE(js, root_mark); + return done; + } + if (is_err(*out_value)) { + GC_ROOT_RESTORE(js, root_mark); + return *out_value; + } + *out_done = js_truthy(js, done); + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + }} +} + +ant_value_t jit_helper_for_of( + sv_vm_t *vm, ant_t *js, + ant_value_t iterable, ant_value_t *iter_buf +) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, iterable); + + if (vtype(iterable) == T_ARR) { + iter_buf[0] = iterable; + iter_buf[1] = tov(0); + iter_buf[2] = tov(SV_ITER_ARRAY); + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + if (vtype(iterable) == T_STR) { + if (str_is_heap_rope(iterable) || str_is_heap_builder(iterable)) { + iterable = str_materialize(js, iterable); + if (is_err(iterable)) { + GC_ROOT_RESTORE(js, root_mark); + return iterable; + } + GC_ROOT_PIN(js, iterable); + } + iter_buf[0] = iterable; + iter_buf[1] = tov(0); + iter_buf[2] = tov(SV_ITER_STRING); + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + ant_value_t iter_fn = js_get_sym(js, iterable, get_iterator_sym()); + GC_ROOT_PIN(js, iter_fn); + uint8_t ft = vtype(iter_fn); + if (ft != T_FUNC && ft != T_CFUNC) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "not iterable"); + } + ant_value_t iterator = sv_vm_call(vm, js, iter_fn, iterable, NULL, 0, NULL, false); + if (is_err(iterator)) { + GC_ROOT_RESTORE(js, root_mark); + return iterator; + } + GC_ROOT_PIN(js, iterator); + + map_iterator_state_t *map_st; + iter_type_t map_type; + if (sv_is_map_iter(js, iterator, &map_st, &map_type)) { + iter_buf[0] = ANT_PTR(map_st); + iter_buf[1] = tov((double)map_type); + iter_buf[2] = tov(SV_ITER_MAP); + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + set_iterator_state_t *set_st; + iter_type_t set_type; + if (sv_is_set_iter(js, iterator, &set_st, &set_type)) { + iter_buf[0] = ANT_PTR(set_st); + iter_buf[1] = tov((double)set_type); + iter_buf[2] = tov(SV_ITER_SET); + GC_ROOT_RESTORE(js, root_mark); + return tov(0); + } + + ant_value_t next_method = js_getprop_fallback(js, iterator, "next"); + GC_ROOT_PIN(js, next_method); + iter_buf[0] = iterator; + iter_buf[1] = next_method; + iter_buf[2] = tov(SV_ITER_GENERIC); + GC_ROOT_RESTORE(js, root_mark); + return tov(0); +} + +void jit_helper_destructure_close( + sv_vm_t *vm, ant_t *js, + ant_value_t *iter_buf +) { + int old_sp = vm->sp; + if (!jit_vm_ensure_stack(vm, 3)) + return; + + vm->stack[vm->sp++] = iter_buf[0]; + vm->stack[vm->sp++] = iter_buf[1]; + vm->stack[vm->sp++] = iter_buf[2]; + sv_op_iter_close(vm, js); + vm->sp = old_sp; +} + +ant_value_t jit_helper_destructure_next( + sv_vm_t *vm, ant_t *js, + ant_value_t *iter_buf +) { + int old_sp = vm->sp; + if (!jit_vm_ensure_stack(vm, 4)) + return js_mkerr(js, "stack overflow"); + + vm->stack[vm->sp++] = iter_buf[0]; + vm->stack[vm->sp++] = iter_buf[1]; + vm->stack[vm->sp++] = iter_buf[2]; + + ant_value_t value = js_mkundef(); + bool done = false; + ant_value_t status = sv_iter_advance(vm, js, 0, &value, &done); + if (is_err(status)) { + vm->sp = old_sp; + return status; + } + + iter_buf[0] = vm->stack[old_sp + 0]; + iter_buf[1] = vm->stack[old_sp + 1]; + iter_buf[2] = vm->stack[old_sp + 2]; + iter_buf[3] = done ? js_mkundef() : value; + vm->sp = old_sp; + return status; +} + ant_value_t jit_helper_seq(sv_vm_t *vm, ant_t *js, ant_value_t l, ant_value_t r) { return mkval(T_BOOL, strict_eq_values(js, l, r)); } diff --git a/src/silver/swarm.c b/src/silver/swarm.c index d5e86a0..9326fcb 100644 --- a/src/silver/swarm.c +++ b/src/silver/swarm.c @@ -53,6 +53,7 @@ static void jit_load_externals_once(sv_jit_ctx_t *jc) { LOAD_EXT(jit_helper_mod); LOAD_EXT(jit_helper_str_append_local); LOAD_EXT(jit_helper_str_append_local_snapshot); + LOAD_EXT(jit_helper_str_flush_local); LOAD_EXT(jit_helper_lt); LOAD_EXT(jit_helper_le); LOAD_EXT(jit_helper_gt); @@ -61,6 +62,9 @@ static void jit_load_externals_once(sv_jit_ctx_t *jc) { LOAD_EXT(jit_helper_apply); LOAD_EXT(jit_helper_rest); LOAD_EXT(jit_helper_special_obj); + LOAD_EXT(jit_helper_for_of); + LOAD_EXT(jit_helper_destructure_close); + LOAD_EXT(jit_helper_destructure_next); LOAD_EXT(jit_helper_get_global); LOAD_EXT(jit_helper_get_field); LOAD_EXT(jit_helper_to_propkey); @@ -72,6 +76,7 @@ static void jit_load_externals_once(sv_jit_ctx_t *jc) { LOAD_EXT(jit_helper_call_is_proto); LOAD_EXT(jit_helper_get_length); LOAD_EXT(jit_helper_define_field); + LOAD_EXT(jit_helper_define_method_comp); LOAD_EXT(jit_helper_seq); LOAD_EXT(jit_helper_eq); LOAD_EXT(jit_helper_ne); @@ -1050,8 +1055,8 @@ static bool jit_inlineable(sv_func_t *f) { case OP_GET_FIELD: case OP_GET_FIELD2: case OP_GET_GLOBAL: break; case OP_SPECIAL_OBJ: - if (sv_get_u8(ip + 1) != 1) return false; - break; + // TODO: RE_ENABLE once SPECIAL_OBJ semantics match the interpreter in JIT. + return false; case OP_NOP: case OP_LINE_NUM: case OP_COL_NUM: case OP_LABEL: break; default: @@ -1854,6 +1859,7 @@ typedef struct { bool needs_bailout; bool needs_inc_local; bool needs_args_buf; + bool needs_iter_roots; bool needs_close_upval; bool needs_tco_args; bool needs_ic_epoch; @@ -1889,7 +1895,10 @@ static jit_features_t jit_prescan_features(sv_func_t *func) { case OP_TAIL_CALL: case OP_TAIL_CALL_METHOD: case OP_ARRAY: case OP_NEW: case OP_APPLY: case OP_NEW_APPLY: + case OP_FOR_OF: + case OP_DESTRUCTURE_INIT: case OP_DESTRUCTURE_NEXT: case OP_DESTRUCTURE_CLOSE: f.needs_args_buf = true; + f.needs_iter_roots = true; if (op == OP_TAIL_CALL || op == OP_TAIL_CALL_METHOD) f.needs_tco_args = true; break; @@ -1962,7 +1971,9 @@ static bool jit_is_eligible(sv_func_t *func) { case OP_OBJECT: case OP_ARRAY: case OP_SET_PROTO: case OP_SWAP: case OP_ROT3L: case OP_IN: case OP_GET_LENGTH: - case OP_DEFINE_FIELD: case OP_SEQ: case OP_EQ: + case OP_DEFINE_FIELD: case OP_DEFINE_METHOD_COMP: case OP_SEQ: case OP_EQ: + case OP_FOR_OF: + case OP_DESTRUCTURE_INIT: case OP_DESTRUCTURE_NEXT: case OP_DESTRUCTURE_CLOSE: case OP_INC_LOCAL: case OP_DEC_LOCAL: case OP_ADD_LOCAL: case OP_STR_APPEND_LOCAL: case OP_STR_ALC_SNAPSHOT: @@ -1983,13 +1994,12 @@ static bool jit_is_eligible(sv_func_t *func) { break; } case OP_SPECIAL_OBJ: - if (sv_get_u8(ip + 1) != 1 && sv_get_u8(ip + 1) != 3) { - if (sv_jit_warn_unlikely) - fprintf(stderr, "jit: ineligible op SPECIAL_OBJ(%d) in %s\n", - sv_get_u8(ip + 1), - func->name ? func->name : ""); - eligible = false; - } + // TODO: RE_ENABLE once SPECIAL_OBJ semantics match the interpreter in JIT. + if (sv_jit_warn_unlikely) + fprintf(stderr, "jit: ineligible op SPECIAL_OBJ(%d) in %s\n", + sv_get_u8(ip + 1), + func->name ? func->name : ""); + eligible = false; break; default: if (sv_jit_warn_unlikely) @@ -2125,25 +2135,40 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_type_t sal_ret = MIR_JSVAL; MIR_item_t str_append_local_proto = MIR_new_proto(ctx, "sal_proto", - 1, &sal_ret, 6, + 1, &sal_ret, 8, MIR_T_I64, "vm", MIR_T_I64, "js", MIR_T_P, "func", + MIR_T_P, "args", + MIR_T_I32, "argc", MIR_T_P, "locals", - MIR_T_I32, "local_idx", + MIR_T_I32, "slot_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, + 1, &sals_ret, 9, MIR_T_I64, "vm", MIR_T_I64, "js", MIR_T_P, "func", + MIR_T_P, "args", + MIR_T_I32, "argc", MIR_T_P, "locals", - MIR_T_I32, "local_idx", + MIR_T_I32, "slot_idx", MIR_JSVAL, "lhs", MIR_JSVAL, "rhs"); + MIR_type_t sfl_ret = MIR_JSVAL; + MIR_item_t str_flush_local_proto = MIR_new_proto(ctx, "sfl_proto", + 1, &sfl_ret, 7, + MIR_T_I64, "vm", + MIR_T_I64, "js", + MIR_T_P, "func", + MIR_T_P, "args", + MIR_T_I32, "argc", + MIR_T_P, "locals", + MIR_T_I32, "slot_idx"); + MIR_type_t truthy_ret = MIR_T_I64; MIR_item_t truthy_proto = MIR_new_proto(ctx, "truthy_proto", 1, &truthy_ret, 2, @@ -2211,6 +2236,14 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_T_P, "str", MIR_T_I32, "len"); + MIR_item_t define_method_comp_proto = MIR_new_proto(ctx, "dmc_proto", + 0, NULL, 5, + MIR_T_I64, "js", + MIR_JSVAL, "obj", + MIR_JSVAL, "key", + MIR_JSVAL, "fn", + MIR_T_I32, "flags"); + MIR_type_t pf_ret = MIR_JSVAL; MIR_item_t put_field_proto = MIR_new_proto(ctx, "pf_proto", 1, &pf_ret, 6, @@ -2275,10 +2308,32 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_type_t special_obj_ret = MIR_JSVAL; MIR_item_t special_obj_proto = MIR_new_proto(ctx, "soj_proto", - 1, &special_obj_ret, 2, + 1, &special_obj_ret, 3, + MIR_T_I64, "vm", MIR_T_I64, "js", MIR_T_I32, "which"); + MIR_type_t for_of_ret = MIR_JSVAL; + MIR_item_t for_of_proto = MIR_new_proto(ctx, "fo_proto", + 1, &for_of_ret, 4, + MIR_T_I64, "vm", + MIR_T_I64, "js", + MIR_JSVAL, "iterable", + MIR_T_P, "iter_buf"); + + MIR_item_t destructure_close_proto = MIR_new_proto(ctx, "dclose_proto", + 0, NULL, 3, + MIR_T_I64, "vm", + MIR_T_I64, "js", + MIR_T_P, "iter_buf"); + + MIR_type_t destructure_next_ret = MIR_JSVAL; + MIR_item_t destructure_next_proto = MIR_new_proto(ctx, "dnext_proto", + 1, &destructure_next_ret, 3, + MIR_T_I64, "vm", + MIR_T_I64, "js", + MIR_T_P, "iter_buf"); + MIR_item_t imp_add = MIR_new_import(ctx, "jit_helper_add"); MIR_item_t imp_sub = MIR_new_import(ctx, "jit_helper_sub"); MIR_item_t imp_mul = MIR_new_import(ctx, "jit_helper_mul"); @@ -2288,6 +2343,8 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos 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_str_flush_local = + MIR_new_import(ctx, "jit_helper_str_flush_local"); 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"); @@ -2296,6 +2353,9 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_item_t imp_apply = MIR_new_import(ctx, "jit_helper_apply"); MIR_item_t imp_rest = MIR_new_import(ctx, "jit_helper_rest"); MIR_item_t imp_special_obj = MIR_new_import(ctx, "jit_helper_special_obj"); + MIR_item_t imp_for_of = MIR_new_import(ctx, "jit_helper_for_of"); + MIR_item_t imp_dnext = MIR_new_import(ctx, "jit_helper_destructure_next"); + MIR_item_t imp_dclose = MIR_new_import(ctx, "jit_helper_destructure_close"); MIR_item_t imp_gg = MIR_new_import(ctx, "jit_helper_get_global"); MIR_item_t imp_get_field = MIR_new_import(ctx, "jit_helper_get_field"); MIR_item_t imp_to_propkey = MIR_new_import(ctx, "jit_helper_to_propkey"); @@ -2305,6 +2365,7 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos MIR_item_t imp_in = MIR_new_import(ctx, "jit_helper_in"); MIR_item_t imp_get_length = MIR_new_import(ctx, "jit_helper_get_length"); MIR_item_t imp_define_field = MIR_new_import(ctx, "jit_helper_define_field"); + MIR_item_t imp_define_method_comp = MIR_new_import(ctx, "jit_helper_define_method_comp"); MIR_item_t imp_seq = MIR_new_import(ctx, "jit_helper_seq"); MIR_item_t imp_eq = MIR_new_import(ctx, "jit_helper_eq"); MIR_item_t imp_ne = MIR_new_import(ctx, "jit_helper_ne"); @@ -2485,6 +2546,23 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos mir_load_imm(ctx, jit_func, r_args_buf, 0); } + MIR_reg_t r_iter_roots = MIR_new_func_reg(ctx, jit_func->u.func, MIR_T_I64, "iter_roots"); + if (feat.needs_iter_roots && vs.max > 0) { + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_ALLOCA, + MIR_new_reg_op(ctx, r_iter_roots), + MIR_new_uint_op(ctx, (uint64_t)vs.max * sizeof(ant_value_t)))); + for (int i = 0; i < vs.max; i++) { + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_iter_roots, 0, 1), + MIR_new_uint_op(ctx, mkval(T_UNDEF, 0)))); + } + } else { + mir_load_imm(ctx, jit_func, r_iter_roots, 0); + } + MIR_reg_t r_tco_args = MIR_new_func_reg(ctx, jit_func->u.func, MIR_T_I64, "tco_args"); MIR_reg_t r_cond_d = 0, r_cond_nan = 0, r_cond_zd = 0, r_cond_zero = 0; bool needs_bailout = feat.needs_bailout; @@ -4934,14 +5012,16 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos } case OP_SPECIAL_OBJ: { + // TODO: RE_ENABLE once SPECIAL_OBJ semantics match the interpreter in JIT. uint8_t which = sv_get_u8(ip + 1); MIR_reg_t dst = vstack_push(&vs); - if (which == 3) { + if (which == 2 || which == 3) { MIR_append_insn(ctx, jit_func, - MIR_new_call_insn(ctx, 5, + MIR_new_call_insn(ctx, 6, MIR_new_ref_op(ctx, special_obj_proto), MIR_new_ref_op(ctx, imp_special_obj), MIR_new_reg_op(ctx, dst), + MIR_new_reg_op(ctx, r_vm), MIR_new_reg_op(ctx, r_js), MIR_new_int_op(ctx, (int64_t)which))); } else mir_load_imm(ctx, jit_func, dst, mkval(T_UNDEF, 0)); @@ -5322,55 +5402,86 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos uint16_t slot_idx = sv_get_u16(ip + 1); int pre_op_sp = vs.sp; if ((int)slot_idx < param_count) { + MIR_label_t arg_in_range = 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)slot_idx))); 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; - } + MIR_append_insn(ctx, jit_func, arg_in_range); - 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); - 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, 11, + 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_args), + MIR_new_reg_op(ctx, r_argc), + MIR_new_uint_op(ctx, 0), + MIR_new_int_op(ctx, (int64_t)slot_idx), + MIR_new_reg_op(ctx, rhs))); + } else { + uint16_t local_idx = (uint16_t)(slot_idx - (uint16_t)param_count); + if (local_idx >= (uint16_t)n_locals) { ok = false; break; } - 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))); + 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_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((int)local_idx * (int)sizeof(ant_value_t)), r_lbuf, 0, 1), + MIR_new_reg_op(ctx, local_regs[local_idx]))); + + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 11, + 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_uint_op(ctx, 0), + MIR_new_int_op(ctx, (int64_t)param_count), + MIR_new_reg_op(ctx, r_lbuf), + MIR_new_int_op(ctx, (int64_t)slot_idx), + MIR_new_reg_op(ctx, rhs))); + + 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_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, @@ -5403,58 +5514,92 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos uint16_t slot_idx = sv_get_u16(ip + 1); int pre_op_sp = vs.sp; if ((int)slot_idx < param_count) { + MIR_label_t arg_in_range = 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)slot_idx))); 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; - } + MIR_append_insn(ctx, jit_func, arg_in_range); - 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); - 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, 12, + 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_args), + MIR_new_reg_op(ctx, r_argc), + MIR_new_uint_op(ctx, 0), + MIR_new_int_op(ctx, (int64_t)slot_idx), + MIR_new_reg_op(ctx, lhs), + MIR_new_reg_op(ctx, rhs))); + } else { + uint16_t local_idx = (uint16_t)(slot_idx - (uint16_t)param_count); + if (local_idx >= (uint16_t)n_locals) { ok = false; break; } - 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))); + 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_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((int)local_idx * (int)sizeof(ant_value_t)), r_lbuf, 0, 1), + MIR_new_reg_op(ctx, local_regs[local_idx]))); + + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 12, + 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_uint_op(ctx, 0), + MIR_new_int_op(ctx, (int64_t)param_count), + MIR_new_reg_op(ctx, r_lbuf), + MIR_new_int_op(ctx, (int64_t)slot_idx), + MIR_new_reg_op(ctx, lhs), + MIR_new_reg_op(ctx, rhs))); + + 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_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, @@ -5717,6 +5862,26 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos break; } + case OP_DEFINE_METHOD_COMP: { + uint8_t flags = sv_get_u8(ip + 1); + 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); + vstack_ensure_boxed(&vs, vs.sp - 3, ctx, jit_func, r_d_slot); + MIR_reg_t fn_val = vstack_pop(&vs); + MIR_reg_t key = vstack_pop(&vs); + MIR_reg_t obj = vstack_top(&vs); + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 7, + MIR_new_ref_op(ctx, define_method_comp_proto), + MIR_new_ref_op(ctx, imp_define_method_comp), + MIR_new_reg_op(ctx, r_js), + MIR_new_reg_op(ctx, obj), + MIR_new_reg_op(ctx, key), + MIR_new_reg_op(ctx, fn_val), + MIR_new_int_op(ctx, (int64_t)flags))); + break; + } + case OP_GET_ELEM: { 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); @@ -5844,6 +6009,147 @@ sv_jit_func_t sv_jit_compile(ant_t *js, sv_func_t *func, sv_closure_t *hint_clos break; } + case OP_FOR_OF: + case OP_DESTRUCTURE_INIT: { + vstack_ensure_boxed(&vs, vs.sp - 1, ctx, jit_func, r_d_slot); + MIR_reg_t iterable = vstack_pop(&vs); + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 7, + MIR_new_ref_op(ctx, for_of_proto), + MIR_new_ref_op(ctx, imp_for_of), + MIR_new_reg_op(ctx, r_err_tmp), + MIR_new_reg_op(ctx, r_vm), + MIR_new_reg_op(ctx, r_js), + MIR_new_reg_op(ctx, iterable), + MIR_new_reg_op(ctx, r_args_buf))); + 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); + for (int i = 0; i < 3; i++) { + MIR_reg_t dst = vstack_push(&vs); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, dst), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_args_buf, 0, 1))); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((vs.sp - 1) * (int)sizeof(ant_value_t)), r_iter_roots, 0, 1), + MIR_new_reg_op(ctx, dst))); + } + break; + } + + case OP_DESTRUCTURE_NEXT: { + int iter_base = vs.sp - 3; + for (int i = 0; i < 3; i++) { + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_args_buf, 0, 1), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((iter_base + i) * (int)sizeof(ant_value_t)), r_iter_roots, 0, 1))); + } + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 6, + MIR_new_ref_op(ctx, destructure_next_proto), + MIR_new_ref_op(ctx, imp_dnext), + MIR_new_reg_op(ctx, r_err_tmp), + MIR_new_reg_op(ctx, r_vm), + MIR_new_reg_op(ctx, r_js), + MIR_new_reg_op(ctx, r_args_buf))); + 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); + vstack_pop(&vs); + vstack_pop(&vs); + vstack_pop(&vs); + for (int i = 0; i < 4; i++) { + MIR_reg_t dst = vstack_push(&vs); + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, dst), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_args_buf, 0, 1))); + if (i < 3) { + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((vs.sp - 1) * (int)sizeof(ant_value_t)), r_iter_roots, 0, 1), + MIR_new_reg_op(ctx, dst))); + } + } + break; + } + + case OP_DESTRUCTURE_CLOSE: { + int iter_base = vs.sp - 3; + for (int i = 0; i < 3; i++) { + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_args_buf, 0, 1), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((iter_base + i) * (int)sizeof(ant_value_t)), r_iter_roots, 0, 1))); + } + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 5, + MIR_new_ref_op(ctx, destructure_close_proto), + MIR_new_ref_op(ctx, imp_dclose), + MIR_new_reg_op(ctx, r_vm), + MIR_new_reg_op(ctx, r_js), + MIR_new_reg_op(ctx, r_args_buf))); + vstack_pop(&vs); + vstack_pop(&vs); + vstack_pop(&vs); + break; + } + case OP_PUT_GLOBAL: { uint32_t idx = sv_get_u32(ip + 1); if (idx >= (uint32_t)func->atom_count) { ok = false; break; } @@ -7331,9 +7637,86 @@ 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; + case OP_STR_FLUSH_LOCAL: { + uint16_t slot_idx = sv_get_u16(ip + 1); + int pre_op_sp = vs.sp; + if ((int)slot_idx < param_count) { + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 10, + MIR_new_ref_op(ctx, str_flush_local_proto), + MIR_new_ref_op(ctx, imp_str_flush_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_args), + MIR_new_reg_op(ctx, r_argc), + MIR_new_uint_op(ctx, 0), + MIR_new_int_op(ctx, (int64_t)slot_idx))); + } else { + uint16_t local_idx = (uint16_t)(slot_idx - (uint16_t)param_count); + if (local_idx >= (uint16_t)n_locals) { ok = false; break; } + + MIR_append_insn(ctx, jit_func, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)((int)local_idx * (int)sizeof(ant_value_t)), r_lbuf, 0, 1), + MIR_new_reg_op(ctx, local_regs[local_idx]))); + + MIR_append_insn(ctx, jit_func, + MIR_new_call_insn(ctx, 10, + MIR_new_ref_op(ctx, str_flush_local_proto), + MIR_new_ref_op(ctx, imp_str_flush_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_uint_op(ctx, 0), + MIR_new_int_op(ctx, (int64_t)param_count), + MIR_new_reg_op(ctx, r_lbuf), + MIR_new_int_op(ctx, (int64_t)slot_idx))); + + 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_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); + + 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_SET_NAME: { uint32_t atom_idx = sv_get_u32(ip + 1); diff --git a/tests/amp/mini_amp_screen_repro.js b/tests/amp/mini_amp_screen_repro.js new file mode 100644 index 0000000..36c53e8 --- /dev/null +++ b/tests/amp/mini_amp_screen_repro.js @@ -0,0 +1,123 @@ +function e4(char = " ", style = {}, width = 1, hyperlink) { + return { char, style: { ...style }, width, hyperlink }; +} + +const bw = e4(" ", {}); + +function sameColor(a, b) { + if (a === b) return true; + if (a === void 0 || b === void 0) return false; + if (a.type !== b.type) return false; + if (a.alpha !== b.alpha) return false; + if (a.type === "index") return a.value === b.value; + if (a.type === "rgb") + return a.value.r === b.value.r && a.value.g === b.value.g && a.value.b === b.value.b; + return true; +} + +function sameStyle(a, b) { + return ( + sameColor(a.fg, b.fg) && + sameColor(a.bg, b.bg) && + a.bold === b.bold && + a.italic === b.italic && + a.underline === b.underline && + a.strikethrough === b.strikethrough && + a.reverse === b.reverse && + a.dim === b.dim + ); +} + +function sameCell(a, b) { + return ( + a.char === b.char && + a.width === b.width && + sameStyle(a.style, b.style) && + a.hyperlink === b.hyperlink + ); +} + +class BufferGrid { + constructor(width, height) { + this.width = width; + this.height = height; + this.resize(width, height); + } + resize(width, height) { + this.width = width; + this.height = height; + this.cells = Array(height).fill(null).map(() => Array(width).fill(null).map(() => bw)); + } + setCell(x, y, cell) { + this.cells[y][x] = { ...cell, style: { ...cell.style } }; + } + getCell(x, y) { + return this.cells[y][x]; + } + clear() { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) this.cells[y][x] = bw; + } + } + rowString(y) { + let out = ""; + for (let x = 0; x < this.width; x++) out += this.cells[y][x].char; + return out; + } +} + +class Screen { + constructor(width, height) { + this.width = width; + this.height = height; + this.frontBuffer = new BufferGrid(width, height); + this.backBuffer = new BufferGrid(width, height); + } + getBuffer() { + return this.backBuffer; + } + clear() { + this.backBuffer.clear(); + } + setCell(x, y, cell) { + this.backBuffer.setCell(x, y, cell); + } + getDiff() { + const diff = []; + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + const a = this.frontBuffer.getCell(x, y); + const b = this.backBuffer.getCell(x, y); + if (!sameCell(a, b)) diff.push({ x, y, cell: b }); + } + } + return diff; + } + present() { + const old = this.frontBuffer; + this.frontBuffer = this.backBuffer; + this.backBuffer = old; + } +} + +function drawText(screen, x, y, text) { + for (let i = 0; i < text.length; i++) { + screen.setCell(x + i, y, e4(text[i], { fg: { type: "index", value: 7 } })); + } +} + +const screen = new Screen(20, 4); + +for (let frame = 0; frame < 5; frame++) { + screen.clear(); + drawText(screen, 0, 0, "frame:" + frame); + drawText(screen, 0, 1, "ball:" + " ".repeat(frame) + "o"); + const diff = screen.getDiff(); + console.log(JSON.stringify({ + frame, + diffCount: diff.length, + row0: screen.backBuffer.rowString(0), + row1: screen.backBuffer.rowString(1), + })); + screen.present(); +} diff --git a/tests/amp/mini_amp_state_repro.js b/tests/amp/mini_amp_state_repro.js new file mode 100644 index 0000000..0035ad9 --- /dev/null +++ b/tests/amp/mini_amp_state_repro.js @@ -0,0 +1,198 @@ +class Widget { + constructor(key) { + this.key = key; + } + canUpdate(other) { + return this.constructor === other.constructor && this.key === other.key; + } +} + +let BUILD_OWNER = null; + +class Element { + constructor(widget) { + this.widget = widget; + this.parent = void 0; + this._children = []; + this._dirty = false; + this._mounted = false; + } + get dirty() { + return this._dirty; + } + addChild(child) { + child.parent = this; + this._children.push(child); + } + removeChild(child) { + const i = this._children.indexOf(child); + if (i >= 0) this._children.splice(i, 1); + child.parent = void 0; + } + markMounted() { + this._mounted = true; + } + unmount() { + this._mounted = false; + this._dirty = false; + } + markNeedsRebuild() { + if (!this._mounted) return; + this._dirty = true; + BUILD_OWNER.scheduleBuildFor(this); + } + markNeedsBuild() { + this.markNeedsRebuild(); + } + update(widget) { + this.widget = widget; + } +} + +class BuildOwner { + constructor() { + this._dirtyElements = new Set(); + } + scheduleBuildFor(el) { + this._dirtyElements.add(el); + } + buildScopes() { + const batch = Array.from(this._dirtyElements); + this._dirtyElements.clear(); + for (const el of batch) { + if (el.dirty) { + el.performRebuild(); + el._dirty = false; + } + } + } +} + +class State { + _mounted = false; + _mount(widget, context) { + this.widget = widget; + this.context = context; + this._mounted = true; + this.initState?.(); + } + _update(widget) { + const oldWidget = this.widget; + this.widget = widget; + this.didUpdateWidget?.(oldWidget); + } + _unmount() { + this._mounted = false; + this.dispose?.(); + } + setState(fn) { + if (!this._mounted) throw new Error("setState() called after dispose()"); + if (fn) fn(); + this.context.element.markNeedsBuild(); + } +} + +class BuildContext { + constructor(element, widget) { + this.element = element; + this.widget = widget; + } +} + +class RenderObjectWidget extends Widget { + createElement() { + return new RenderObjectElement(this); + } +} + +class RenderObjectElement extends Element { + mount() { + this.renderObject = this.widget.createRenderObject(); + this.markMounted(); + } + update(widget) { + super.update(widget); + widget.updateRenderObject(this.renderObject); + } + performRebuild() {} +} + +class StatefulWidget extends Widget { + createElement() { + return new StatefulElement(this); + } +} + +class StatefulElement extends Element { + mount() { + this._context = new BuildContext(this, this.widget); + this._state = this.widget.createState(); + this._state._mount(this.widget, this._context); + this.rebuild(); + this.markMounted(); + } + performRebuild() { + this.rebuild(); + } + rebuild() { + const next = this._state.build(this._context); + if (!this._child) { + this._child = next.createElement(); + this.addChild(this._child); + this._child.mount(); + return; + } + if (this._child.widget.canUpdate(next)) { + this._child.update(next); + } else { + this._child.unmount(); + this.removeChild(this._child); + this._child = next.createElement(); + this.addChild(this._child); + this._child.mount(); + } + } +} + +class Label extends RenderObjectWidget { + constructor(text) { + super(); + this.text = text; + } + createRenderObject() { + return { text: this.text }; + } + updateRenderObject(ro) { + ro.text = this.text; + console.log("renderObject updated ->", JSON.stringify(ro.text)); + } +} + +class CounterApp extends StatefulWidget { + createState() { + return new CounterState(); + } +} + +class CounterState extends State { + initState() { + this.count = 0; + } + build() { + console.log("build ->", this.count); + return new Label("count:" + this.count); + } +} + +BUILD_OWNER = new BuildOwner(); + +const root = new CounterApp().createElement(); +root.mount(); + +for (let i = 1; i <= 5; i++) { + root._state.setState(() => { + root._state.count = i; + }); + BUILD_OWNER.buildScopes(); + console.log("visible now ->", root._child.renderObject.text); +} diff --git a/tests/amp/mini_amp_tui_repro.js b/tests/amp/mini_amp_tui_repro.js new file mode 100644 index 0000000..f80e783 --- /dev/null +++ b/tests/amp/mini_amp_tui_repro.js @@ -0,0 +1,213 @@ +function e4(char = " ", style = {}, width = 1) { + return { char, style: { ...style }, width }; +} + +const bw = e4(" ", {}); + +function sameColor(a, b) { + if (a === b) return true; + if (a === void 0 || b === void 0) return false; + if (a.type !== b.type) return false; + switch (a.type) { + case "index": + return a.value === b.value; + case "rgb": + return a.value.r === b.value.r && a.value.g === b.value.g && a.value.b === b.value.b; + default: + return true; + } +} + +function sameStyle(a, b) { + return ( + sameColor(a.fg, b.fg) && + sameColor(a.bg, b.bg) && + a.bold === b.bold && + a.italic === b.italic && + a.underline === b.underline && + a.dim === b.dim + ); +} + +function sameCell(a, b) { + return a.char === b.char && a.width === b.width && sameStyle(a.style, b.style); +} + +class BufferGrid { + constructor(width, height) { + this.resize(width, height); + } + resize(width, height) { + this.width = width; + this.height = height; + this.cells = Array(height) + .fill(null) + .map(() => Array(width).fill(null).map(() => bw)); + } + getCell(x, y) { + if (x < 0 || y < 0 || x >= this.width || y >= this.height) return null; + return this.cells[y][x]; + } + setCell(x, y, cell) { + if (x < 0 || y < 0 || x >= this.width || y >= this.height) return; + this.cells[y][x] = { ...cell, style: { ...cell.style } }; + } + clear() { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) this.cells[y][x] = bw; + } + } +} + +class Screen { + constructor(width, height) { + this.width = width; + this.height = height; + this.frontBuffer = new BufferGrid(width, height); + this.backBuffer = new BufferGrid(width, height); + } + clear() { + this.backBuffer.clear(); + } + setCell(x, y, cell) { + this.backBuffer.setCell(x, y, cell); + } + setText(x, y, text, style = {}) { + for (let i = 0; i < text.length; i++) this.setCell(x + i, y, e4(text[i], style)); + } + getDiff() { + const out = []; + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + const a = this.frontBuffer.getCell(x, y) ?? bw; + const b = this.backBuffer.getCell(x, y) ?? bw; + if (!sameCell(a, b)) out.push({ x, y, cell: b }); + } + } + return out; + } + present() { + const old = this.frontBuffer; + this.frontBuffer = this.backBuffer; + this.backBuffer = old; + } +} + +class StringBuilder { + constructor() { + this.parts = []; + } + append(...xs) { + this.parts.push(...xs); + } + toString() { + return this.parts.join(""); + } +} + +function moveTo(y, x) { + return `\x1b[${y + 1};${x + 1}H`; +} + +function ansiStyle(style) { + const out = []; + if (style.bold) out.push("1"); + if (style.dim) out.push("2"); + if (style.fg?.type === "index") out.push(String(30 + style.fg.value)); + if (style.bg?.type === "index") out.push(String(40 + style.bg.value)); + return out.length ? `\x1b[${out.join(";")}m` : "\x1b[0m"; +} + +function renderDiff(diff) { + const sb = new StringBuilder(); + let lastStyle = null; + for (const item of diff) { + sb.append(moveTo(item.y, item.x)); + if (!lastStyle || !sameStyle(lastStyle, item.cell.style)) { + sb.append(ansiStyle(item.cell.style)); + lastStyle = item.cell.style; + } + sb.append(item.cell.char); + } + sb.append("\x1b[0m"); + return sb.toString(); +} + +const width = process.stdout.columns || 80; +const height = process.stdout.rows || 24; +const screen = new Screen(width, height); + +let tick = 0; +let text = ""; +let menuOpen = false; +let running = true; + +function paintFrame() { + screen.clear(); + screen.setText(0, 0, "mini amp tui repro", { fg: { type: "index", value: 6 }, bold: true }); + screen.setText(0, 2, "type text, press m to toggle menu, q to quit", { + fg: { type: "index", value: 7 }, + }); + screen.setText(0, 4, "textbox: [" + text + "]", { + fg: { type: "index", value: 2 }, + }); + screen.setText(0, 6, menuOpen ? "menu: [open]" : "menu: [closed]", { + fg: { type: "index", value: menuOpen ? 3 : 1 }, + bold: menuOpen, + }); + const ballX = 8 + (tick % Math.max(1, Math.min(width - 10, 30))); + screen.setText(0, 8, "ball:", { fg: { type: "index", value: 5 } }); + screen.setText(ballX, 8, "o", { fg: { type: "index", value: 4 }, bold: true }); + screen.setText(0, 10, "tick:" + tick, { fg: { type: "index", value: 7 } }); + + const diff = screen.getDiff(); + const out = renderDiff(diff); + process.stdout.write(out); + screen.present(); +} + +function shutdown() { + if (!running) return; + running = false; + clearInterval(timer); + process.stdout.write("\x1b[0m\x1b[2J\x1b[H"); + if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { + process.stdin.setRawMode(false); + } + process.stdin.pause(); +} + +if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { + process.stdin.setRawMode(true); +} +process.stdin.resume(); +process.stdin.on("data", (chunk) => { + if (typeof chunk !== "string") chunk = String(chunk); + for (const ch of chunk) { + if (ch === "q" || ch === "\u0003") { + shutdown(); + return; + } + if (ch === "m") { + menuOpen = !menuOpen; + paintFrame(); + continue; + } + if (ch === "\u007f") { + text = text.slice(0, -1); + paintFrame(); + continue; + } + if (ch >= " " && ch <= "~") { + text += ch; + paintFrame(); + } + } +}); + +process.stdout.write("\x1b[2J\x1b[H\x1b[?25l"); +paintFrame(); +const timer = setInterval(() => { + tick++; + paintFrame(); +}, 120); diff --git a/tests/amp/mini_amp_widget_tui_repro.js b/tests/amp/mini_amp_widget_tui_repro.js new file mode 100644 index 0000000..95383ce --- /dev/null +++ b/tests/amp/mini_amp_widget_tui_repro.js @@ -0,0 +1,380 @@ +function e4(char = " ", style = {}, width = 1) { + return { char, style: { ...style }, width }; +} + +const bw = e4(" ", {}); + +function sameColor(a, b) { + if (a === b) return true; + if (a === void 0 || b === void 0) return false; + if (a.type !== b.type) return false; + if (a.type === "index") return a.value === b.value; + return true; +} + +function sameStyle(a, b) { + return sameColor(a.fg, b.fg) && sameColor(a.bg, b.bg) && a.bold === b.bold; +} + +function sameCell(a, b) { + return a.char === b.char && a.width === b.width && sameStyle(a.style, b.style); +} + +class BufferGrid { + constructor(width, height) { + this.width = width; + this.height = height; + this.cells = Array(height).fill(null).map(() => Array(width).fill(null).map(() => bw)); + } + clear() { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) this.cells[y][x] = bw; + } + } + setCell(x, y, cell) { + if (x < 0 || y < 0 || x >= this.width || y >= this.height) return; + this.cells[y][x] = { ...cell, style: { ...cell.style } }; + } + getCell(x, y) { + return this.cells[y][x]; + } +} + +class Screen { + constructor(width, height) { + this.width = width; + this.height = height; + this.front = new BufferGrid(width, height); + this.back = new BufferGrid(width, height); + } + clear() { + this.back.clear(); + } + setText(x, y, text, style = {}) { + for (let i = 0; i < text.length; i++) this.back.setCell(x + i, y, e4(text[i], style)); + } + getDiff() { + const out = []; + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + const a = this.front.getCell(x, y); + const b = this.back.getCell(x, y); + if (!sameCell(a, b)) out.push({ x, y, cell: b }); + } + } + return out; + } + present() { + const old = this.front; + this.front = this.back; + this.back = old; + } +} + +function moveTo(y, x) { + return `\x1b[${y + 1};${x + 1}H`; +} + +function styleCode(style) { + const out = []; + if (style.bold) out.push("1"); + if (style.fg?.type === "index") out.push(String(30 + style.fg.value)); + return out.length ? `\x1b[${out.join(";")}m` : "\x1b[0m"; +} + +function renderDiff(diff) { + const parts = []; + let lastStyle = null; + for (const item of diff) { + parts.push(moveTo(item.y, item.x)); + if (!lastStyle || !sameStyle(lastStyle, item.cell.style)) { + parts.push(styleCode(item.cell.style)); + lastStyle = item.cell.style; + } + parts.push(item.cell.char); + } + parts.push("\x1b[0m"); + return parts.join(""); +} + +let BUILD_OWNER = null; +let PIPELINE = null; +let ROOT_ELEMENT = null; +const SCREEN = new Screen(process.stdout.columns || 80, process.stdout.rows || 24); + +class Widget { + canUpdate(other) { + return this.constructor === other.constructor; + } +} + +class Element { + constructor(widget) { + this.widget = widget; + this.parent = void 0; + this._dirty = false; + this._mounted = false; + } + markMounted() { + this._mounted = true; + } + markNeedsRebuild() { + if (!this._mounted) return; + this._dirty = true; + BUILD_OWNER.scheduleBuildFor(this); + } + markNeedsBuild() { + this.markNeedsRebuild(); + } +} + +class BuildOwner { + constructor() { + this.dirty = new Set(); + } + scheduleBuildFor(el) { + this.dirty.add(el); + } + buildScopes() { + const batch = Array.from(this.dirty); + this.dirty.clear(); + for (const el of batch) { + if (el._dirty) { + el.performRebuild(); + el._dirty = false; + } + } + } +} + +class BuildContext { + constructor(element, widget) { + this.element = element; + this.widget = widget; + } +} + +class State { + _mount(widget, context) { + this.widget = widget; + this.context = context; + this._mounted = true; + this.initState?.(); + } + _update(widget) { + const oldWidget = this.widget; + this.widget = widget; + this.didUpdateWidget?.(oldWidget); + } + setState(fn) { + if (fn) fn(); + this.context.element.markNeedsBuild(); + } +} + +class RenderObject { + constructor() { + this._attached = false; + this._needsLayout = true; + this._needsPaint = true; + } + attach() { + this._attached = true; + } + markNeedsLayout() { + if (!this._attached) return; + this._needsLayout = true; + } + markNeedsPaint() { + if (!this._attached) return; + this._needsPaint = true; + } +} + +class LabelRenderObject extends RenderObject { + constructor(line, text, style) { + super(); + this.line = line; + this.text = text; + this.style = style; + } + update(line, text, style) { + this.line = line; + this.text = text; + this.style = style; + this.markNeedsLayout(); + this.markNeedsPaint(); + } + paint(screen) { + screen.setText(0, this.line, this.text, this.style); + this._needsPaint = false; + this._needsLayout = false; + } +} + +class RenderObjectWidget extends Widget { + createElement() { + return new RenderObjectElement(this); + } +} + +class RenderObjectElement extends Element { + mount() { + this.renderObject = this.widget.createRenderObject(); + this.renderObject.attach(); + this.markMounted(); + } + update(widget) { + this.widget = widget; + this.widget.updateRenderObject(this.renderObject); + } + performRebuild() {} +} + +class StatefulWidget extends Widget { + createElement() { + return new StatefulElement(this); + } +} + +class StatefulElement extends Element { + mount() { + this.context = new BuildContext(this, this.widget); + this.state = this.widget.createState(); + this.state._mount(this.widget, this.context); + this.rebuild(); + this.markMounted(); + } + rebuild() { + const widgets = this.state.build(this.context); + if (!this.children) { + this.children = widgets.map((w) => { + const el = w.createElement(); + el.parent = this; + el.mount(); + return el; + }); + return; + } + for (let i = 0; i < widgets.length; i++) { + const next = widgets[i]; + const child = this.children[i]; + if (child.widget.canUpdate(next)) child.update(next); + } + } + performRebuild() { + this.rebuild(); + } +} + +class Label extends RenderObjectWidget { + constructor(line, text, style) { + super(); + this.line = line; + this.text = text; + this.style = style; + } + createRenderObject() { + return new LabelRenderObject(this.line, this.text, this.style); + } + updateRenderObject(ro) { + ro.update(this.line, this.text, this.style); + } +} + +class App extends StatefulWidget { + createState() { + return new AppState(); + } +} + +class AppState extends State { + initState() { + this.tick = 0; + this.text = ""; + this.menuOpen = false; + } + build() { + return [ + new Label(0, "widget-tree repro", { fg: { type: "index", value: 6 }, bold: true }), + new Label(2, "textbox: [" + this.text + "]", { fg: { type: "index", value: 2 } }), + new Label(4, this.menuOpen ? "menu: [open]" : "menu: [closed]", { + fg: { type: "index", value: this.menuOpen ? 3 : 1 }, + bold: this.menuOpen, + }), + new Label(6, "ball: " + " ".repeat(this.tick % 20) + "o", { + fg: { type: "index", value: 4 }, + }), + new Label(8, "tick:" + this.tick, { fg: { type: "index", value: 7 } }), + ]; + } +} + +class Pipeline { + paint() { + SCREEN.clear(); + for (const child of ROOT_ELEMENT.children) { + if (child.renderObject._needsLayout || child.renderObject._needsPaint) { + child.renderObject.paint(SCREEN); + } else { + child.renderObject.paint(SCREEN); + } + } + process.stdout.write(renderDiff(SCREEN.getDiff())); + SCREEN.present(); + } +} + +BUILD_OWNER = new BuildOwner(); +PIPELINE = new Pipeline(); +ROOT_ELEMENT = new App().createElement(); +ROOT_ELEMENT.mount(); + +function frame() { + BUILD_OWNER.buildScopes(); + PIPELINE.paint(); +} + +if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { + process.stdin.setRawMode(true); +} +process.stdin.resume(); +process.stdin.on("data", (chunk) => { + if (typeof chunk !== "string") chunk = String(chunk); + for (const ch of chunk) { + if (ch === "q" || ch === "\u0003") { + clearInterval(timer); + process.stdout.write("\x1b[0m\x1b[2J\x1b[H"); + process.exit(0); + } + if (ch === "m") { + ROOT_ELEMENT.state.setState(() => { + ROOT_ELEMENT.state.menuOpen = !ROOT_ELEMENT.state.menuOpen; + }); + frame(); + continue; + } + if (ch === "\u007f") { + ROOT_ELEMENT.state.setState(() => { + ROOT_ELEMENT.state.text = ROOT_ELEMENT.state.text.slice(0, -1); + }); + frame(); + continue; + } + if (ch >= " " && ch <= "~") { + ROOT_ELEMENT.state.setState(() => { + ROOT_ELEMENT.state.text += ch; + }); + frame(); + } + } +}); + +process.stdout.write("\x1b[2J\x1b[H\x1b[?25l"); +frame(); +const timer = setInterval(() => { + ROOT_ELEMENT.state.setState(() => { + ROOT_ELEMENT.state.tick++; + }); + frame(); +}, 120); -- 2.51.2