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

Configure Feed

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

inherit type hints from typescript

+858 -13
+12
include/oxc.h
··· 19 19 size_t error_output_len 20 20 ); 21 21 22 + char *OXC_strip_types_with_hints_owned( 23 + const char *input, 24 + const char *filename, 25 + int is_module, 26 + size_t *out_len, 27 + int *out_error, 28 + char **out_hints, 29 + size_t *out_hints_len, 30 + char *error_output, 31 + size_t error_output_len 32 + ); 33 + 22 34 #endif
+38
include/silver/engine.h
··· 144 144 145 145 sv_type_info_t *local_types; 146 146 int local_type_count; 147 + sv_type_info_t *param_hints; 148 + int param_hint_count; 149 + uint8_t return_hint; 147 150 148 151 int param_count; 149 152 int upvalue_count; ··· 182 185 uint32_t jit_compiled_tfb_ver; 183 186 uint8_t *type_feedback; 184 187 uint8_t *local_type_feedback; 188 + uint8_t *param_type_feedback; 185 189 uint64_t ctor_prop_samples; 186 190 uint64_t ctor_prop_hist[17]; 187 191 uint8_t ctor_inobj_limit; ··· 918 922 if (neu != old) { func->type_feedback[off] = neu; func->tfb_version++; } 919 923 }} 920 924 925 + static inline void sv_tfb_seed_param_hints(sv_func_t *fn) { 926 + if (!fn->param_type_feedback || !fn->param_hints) return; 927 + int n = fn->param_hint_count < fn->param_count 928 + ? fn->param_hint_count : fn->param_count; 929 + for (int i = 0; i < n; i++) { 930 + if (fn->param_hints[i].type == SV_TI_NUM) 931 + fn->param_type_feedback[i] |= SV_TFB_NUM; 932 + } 933 + } 934 + 921 935 static inline void sv_tfb_ensure(sv_func_t *fn) { 922 936 if (!fn->type_feedback && fn->code_len > 0) 923 937 fn->type_feedback = calloc((size_t)fn->code_len, 1); 924 938 if (!fn->local_type_feedback && fn->max_locals > 0) 925 939 fn->local_type_feedback = calloc((size_t)fn->max_locals, 1); 940 + if (!fn->param_type_feedback && fn->param_count > 0) { 941 + fn->param_type_feedback = calloc((size_t)fn->param_count, 1); 942 + sv_tfb_seed_param_hints(fn); 943 + } 944 + } 945 + 946 + static inline void sv_tfb_record_param(sv_func_t *func, int idx, ant_value_t v) { 947 + if (func->param_type_feedback && idx >= 0 && idx < func->param_count) { 948 + uint8_t old = func->param_type_feedback[idx]; 949 + uint8_t neu = old | sv_tfb_classify(v); 950 + if (neu != old) { func->param_type_feedback[idx] = neu; func->tfb_version++; } 951 + } 952 + } 953 + 954 + static inline bool sv_tfb_param_numeric_hint(const sv_func_t *func, int idx) { 955 + if (!func || idx < 0 || idx >= func->param_count) return false; 956 + uint8_t fb = func->param_type_feedback ? func->param_type_feedback[idx] : 0; 957 + if (fb && (fb & ~SV_TFB_NUM)) return false; 958 + if (fb == SV_TFB_NUM) return true; 959 + return ( 960 + func->param_hints && 961 + idx < func->param_hint_count && 962 + func->param_hints[idx].type == SV_TI_NUM 963 + ); 926 964 } 927 965 928 966 static inline void sv_tfb_record_call_target(sv_func_t *func, int bc_off, sv_func_t *callee) {
+3
include/utils.h
··· 41 41 const char **error_detail 42 42 ); 43 43 44 + void ant_ts_hints_store(const char *filename, const char *hints); 45 + const char *ant_ts_hints_find(const char *filename); 46 + 44 47 void *try_oom(size_t size); 45 48 void cstr_free(cstr_buf_t *buf); 46 49
+91
src/silver/compiler.c
··· 9 9 #include "tokens.h" 10 10 #include "runtime.h" 11 11 #include "ops/coercion.h" 12 + #include "utils.h" 12 13 13 14 #include <stdlib.h> 14 15 #include <string.h> ··· 657 658 case SV_TI_STR: return SV_ITER_HINT_STRING; 658 659 default: return SV_ITER_HINT_GENERIC; 659 660 } 661 + } 662 + 663 + static uint8_t ts_hint_char_to_type(char hint) { 664 + switch (hint) { 665 + case 'N': return SV_TI_NUM; 666 + case 'S': return SV_TI_STR; 667 + case 'B': return SV_TI_BOOL; 668 + case 'A': return SV_TI_ARR; 669 + case 'O': return SV_TI_OBJ; 670 + case 'V': return SV_TI_UNDEF; 671 + case '0': return SV_TI_NULL; 672 + default: return SV_TI_UNKNOWN; 673 + } 674 + } 675 + 676 + static const char *ts_hint_find_field( 677 + const char *line, const char *line_end, 678 + const char *field, size_t field_len, 679 + const char **out_end 680 + ) { 681 + const char *p = line; 682 + while (p < line_end) { 683 + const char *next = memchr(p, '|', (size_t)(line_end - p)); 684 + if (!next) next = line_end; 685 + if ((size_t)(next - p) >= field_len && memcmp(p, field, field_len) == 0) { 686 + const char *value = p + field_len; 687 + if (out_end) *out_end = next; 688 + return value; 689 + } 690 + p = next < line_end ? next + 1 : line_end; 691 + } 692 + return NULL; 693 + } 694 + 695 + static bool lookup_ts_function_hints( 696 + const char *filename, 697 + const char *name, 698 + uint32_t name_len, 699 + int param_count, 700 + sv_type_info_t *out_params, 701 + uint8_t *out_return 702 + ) { 703 + const char *hints = ant_ts_hints_find(filename); 704 + if (!hints || !name || name_len == 0) return false; 705 + 706 + bool found = false; 707 + for (const char *line = hints; *line;) { 708 + const char *line_end = strchr(line, '\n'); 709 + if (!line_end) line_end = line + strlen(line); 710 + 711 + const char *fn_end = NULL; 712 + const char *fn = ts_hint_find_field(line, line_end, "fn:", 3, &fn_end); 713 + if (fn && (uint32_t)(fn_end - fn) == name_len && memcmp(fn, name, name_len) == 0) { 714 + const char *params_end = NULL; 715 + const char *params = ts_hint_find_field(line, line_end, "p:", 2, &params_end); 716 + const char *ret_end = NULL; 717 + const char *ret = ts_hint_find_field(line, line_end, "r:", 2, &ret_end); 718 + 719 + if (out_params && params) { 720 + int n = (int)(params_end - params); 721 + if (n > param_count) n = param_count; 722 + for (int i = 0; i < n; i++) 723 + out_params[i].type = ts_hint_char_to_type(params[i]); 724 + } 725 + if (out_return && ret && ret < ret_end) 726 + *out_return = ts_hint_char_to_type(*ret); 727 + found = true; 728 + break; 729 + } 730 + 731 + line = *line_end == '\n' ? line_end + 1 : line_end; 732 + } 733 + 734 + return found; 660 735 } 661 736 662 737 static int ensure_local_at_depth( ··· 5273 5348 if (comp.slot_types) { 5274 5349 int ncopy = max_locals < comp.slot_type_cap ? max_locals : comp.slot_type_cap; 5275 5350 memcpy(func->local_types, comp.slot_types, (size_t)ncopy * sizeof(sv_type_info_t)); 5351 + } 5352 + } 5353 + if (comp.param_count > 0 && node->str && node->len > 0) { 5354 + sv_type_info_t *param_hints = code_arena_bump((size_t)comp.param_count * sizeof(sv_type_info_t)); 5355 + if (param_hints) { 5356 + memset(param_hints, 0, (size_t)comp.param_count * sizeof(sv_type_info_t)); 5357 + uint8_t return_hint = SV_TI_UNKNOWN; 5358 + if (lookup_ts_function_hints( 5359 + enclosing->filename ? enclosing->filename : enclosing->js->filename, 5360 + node->str, node->len, 5361 + comp.param_count, param_hints, &return_hint 5362 + )) { 5363 + func->param_hints = param_hints; 5364 + func->param_hint_count = comp.param_count; 5365 + func->return_hint = return_hint; 5366 + } 5276 5367 } 5277 5368 } 5278 5369 func->param_count = comp.param_count;
+4
src/silver/engine.c
··· 510 510 memmove(base, args, (size_t)argc * sizeof(ant_value_t)); 511 511 for (int i = argc; i < arg_slots; i++) 512 512 base[i] = js_mkundef(); 513 + #ifdef ANT_JIT 514 + for (int i = 0; i < func->param_count; i++) 515 + sv_tfb_record_param(func, i, base[i]); 516 + #endif 513 517 if (func->max_locals > 0) 514 518 for (int i = 0; i < func->max_locals; i++) 515 519 (*out_lp)[i] = js_mkundef();
+166 -9
src/silver/swarm.c
··· 318 318 MIR_append_insn(ctx, fn, no_bail); 319 319 } 320 320 321 + static void mir_emit_bailout_jump(MIR_context_t ctx, MIR_item_t fn, 322 + MIR_reg_t r_bailout_off, int bc_off, 323 + MIR_reg_t r_bailout_sp, int pre_op_sp, 324 + MIR_label_t bailout_tramp, 325 + MIR_reg_t r_args_buf, 326 + jit_vstack_t *vs, 327 + MIR_reg_t *local_regs, int n_locals, 328 + MIR_reg_t r_lbuf, 329 + MIR_reg_t r_d_slot) { 330 + for (int i = 0; i < pre_op_sp; i++) { 331 + if (vs->slot_type && vs->slot_type[i] == SLOT_NUM) 332 + mir_d_to_i64(ctx, fn, vs->regs[i], vs->d_regs[i], r_d_slot); 333 + MIR_append_insn(ctx, fn, 334 + MIR_new_insn(ctx, MIR_MOV, 335 + MIR_new_mem_op(ctx, MIR_T_I64, 336 + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_args_buf, 0, 1), 337 + MIR_new_reg_op(ctx, vs->regs[i]))); 338 + } 339 + for (int i = 0; i < n_locals; i++) 340 + MIR_append_insn(ctx, fn, 341 + MIR_new_insn(ctx, MIR_MOV, 342 + MIR_new_mem_op(ctx, MIR_T_I64, 343 + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), r_lbuf, 0, 1), 344 + MIR_new_reg_op(ctx, local_regs[i]))); 345 + MIR_append_insn(ctx, fn, 346 + MIR_new_insn(ctx, MIR_MOV, 347 + MIR_new_reg_op(ctx, r_bailout_off), 348 + MIR_new_int_op(ctx, bc_off))); 349 + MIR_append_insn(ctx, fn, 350 + MIR_new_insn(ctx, MIR_MOV, 351 + MIR_new_reg_op(ctx, r_bailout_sp), 352 + MIR_new_int_op(ctx, pre_op_sp))); 353 + MIR_append_insn(ctx, fn, 354 + MIR_new_insn(ctx, MIR_JMP, 355 + MIR_new_label_op(ctx, bailout_tramp))); 356 + } 357 + 321 358 322 359 static void mir_load_imm(MIR_context_t ctx, MIR_item_t fn, 323 360 MIR_reg_t dst, uint64_t imm) { ··· 1018 1055 return captured; 1019 1056 } 1020 1057 1058 + static bool *scan_mutated_params(sv_func_t *func) { 1059 + int param_count = func ? func->param_count : 0; 1060 + if (param_count <= 0) return NULL; 1061 + bool *mutated = calloc((size_t)param_count, sizeof(bool)); 1062 + if (!mutated) return NULL; 1063 + 1064 + uint8_t *ip = func->code; 1065 + uint8_t *end = func->code + func->code_len; 1066 + while (ip < end) { 1067 + sv_op_t op = (sv_op_t)*ip; 1068 + int sz = sv_op_size[op]; 1069 + if (sz == 0) break; 1070 + 1071 + if (op == OP_PUT_ARG || op == OP_SET_ARG) { 1072 + uint16_t idx = sv_get_u16(ip + 1); 1073 + if (idx < (uint16_t)param_count) mutated[idx] = true; 1074 + } 1075 + 1076 + ip += sz; 1077 + } 1078 + 1079 + return mutated; 1080 + } 1081 + 1021 1082 1022 1083 #define JIT_INLINE_MAX_BYTECODE 128 1023 1084 ··· 1896 1957 1897 1958 static jit_features_t jit_prescan_features(sv_func_t *func) { 1898 1959 jit_features_t f = {0}; 1960 + if (func->param_count > 0) { 1961 + for (int i = 0; i < func->param_count; i++) { 1962 + if (sv_tfb_param_numeric_hint(func, i)) { 1963 + f.needs_bailout = true; 1964 + break; 1965 + } 1966 + } 1967 + } 1899 1968 uint8_t *ip = func->code; 1900 1969 uint8_t *end = func->code + func->code_len; 1901 1970 while (ip < end) { ··· 2653 2722 int param_count = func->param_count; 2654 2723 bool *captured_params = scan_captured_params(func); 2655 2724 bool *captured_locals = scan_captured_locals(func, n_locals); 2725 + bool *mutated_params = scan_mutated_params(func); 2726 + bool *entry_num_params = NULL; 2727 + MIR_reg_t *param_d_regs = NULL; 2656 2728 bool has_captured_params = false; 2657 2729 bool has_captures = false; 2658 2730 if (captured_params) { ··· 2667 2739 bool use_unified_slotbuf = has_captured_slots && has_captures; 2668 2740 int slotbuf_count = use_unified_slotbuf ? (param_count + n_locals) : param_count; 2669 2741 2742 + if (param_count > 0) { 2743 + entry_num_params = calloc((size_t)param_count, sizeof(bool)); 2744 + param_d_regs = calloc((size_t)param_count, sizeof(MIR_reg_t)); 2745 + if (!entry_num_params || !param_d_regs) { 2746 + free(vs.regs); free(vs.known_func); free(vs.d_regs); free(vs.slot_type); 2747 + free(vs.known_const); free(vs.has_const); 2748 + free(local_regs); free(local_d_regs); free(known_func_locals); free(known_type_locals); 2749 + free(captured_params); free(captured_locals); free(mutated_params); 2750 + free(entry_num_params); free(param_d_regs); 2751 + MIR_finish_func(ctx); MIR_finish_module(ctx); func->jit_compiling = false; return NULL; 2752 + } 2753 + 2754 + for (int i = 0; i < param_count; i++) { 2755 + if (!func->param_hints || i >= func->param_hint_count || 2756 + func->param_hints[i].type != SV_TI_NUM) continue; 2757 + if (!sv_tfb_param_numeric_hint(func, i)) continue; 2758 + if (captured_params && captured_params[i]) continue; 2759 + if (mutated_params && mutated_params[i]) continue; 2760 + 2761 + char dname[32]; 2762 + snprintf(dname, sizeof(dname), "pd%d", i); 2763 + entry_num_params[i] = true; 2764 + param_d_regs[i] = MIR_new_func_reg(ctx, jit_func->u.func, MIR_T_D, dname); 2765 + 2766 + MIR_label_t in_range = MIR_new_label(ctx); 2767 + MIR_label_t is_num = MIR_new_label(ctx); 2768 + MIR_append_insn(ctx, jit_func, 2769 + MIR_new_insn(ctx, MIR_UBGT, 2770 + MIR_new_label_op(ctx, in_range), 2771 + MIR_new_reg_op(ctx, r_argc), 2772 + MIR_new_int_op(ctx, (int64_t)i))); 2773 + mir_load_imm(ctx, jit_func, r_bailout_val, (uint64_t)SV_JIT_BAILOUT); 2774 + MIR_append_insn(ctx, jit_func, 2775 + MIR_new_ret_insn(ctx, 1, MIR_new_reg_op(ctx, r_bailout_val))); 2776 + MIR_append_insn(ctx, jit_func, in_range); 2777 + MIR_append_insn(ctx, jit_func, 2778 + MIR_new_insn(ctx, MIR_MOV, 2779 + MIR_new_reg_op(ctx, r_tmp), 2780 + MIR_new_mem_op(ctx, MIR_JSVAL, 2781 + (MIR_disp_t)(i * (int)sizeof(ant_value_t)), 2782 + r_args, 0, 1))); 2783 + mir_emit_is_num_guard(ctx, jit_func, r_bool, r_tmp, is_num); 2784 + mir_i64_to_d(ctx, jit_func, param_d_regs[i], r_tmp, r_d_slot); 2785 + MIR_label_t guard_done = MIR_new_label(ctx); 2786 + MIR_append_insn(ctx, jit_func, 2787 + MIR_new_insn(ctx, MIR_JMP, MIR_new_label_op(ctx, guard_done))); 2788 + MIR_append_insn(ctx, jit_func, is_num); 2789 + mir_load_imm(ctx, jit_func, r_bailout_val, (uint64_t)SV_JIT_BAILOUT); 2790 + MIR_append_insn(ctx, jit_func, 2791 + MIR_new_ret_insn(ctx, 1, MIR_new_reg_op(ctx, r_bailout_val))); 2792 + MIR_append_insn(ctx, jit_func, guard_done); 2793 + } 2794 + } 2795 + 2670 2796 if (has_captured_params && needs_bailout) { 2671 2797 free(vs.regs); free(vs.known_func); free(vs.d_regs); free(vs.slot_type); 2672 2798 free(vs.known_const); free(vs.has_const); 2673 2799 free(local_regs); free(local_d_regs); free(known_func_locals); free(known_type_locals); 2674 - free(captured_params); free(captured_locals); 2800 + free(captured_params); free(captured_locals); free(mutated_params); 2801 + free(entry_num_params); free(param_d_regs); 2675 2802 MIR_finish_func(ctx); MIR_finish_module(ctx); func->jit_compiling = false; return NULL; 2676 2803 } 2677 2804 ··· 2921 3048 case OP_GET_ARG: { 2922 3049 uint16_t idx = sv_get_u16(ip + 1); 2923 3050 MIR_reg_t dst = vstack_push(&vs); 3051 + if (entry_num_params && idx < (uint16_t)param_count && entry_num_params[idx]) { 3052 + MIR_append_insn(ctx, jit_func, 3053 + MIR_new_insn(ctx, MIR_DMOV, 3054 + MIR_new_reg_op(ctx, vs.d_regs[vs.sp - 1]), 3055 + MIR_new_reg_op(ctx, param_d_regs[idx]))); 3056 + if (vs.slot_type) vs.slot_type[vs.sp - 1] = SLOT_NUM; 3057 + break; 3058 + } 2924 3059 if (has_captured_params && captured_params && idx < (uint16_t)param_count && captured_params[idx]) { 2925 3060 MIR_append_insn(ctx, jit_func, 2926 3061 MIR_new_insn(ctx, MIR_MOV, ··· 2948 3083 r_args, 0, 1))); 2949 3084 MIR_append_insn(ctx, jit_func, arg_done); 2950 3085 } 3086 + if (sv_tfb_param_numeric_hint(func, (int)idx)) { 3087 + MIR_label_t hint_bail = MIR_new_label(ctx); 3088 + MIR_label_t hint_done = MIR_new_label(ctx); 3089 + mir_emit_is_num_guard(ctx, jit_func, r_bool, dst, hint_bail); 3090 + mir_i64_to_d(ctx, jit_func, vs.d_regs[vs.sp - 1], dst, r_d_slot); 3091 + if (vs.slot_type) vs.slot_type[vs.sp - 1] = SLOT_NUM; 3092 + MIR_append_insn(ctx, jit_func, 3093 + MIR_new_insn(ctx, MIR_JMP, MIR_new_label_op(ctx, hint_done))); 3094 + MIR_append_insn(ctx, jit_func, hint_bail); 3095 + mir_emit_bailout_jump(ctx, jit_func, 3096 + r_bailout_off, bc_off, 3097 + r_bailout_sp, vs.sp - 1, bailout_tramp, 3098 + r_args_buf, &vs, local_regs, n_locals, r_lbuf, r_d_slot); 3099 + MIR_append_insn(ctx, jit_func, hint_done); 3100 + } 2951 3101 break; 2952 3102 } 2953 3103 ··· 3356 3506 case OP_ADD_NUM: { 3357 3507 uint8_t fb = func->type_feedback ? func->type_feedback[bc_off] : 0; 3358 3508 bool force_num_only = (op == OP_ADD_NUM); 3359 - bool fb_num_only = force_num_only || (fb && !(fb & ~SV_TFB_NUM)); 3360 - bool fb_never_num = !force_num_only && fb && !(fb & SV_TFB_NUM); 3361 3509 3362 3510 bool l_is_num = vs.slot_type && vs.slot_type[vs.sp - 2] == SLOT_NUM; 3363 3511 bool r_is_num = vs.slot_type && vs.slot_type[vs.sp - 1] == SLOT_NUM; 3512 + bool hinted_num_only = l_is_num && r_is_num; 3513 + bool fb_num_only = force_num_only || hinted_num_only || (fb && !(fb & ~SV_TFB_NUM)); 3514 + bool fb_never_num = !force_num_only && !hinted_num_only && fb && !(fb & SV_TFB_NUM); 3364 3515 3365 3516 MIR_reg_t rr = vstack_pop(&vs); 3366 3517 MIR_reg_t rl = vstack_pop(&vs); ··· 3546 3697 case OP_SUB_NUM: { 3547 3698 uint8_t fb = func->type_feedback ? func->type_feedback[bc_off] : 0; 3548 3699 bool force_num_only = (op == OP_SUB_NUM); 3549 - bool fb_num_only = force_num_only || (fb && !(fb & ~SV_TFB_NUM)); 3550 - bool fb_never_num = !force_num_only && fb && !(fb & SV_TFB_NUM); 3551 3700 3552 3701 bool l_is_num = vs.slot_type && vs.slot_type[vs.sp - 2] == SLOT_NUM; 3553 3702 bool r_is_num = vs.slot_type && vs.slot_type[vs.sp - 1] == SLOT_NUM; 3703 + bool hinted_num_only = l_is_num && r_is_num; 3704 + bool fb_num_only = force_num_only || hinted_num_only || (fb && !(fb & ~SV_TFB_NUM)); 3705 + bool fb_never_num = !force_num_only && !hinted_num_only && fb && !(fb & SV_TFB_NUM); 3554 3706 3555 3707 MIR_reg_t rr = vstack_pop(&vs); 3556 3708 MIR_reg_t rl = vstack_pop(&vs); ··· 3719 3871 case OP_MUL_NUM: { 3720 3872 uint8_t fb = func->type_feedback ? func->type_feedback[bc_off] : 0; 3721 3873 bool force_num_only = (op == OP_MUL_NUM); 3722 - bool fb_num_only = force_num_only || (fb && !(fb & ~SV_TFB_NUM)); 3723 - bool fb_never_num = !force_num_only && fb && !(fb & SV_TFB_NUM); 3724 3874 3725 3875 bool l_is_num = vs.slot_type && vs.slot_type[vs.sp - 2] == SLOT_NUM; 3726 3876 bool r_is_num = vs.slot_type && vs.slot_type[vs.sp - 1] == SLOT_NUM; 3877 + bool hinted_num_only = l_is_num && r_is_num; 3878 + bool fb_num_only = force_num_only || hinted_num_only || (fb && !(fb & ~SV_TFB_NUM)); 3879 + bool fb_never_num = !force_num_only && !hinted_num_only && fb && !(fb & SV_TFB_NUM); 3727 3880 3728 3881 MIR_reg_t rr = vstack_pop(&vs); 3729 3882 MIR_reg_t rl = vstack_pop(&vs); ··· 3892 4045 case OP_DIV_NUM: { 3893 4046 uint8_t fb = func->type_feedback ? func->type_feedback[bc_off] : 0; 3894 4047 bool force_num_only = (op == OP_DIV_NUM); 3895 - bool fb_num_only = force_num_only || (fb && !(fb & ~SV_TFB_NUM)); 3896 - bool fb_never_num = !force_num_only && fb && !(fb & SV_TFB_NUM); 3897 4048 3898 4049 bool l_is_num = vs.slot_type && vs.slot_type[vs.sp - 2] == SLOT_NUM; 3899 4050 bool r_is_num = vs.slot_type && vs.slot_type[vs.sp - 1] == SLOT_NUM; 4051 + bool hinted_num_only = l_is_num && r_is_num; 4052 + bool fb_num_only = force_num_only || hinted_num_only || (fb && !(fb & ~SV_TFB_NUM)); 4053 + bool fb_never_num = !force_num_only && !hinted_num_only && fb && !(fb & SV_TFB_NUM); 3900 4054 3901 4055 MIR_reg_t rr = vstack_pop(&vs); 3902 4056 MIR_reg_t rl = vstack_pop(&vs); ··· 8213 8367 free(known_type_locals); 8214 8368 free(captured_params); 8215 8369 free(captured_locals); 8370 + free(mutated_params); 8371 + free(entry_num_params); 8372 + free(param_d_regs); 8216 8373 8217 8374 if (!ok) return NULL; 8218 8375
+1
src/strip/Cargo.lock
··· 304 304 version = "0.0.0" 305 305 dependencies = [ 306 306 "oxc_allocator", 307 + "oxc_ast", 307 308 "oxc_codegen", 308 309 "oxc_parser", 309 310 "oxc_semantic",
+1
src/strip/Cargo.toml
··· 7 7 8 8 [dependencies] 9 9 oxc_allocator = "0.110.0" 10 + oxc_ast = "0.110.0" 10 11 oxc_span = "0.110.0" 11 12 oxc_transformer = "0.110.0" 12 13 oxc_semantic = "0.110.0"
+99 -1
src/strip/src/ffi.rs
··· 1 1 use std::ffi::{CStr, c_char, c_int}; 2 2 use std::ptr; 3 3 4 - use crate::strip::strip_types_internal; 4 + use crate::strip::{strip_types_internal, strip_types_with_hints_internal}; 5 5 6 6 pub const OXC_ERR_NULL_INPUT: c_int = -1; 7 7 pub const OXC_ERR_INVALID_UTF8: c_int = -2; ··· 32 32 ptr::copy_nonoverlapping(bytes.as_ptr(), output as *mut u8, copy_len); 33 33 *output.add(copy_len) = 0; 34 34 } 35 + } 36 + 37 + unsafe fn alloc_c_string(bytes: &[u8]) -> *mut c_char { 38 + let alloc_len = bytes.len() + 1; 39 + let out_ptr = unsafe { malloc(alloc_len) as *mut c_char }; 40 + if out_ptr.is_null() { 41 + return ptr::null_mut(); 42 + } 43 + 44 + unsafe { 45 + ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, bytes.len()); 46 + *out_ptr.add(bytes.len()) = 0; 47 + } 48 + out_ptr 35 49 } 36 50 37 51 #[unsafe(no_mangle)] ··· 105 119 } 106 120 } 107 121 } 122 + 123 + #[unsafe(no_mangle)] 124 + pub unsafe extern "C" fn OXC_strip_types_with_hints_owned( 125 + input: *const c_char, filename: *const c_char, is_module: c_int, out_len: *mut usize, out_error: *mut c_int, out_hints: *mut *mut c_char, out_hints_len: *mut usize, error_output: *mut c_char, error_output_len: usize, 126 + ) -> *mut c_char { 127 + if !out_error.is_null() { 128 + unsafe { *out_error = OXC_ERR_NULL_INPUT }; 129 + } 130 + 131 + if !out_len.is_null() { 132 + unsafe { *out_len = 0 }; 133 + } 134 + if !out_hints.is_null() { 135 + unsafe { *out_hints = ptr::null_mut() }; 136 + } 137 + if !out_hints_len.is_null() { 138 + unsafe { *out_hints_len = 0 }; 139 + } 140 + 141 + if input.is_null() || filename.is_null() || out_len.is_null() { 142 + unsafe { write_error(error_output, error_output_len, "null input/output passed") }; 143 + return ptr::null_mut(); 144 + } 145 + 146 + let filename_str = match unsafe { CStr::from_ptr(filename).to_str() } { 147 + Ok(s) => s, 148 + Err(_) => { 149 + unsafe { write_error(error_output, error_output_len, "filename is not valid UTF-8") }; 150 + if !out_error.is_null() { 151 + unsafe { *out_error = OXC_ERR_INVALID_UTF8 }; 152 + } 153 + return ptr::null_mut(); 154 + } 155 + }; 156 + 157 + let input_str = match unsafe { CStr::from_ptr(input).to_str() } { 158 + Ok(s) => s, 159 + Err(_) => { 160 + unsafe { write_error(error_output, error_output_len, "source input is not valid UTF-8") }; 161 + if !out_error.is_null() { 162 + unsafe { *out_error = OXC_ERR_INVALID_UTF8 }; 163 + } 164 + return ptr::null_mut(); 165 + } 166 + }; 167 + 168 + match strip_types_with_hints_internal(input_str, filename_str, is_module != 0) { 169 + Ok(result) => { 170 + let code_bytes = result.code.as_bytes(); 171 + let out_ptr = unsafe { alloc_c_string(code_bytes) }; 172 + if out_ptr.is_null() { 173 + unsafe { write_error(error_output, error_output_len, "out of memory allocating strip output") }; 174 + if !out_error.is_null() { 175 + unsafe { *out_error = OXC_ERR_OUTPUT_TOO_LARGE }; 176 + } 177 + return ptr::null_mut(); 178 + } 179 + 180 + unsafe { *out_len = code_bytes.len() }; 181 + if !result.hints.is_empty() && !out_hints.is_null() && !out_hints_len.is_null() { 182 + let hint_bytes = result.hints.as_bytes(); 183 + let hints_ptr = unsafe { alloc_c_string(hint_bytes) }; 184 + if !hints_ptr.is_null() { 185 + unsafe { 186 + *out_hints = hints_ptr; 187 + *out_hints_len = hint_bytes.len(); 188 + } 189 + } 190 + } 191 + 192 + if !out_error.is_null() { 193 + unsafe { *out_error = 0 }; 194 + } 195 + out_ptr 196 + } 197 + Err(err) => { 198 + unsafe { write_error(error_output, error_output_len, &err) }; 199 + if !out_error.is_null() { 200 + unsafe { *out_error = classify_strip_error(&err) }; 201 + } 202 + ptr::null_mut() 203 + } 204 + } 205 + }
+1 -1
src/strip/src/lib.rs
··· 2 2 mod strip; 3 3 4 4 pub use ffi::*; 5 - pub use strip::strip_types_internal; 5 + pub use strip::{strip_types_internal, strip_types_with_hints_internal};
+116 -1
src/strip/src/strip.rs
··· 1 1 use std::path::Path; 2 2 3 3 use oxc_allocator::Allocator; 4 + use oxc_ast::ast::{ 5 + BindingPattern, Declaration, ExportDefaultDeclarationKind, FormalParameter, Function, Program, 6 + Statement, TSType, TSTypeAnnotation, 7 + }; 4 8 use oxc_codegen::Codegen; 5 9 use oxc_parser::Parser; 6 10 use oxc_semantic::SemanticBuilder; 7 11 use oxc_span::SourceType; 8 12 use oxc_transformer::{TransformOptions, Transformer, TypeScriptOptions}; 9 13 14 + pub struct StripTypesResult { 15 + pub code: String, 16 + pub hints: String, 17 + } 18 + 19 + fn type_hint_from_type(ts_type: &TSType<'_>) -> char { 20 + match ts_type { 21 + TSType::TSNumberKeyword(_) => 'N', 22 + TSType::TSStringKeyword(_) => 'S', 23 + TSType::TSBooleanKeyword(_) => 'B', 24 + TSType::TSArrayType(_) | TSType::TSTupleType(_) => 'A', 25 + TSType::TSObjectKeyword(_) | TSType::TSTypeLiteral(_) => 'O', 26 + TSType::TSUndefinedKeyword(_) | TSType::TSVoidKeyword(_) => 'V', 27 + TSType::TSNullKeyword(_) => '0', 28 + TSType::TSParenthesizedType(parenthesized) => type_hint_from_type(&parenthesized.type_annotation), 29 + _ => 'U', 30 + } 31 + } 32 + 33 + fn type_hint_from_annotation(annotation: Option<&TSTypeAnnotation<'_>>) -> char { 34 + annotation.map_or('U', |annotation| type_hint_from_type(&annotation.type_annotation)) 35 + } 36 + 37 + fn binding_name<'a>(pattern: &'a BindingPattern<'a>) -> Option<&'a str> { 38 + match pattern { 39 + BindingPattern::BindingIdentifier(ident) => Some(ident.name.as_str()), 40 + BindingPattern::AssignmentPattern(assign) => binding_name(&assign.left), 41 + _ => None, 42 + } 43 + } 44 + 45 + fn param_hint(param: &FormalParameter<'_>) -> char { 46 + if binding_name(&param.pattern).is_none() { 47 + return 'U'; 48 + } 49 + type_hint_from_annotation(param.type_annotation.as_deref()) 50 + } 51 + 52 + fn collect_function_hint(func: &Function<'_>, out: &mut String) { 53 + let Some(id) = &func.id else { 54 + return; 55 + }; 56 + if func.body.is_none() { 57 + return; 58 + } 59 + 60 + let mut params = String::new(); 61 + let mut has_hint = false; 62 + for param in &func.params.items { 63 + let hint = param_hint(param); 64 + if hint != 'U' { 65 + has_hint = true; 66 + } 67 + params.push(hint); 68 + } 69 + 70 + if let Some(rest) = &func.params.rest { 71 + let hint = type_hint_from_annotation(rest.type_annotation.as_deref()); 72 + if hint != 'U' { 73 + has_hint = true; 74 + } 75 + params.push(hint); 76 + } 77 + 78 + let ret = type_hint_from_annotation(func.return_type.as_deref()); 79 + if ret != 'U' { 80 + has_hint = true; 81 + } 82 + if !has_hint { 83 + return; 84 + } 85 + 86 + out.push_str("fn:"); 87 + out.push_str(id.name.as_str()); 88 + out.push_str("|p:"); 89 + out.push_str(&params); 90 + out.push_str("|r:"); 91 + out.push(ret); 92 + out.push('\n'); 93 + } 94 + 95 + fn collect_statement_hints(stmt: &Statement<'_>, out: &mut String) { 96 + match stmt { 97 + Statement::FunctionDeclaration(func) => collect_function_hint(func, out), 98 + Statement::ExportNamedDeclaration(export) => { 99 + if let Some(Declaration::FunctionDeclaration(func)) = &export.declaration { 100 + collect_function_hint(func, out); 101 + } 102 + } 103 + Statement::ExportDefaultDeclaration(export) => { 104 + if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &export.declaration { 105 + collect_function_hint(func, out); 106 + } 107 + } 108 + _ => {} 109 + } 110 + } 111 + 112 + fn collect_type_hints(program: &Program<'_>) -> String { 113 + let mut hints = String::new(); 114 + for stmt in &program.body { 115 + collect_statement_hints(stmt, &mut hints); 116 + } 117 + hints 118 + } 119 + 10 120 pub fn strip_types_internal(source: &str, filename: &str, is_module: bool) -> Result<String, String> { 121 + strip_types_with_hints_internal(source, filename, is_module).map(|result| result.code) 122 + } 123 + 124 + pub fn strip_types_with_hints_internal(source: &str, filename: &str, is_module: bool) -> Result<StripTypesResult, String> { 11 125 let allocator = Allocator::default(); 12 126 let source_type = SourceType::from_path(filename).unwrap_or_else(|_| SourceType::ts()).with_module(is_module); 13 127 let parser_ret = Parser::new(&allocator, source, source_type).parse(); ··· 18 132 } 19 133 20 134 let mut program = parser_ret.program; 135 + let hints = collect_type_hints(&program); 21 136 let semantic_ret = SemanticBuilder::new().build(&program); 22 137 23 138 if !semantic_ret.errors.is_empty() { ··· 44 159 } 45 160 46 161 let output = Codegen::new().build(&program).code; 47 - Ok(output) 162 + Ok(StripTypesResult { code: output, hints }) 48 163 }
+45 -1
src/utils.c
··· 21 21 ".json", ".node", NULL 22 22 }; 23 23 24 + typedef struct ant_ts_hints_entry { 25 + char *filename; 26 + char *hints; 27 + struct ant_ts_hints_entry *next; 28 + } ant_ts_hints_entry_t; 29 + 30 + static ant_ts_hints_entry_t *ant_ts_hints_head = NULL; 31 + 32 + void ant_ts_hints_store(const char *filename, const char *hints) { 33 + if (!filename || !filename[0]) return; 34 + 35 + for (ant_ts_hints_entry_t *entry = ant_ts_hints_head; entry; entry = entry->next) { 36 + if (strcmp(entry->filename, filename) != 0) continue; 37 + free(entry->hints); 38 + entry->hints = (hints && hints[0]) ? strdup(hints) : NULL; 39 + return; 40 + } 41 + 42 + ant_ts_hints_entry_t *entry = calloc(1, sizeof(*entry)); 43 + if (!entry) return; 44 + entry->filename = strdup(filename); 45 + entry->hints = (hints && hints[0]) ? strdup(hints) : NULL; 46 + if (!entry->filename) { 47 + free(entry->hints); 48 + free(entry); 49 + return; 50 + } 51 + entry->next = ant_ts_hints_head; 52 + ant_ts_hints_head = entry; 53 + } 54 + 55 + const char *ant_ts_hints_find(const char *filename) { 56 + if (!filename || !filename[0]) return NULL; 57 + for (ant_ts_hints_entry_t *entry = ant_ts_hints_head; entry; entry = entry->next) { 58 + if (strcmp(entry->filename, filename) == 0) return entry->hints; 59 + } 60 + return NULL; 61 + } 62 + 24 63 static const char *ant_home_dir(void) { 25 64 #ifdef _WIN32 26 65 const char *home = getenv("USERPROFILE"); ··· 283 322 char *input = *buffer; 284 323 char error_buf[256] = {0}; 285 324 size_t stripped_len = 0; 325 + char *hints = NULL; 326 + size_t hints_len = 0; 286 327 287 328 int strip_error = OXC_ERR_TRANSFORM_FAILED; 288 - char *stripped = OXC_strip_types_owned( 329 + char *stripped = OXC_strip_types_with_hints_owned( 289 330 input, filename, is_module, 290 331 &stripped_len, &strip_error, 332 + &hints, &hints_len, 291 333 error_buf, sizeof(error_buf) 292 334 ); 293 335 ··· 315 357 316 358 memcpy(next, stripped, stripped_len + 1); 317 359 free(stripped); 360 + ant_ts_hints_store(filename, (hints && hints_len > 0) ? hints : NULL); 361 + free(hints); 318 362 319 363 *buffer = next; 320 364 if (out_len) *out_len = stripped_len;
+44
tests/bench_typescript_type_hints_compare.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const path = require('path'); 3 + 4 + function run(file, rounds) { 5 + const result = spawnSync(process.execPath, [file, String(rounds)], { 6 + encoding: 'utf8', 7 + }); 8 + if (result.error) throw result.error; 9 + if (result.status !== 0) { 10 + throw new Error( 11 + `${path.basename(file)} exited ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}` 12 + ); 13 + } 14 + 15 + const out = Object.create(null); 16 + for (const line of result.stdout.trim().split(/\n/)) { 17 + const eq = line.indexOf('='); 18 + if (eq > 0) out[line.slice(0, eq)] = line.slice(eq + 1); 19 + } 20 + return { 21 + ms: Number(out.best_ms), 22 + checksum: out.checksum, 23 + stdout: result.stdout, 24 + }; 25 + } 26 + 27 + const rounds = Number(process.argv[2]) > 0 ? Number(process.argv[2]) | 0 : 5000000; 28 + const dir = __dirname; 29 + const ts = run(path.join(dir, 'bench_typescript_type_hints_compare.ts'), rounds); 30 + const js = run(path.join(dir, 'bench_typescript_type_hints_compare.js'), rounds); 31 + 32 + if (ts.checksum !== js.checksum) { 33 + throw new Error(`checksum mismatch: ts=${ts.checksum} js=${js.checksum}`); 34 + } 35 + 36 + const speedup = js.ms / ts.ms; 37 + const delta = js.ms - ts.ms; 38 + 39 + console.log(`rounds=${rounds}`); 40 + console.log(`ts_best_ms=${ts.ms.toFixed(3)}`); 41 + console.log(`js_best_ms=${js.ms.toFixed(3)}`); 42 + console.log(`speedup=${speedup.toFixed(2)}x`); 43 + console.log(`delta_ms=${delta.toFixed(3)}`); 44 + console.log(`checksum=${ts.checksum}`);
+44
tests/bench_typescript_type_hints_compare.js
··· 1 + const now = () => (typeof performance !== "undefined" && performance.now ? performance.now() : Date.now()); 2 + 3 + function readRounds() { 4 + const raw = Number(process.argv[2]); 5 + return Number.isFinite(raw) && raw > 0 ? raw | 0 : 5000000; 6 + } 7 + 8 + function hot( 9 + a, b, c, d, 10 + e, f, g, h 11 + ) { 12 + return ( 13 + a + b + c + d + e + f + g + h + 14 + a + c + e + g + b + d + f + h + 15 + a + d + g + b + e + h + c + f + 16 + h + g + f + e + d + c + b + a 17 + ); 18 + } 19 + 20 + function run(rounds) { 21 + let total = 0; 22 + for (let i = 0; i < rounds; i++) { 23 + const n = i & 1023; 24 + total += hot(n, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6, n + 7); 25 + } 26 + return total; 27 + } 28 + 29 + const rounds = readRounds(); 30 + run(2000); 31 + 32 + let best = Infinity; 33 + let checksum = 0; 34 + for (let i = 0; i < 5; i++) { 35 + const t0 = now(); 36 + checksum = run(rounds); 37 + const elapsed = now() - t0; 38 + if (elapsed < best) best = elapsed; 39 + } 40 + 41 + console.log("kind=js"); 42 + console.log("rounds=" + rounds); 43 + console.log("best_ms=" + best.toFixed(3)); 44 + console.log("checksum=" + checksum);
+44
tests/bench_typescript_type_hints_compare.ts
··· 1 + const now = () => (typeof performance !== "undefined" && performance.now ? performance.now() : Date.now()); 2 + 3 + function readRounds(): number { 4 + const raw = Number(process.argv[2]); 5 + return Number.isFinite(raw) && raw > 0 ? raw | 0 : 5000000; 6 + } 7 + 8 + function hot( 9 + a: number, b: number, c: number, d: number, 10 + e: number, f: number, g: number, h: number 11 + ): number { 12 + return ( 13 + a + b + c + d + e + f + g + h + 14 + a + c + e + g + b + d + f + h + 15 + a + d + g + b + e + h + c + f + 16 + h + g + f + e + d + c + b + a 17 + ); 18 + } 19 + 20 + function run(rounds: number): number { 21 + let total = 0; 22 + for (let i = 0; i < rounds; i++) { 23 + const n = i & 1023; 24 + total += hot(n, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6, n + 7); 25 + } 26 + return total; 27 + } 28 + 29 + const rounds = readRounds(); 30 + run(2000); 31 + 32 + let best = Infinity; 33 + let checksum = 0; 34 + for (let i = 0; i < 5; i++) { 35 + const t0 = now(); 36 + checksum = run(rounds); 37 + const elapsed = now() - t0; 38 + if (elapsed < best) best = elapsed; 39 + } 40 + 41 + console.log("kind=ts"); 42 + console.log("rounds=" + rounds); 43 + console.log("best_ms=" + best.toFixed(3)); 44 + console.log("checksum=" + checksum);
+27
tests/fixtures/type_hints_compare.js
··· 1 + function addStep(value, delta) { 2 + return value + delta; 3 + } 4 + 5 + function mix(a, b, c) { 6 + return (a + b) * c - a / (b + 1); 7 + } 8 + 9 + function readRounds() { 10 + const raw = Number(process.argv[2]); 11 + return Number.isFinite(raw) && raw > 0 ? raw | 0 : 10000; 12 + } 13 + 14 + const rounds = readRounds(); 15 + let checksum = 0; 16 + 17 + for (let i = 0; i < 240; i++) { 18 + checksum = addStep(checksum, mix(i, i + 1, 3)); 19 + } 20 + 21 + for (let i = 0; i < rounds; i++) { 22 + checksum = addStep(checksum, (i % 13) + (i % 7)); 23 + } 24 + 25 + console.log("rounds=" + rounds); 26 + console.log("checksum=" + checksum.toFixed(3)); 27 + console.log("fallback=" + addStep("type", "script"));
+27
tests/fixtures/type_hints_compare.ts
··· 1 + function addStep(value: number, delta: number): number { 2 + return value + delta; 3 + } 4 + 5 + function mix(a: number, b: number, c: number): number { 6 + return (a + b) * c - a / (b + 1); 7 + } 8 + 9 + function readRounds(): number { 10 + const raw = Number(process.argv[2]); 11 + return Number.isFinite(raw) && raw > 0 ? raw | 0 : 10000; 12 + } 13 + 14 + const rounds = readRounds(); 15 + let checksum = 0; 16 + 17 + for (let i = 0; i < 240; i++) { 18 + checksum = addStep(checksum, mix(i, i + 1, 3)); 19 + } 20 + 21 + for (let i = 0; i < rounds; i++) { 22 + checksum = addStep(checksum, (i % 13) + (i % 7)); 23 + } 24 + 25 + console.log("rounds=" + rounds); 26 + console.log("checksum=" + checksum.toFixed(3)); 27 + console.log("fallback=" + addStep("type" as any, "script" as any));
+40
tests/test_typescript_type_hint_compare.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const path = require('path'); 3 + 4 + function assert(condition, message) { 5 + if (!condition) throw new Error(message); 6 + } 7 + 8 + function runFixture(file) { 9 + const result = spawnSync(process.execPath, [file], { 10 + encoding: 'utf8', 11 + }); 12 + 13 + if (result.error) throw result.error; 14 + 15 + const stdout = result.stdout.replace(/\x1b\[[0-9;]*m/g, ''); 16 + assert( 17 + result.status === 0, 18 + `expected ${path.basename(file)} to exit 0, got ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}` 19 + ); 20 + 21 + return stdout; 22 + } 23 + 24 + const fixtureDir = path.join(__dirname, 'fixtures'); 25 + const tsPath = path.join(fixtureDir, 'type_hints_compare.ts'); 26 + const jsPath = path.join(fixtureDir, 'type_hints_compare.js'); 27 + 28 + const tsOut = runFixture(tsPath); 29 + const jsOut = runFixture(jsPath); 30 + 31 + assert( 32 + tsOut === jsOut, 33 + `expected TypeScript and JavaScript comparison fixtures to match\n.ts:\n${tsOut}\n.js:\n${jsOut}` 34 + ); 35 + assert( 36 + tsOut === 'rounds=10000\nchecksum=262549.128\nfallback=typescript\n', 37 + `unexpected comparison output: ${JSON.stringify(tsOut)}` 38 + ); 39 + 40 + console.log('TypeScript type-hint fixture matches JavaScript fixture');
+55
tests/test_typescript_type_hints.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const fs = require('fs'); 3 + const os = require('os'); 4 + const path = require('path'); 5 + 6 + function assert(condition, message) { 7 + if (!condition) throw new Error(message); 8 + } 9 + 10 + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ant-ts-type-hints-')); 11 + const scriptPath = path.join(tmpRoot, 'entry.ts'); 12 + 13 + fs.writeFileSync( 14 + scriptPath, 15 + [ 16 + 'function add(a: number, b: number): number {', 17 + ' return a + b;', 18 + '}', 19 + '', 20 + 'function observedMismatch(a: number, b: number): number {', 21 + ' return a + b;', 22 + '}', 23 + '', 24 + 'let total = 0;', 25 + 'for (let i = 0; i < 160; i++) total += add(i, 1);', 26 + 'console.log(total);', 27 + 'console.log(add("x" as any, "y" as any));', 28 + 'console.log(observedMismatch("pre" as any, "jit" as any));', 29 + 'console.log(observedMismatch("type" as any, "feedback" as any));', 30 + 'let mismatchTotal = 0;', 31 + 'for (let i = 0; i < 160; i++) mismatchTotal += observedMismatch(i, 2);', 32 + 'console.log(mismatchTotal);', 33 + 'console.log(observedMismatch("post" as any, "warmup" as any));', 34 + '', 35 + ].join('\n') 36 + ); 37 + 38 + const result = spawnSync(process.execPath, [scriptPath], { 39 + encoding: 'utf8', 40 + }); 41 + 42 + if (result.error) throw result.error; 43 + 44 + const stdout = result.stdout.replace(/\x1b\[[0-9;]*m/g, ''); 45 + 46 + assert( 47 + result.status === 0, 48 + `expected TypeScript type-hint optimization to preserve runtime semantics, got ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}` 49 + ); 50 + assert( 51 + stdout === '12880\nxy\nprejit\ntypefeedback\n13040\npostwarmup\n', 52 + `expected numeric warmup and string fallback output, got ${JSON.stringify(stdout)}` 53 + ); 54 + 55 + console.log('TypeScript type hints preserve guarded runtime semantics');