From 03c45ee8b02b7c866caf27836d9d066c90a25146 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 3 Feb 2026 16:59:20 -0600 Subject: [PATCH] lexer --- source/quickjs.c | 284 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 233 insertions(+), 51 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index a0fd77b8..80957335 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -82,7 +82,7 @@ */ // #define DUMP_BYTECODE (1) /* dump GC summary: old/new heap, recovery %, heap growth */ -#define DUMP_GC +// #define DUMP_GC /* dump detailed GC: roots, scanning, object traversal (implies DUMP_GC) */ // #define DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL @@ -97,7 +97,7 @@ // #define DUMP_ROPE_REBALANCE /* test the GC by forcing it before each object allocation */ -//#define FORCE_GC_AT_MALLOC +#define FORCE_GC_AT_MALLOC #define POISON_HEAP /* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */ @@ -856,6 +856,9 @@ struct JSContext { JSInterruptHandler *interrupt_handler; void *interrupt_opaque; + + /* Parser state (for GC to scan cpool during parsing) */ + struct JSFunctionDef *current_parse_fd; }; /* ============================================================ @@ -1864,6 +1867,7 @@ static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, J static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); JSValue JS_ThrowOutOfMemory (JSContext *ctx); static JSVarRef *get_var_ref (JSContext *ctx, JSStackFrame *sf, int var_idx, BOOL is_arg); static JSValue JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); @@ -1958,6 +1962,50 @@ void js_free (JSContext *ctx, void *ptr) { (void)ptr; } +/* Parser memory functions - use system allocator to avoid GC issues */ +static void *pjs_malloc (size_t size) { + return malloc (size); +} + +static void *pjs_mallocz (size_t size) { + void *ptr = malloc (size); + if (ptr) memset (ptr, 0, size); + return ptr; +} + +static void *pjs_realloc (void *ptr, size_t size) { + return realloc (ptr, size); +} + +static void pjs_free (void *ptr) { + free (ptr); +} + +/* Parser-specific resize array using system allocator */ +static no_inline int pjs_realloc_array (void **parray, int elem_size, int *psize, int req_size) { + int new_size; + void *new_array; + size_t old_size; + new_size = max_int (req_size, *psize * 3 / 2); + old_size = (size_t)(*psize) * elem_size; + new_array = pjs_realloc (*parray, (size_t)new_size * elem_size); + if (!new_array) return -1; + /* Zero newly allocated memory */ + if (new_size > *psize) { + memset ((char *)new_array + old_size, 0, (size_t)(new_size - *psize) * elem_size); + } + *psize = new_size; + *parray = new_array; + return 0; +} + +static inline int pjs_resize_array (void **parray, int elem_size, int *psize, int req_size) { + if (unlikely (req_size > *psize)) + return pjs_realloc_array (parray, elem_size, psize, req_size); + else + return 0; +} + static no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { int new_size; void *new_array; @@ -2422,7 +2470,19 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8 JSFunction *fn = (JSFunction *)ptr; /* Scan the function name */ fn->name = gc_copy_value (ctx, fn->name, from_base, from_end, to_base, to_free, to_end); - /* Note: function_bytecode and var_refs are allocated via js_malloc, not bump allocator */ + /* Scan bytecode's cpool - it contains JSValues that may reference GC objects */ + if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) { + JSFunctionBytecode *b = fn->u.func.function_bytecode; + /* Scan cpool entries */ + for (int i = 0; i < b->cpool_count; i++) { + b->cpool[i] = gc_copy_value (ctx, b->cpool[i], from_base, from_end, to_base, to_free, to_end); + } + /* Scan func_name and filename */ + b->func_name = gc_copy_value (ctx, b->func_name, from_base, from_end, to_base, to_free, to_end); + if (b->has_debug) { + b->debug.filename = gc_copy_value (ctx, b->debug.filename, from_base, from_end, to_base, to_free, to_end); + } + } break; } case OBJ_TEXT: @@ -2446,6 +2506,34 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8 } } +/* Forward declaration - defined after JSFunctionDef */ +static void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); + +/* Scan OBJ_CODE cpool for bytecode objects outside GC heap */ +static void gc_scan_bytecode_cpool (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + if (!JS_IsPtr (v)) return; + void *ptr = JS_VALUE_GET_PTR (v); + if (ptr_in_range (ptr, from_base, from_end)) return; /* On GC heap, handled normally */ + if (is_stone_ptr (ctx, ptr)) return; /* Stone memory */ + + /* Check if this is an OBJ_CODE outside GC heap */ + objhdr_t hdr = *(objhdr_t *)ptr; + if (objhdr_type (hdr) == OBJ_CODE) { + JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr; + /* Scan cpool entries */ + for (int i = 0; i < bc->cpool_count; i++) { + bc->cpool[i] = gc_copy_value (ctx, bc->cpool[i], from_base, from_end, to_base, to_free, to_end); + } + /* Scan func_name and filename */ + bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end); + if (bc->has_debug) { + bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end); + } + } +} + /* Cheney copying GC - collect garbage and compact live objects allow_grow: if true, grow heap when <20% recovered; if false, keep same size */ static int ctx_gc (JSContext *ctx, int allow_grow) { @@ -2509,6 +2597,12 @@ static int ctx_gc (JSContext *ctx, int allow_grow) { #endif ctx->eval_obj = gc_copy_value (ctx, ctx->eval_obj, from_base, from_end, to_base, &to_free, to_end); + /* Copy current exception if pending */ +#ifdef DUMP_GC_DETAIL + printf(" roots: current_exception\n"); fflush(stdout); +#endif + rt->current_exception = gc_copy_value (ctx, rt->current_exception, from_base, from_end, to_base, &to_free, to_end); + #ifdef DUMP_GC_DETAIL printf(" roots: native_error_proto\n"); fflush(stdout); #endif @@ -2542,11 +2636,24 @@ static int ctx_gc (JSContext *ctx, int allow_grow) { frame->this_obj = gc_copy_value (ctx, frame->this_obj, from_base, from_end, to_base, &to_free, to_end); } + /* Copy JSStackFrame chain (C stack frames) */ +#ifdef DUMP_GC_DETAIL + printf(" roots: current_stack_frame chain\n"); fflush(stdout); +#endif + for (JSStackFrame *sf = rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + sf->cur_func = gc_copy_value (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); + /* Also scan bytecode cpool if it's a bytecode object outside GC heap */ + gc_scan_bytecode_cpool (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); + /* Scan arg_buf and var_buf - they point into value_stack which is scanned separately, + but we should update cur_func which may be a function/bytecode */ + } + /* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */ #ifdef DUMP_GC_DETAIL printf(" roots: top_gc_ref\n"); fflush(stdout); #endif for (JSGCRef *ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); } @@ -2555,9 +2662,15 @@ static int ctx_gc (JSContext *ctx, int allow_grow) { printf(" roots: last_gc_ref\n"); fflush(stdout); #endif for (JSGCRef *ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { + gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); } + /* Scan parser's cpool (if parsing is in progress) */ + if (ctx->current_parse_fd) { + gc_scan_parser_cpool (ctx, from_base, from_end, to_base, &to_free, to_end); + } + /* Cheney scan: scan copied objects to find more references */ uint8_t *scan = to_base; #ifdef DUMP_GC_DETAIL @@ -4095,26 +4208,35 @@ static void build_backtrace (JSContext *ctx, JSValue error_obj, const char *file const char *func_name_str; const char *str1; JSRecord *p; + JSGCRef err_ref; if (!JS_IsObject (error_obj)) return; /* protection in the out of memory case */ + /* Protect error_obj from GC during backtrace building */ + JS_PushGCRef (ctx, &err_ref); + err_ref.val = error_obj; + js_dbuf_init (ctx, &dbuf); if (filename) { dbuf_printf (&dbuf, " at %s", filename); if (line_num != -1) dbuf_printf (&dbuf, ":%d:%d", line_num, col_num); dbuf_putc (&dbuf, '\n'); str = JS_NewString (ctx, filename); - if (JS_IsException (str)) return; + if (JS_IsException (str)) { + JS_PopGCRef (ctx, &err_ref); + return; + } /* Note: SpiderMonkey does that, could update once there is a standard */ JSValue key_fileName = JS_KEY_STR (ctx, "fileName"); JSValue key_lineNumber = JS_KEY_STR (ctx, "lineNumber"); JSValue key_columnNumber = JS_KEY_STR (ctx, "columnNumber"); - if (JS_SetPropertyInternal (ctx, error_obj, key_fileName, str) < 0 - || JS_SetPropertyInternal (ctx, error_obj, key_lineNumber, JS_NewInt32 (ctx, line_num)) + if (JS_SetPropertyInternal (ctx, err_ref.val, key_fileName, str) < 0 + || JS_SetPropertyInternal (ctx, err_ref.val, key_lineNumber, JS_NewInt32 (ctx, line_num)) < 0 - || JS_SetPropertyInternal (ctx, error_obj, key_columnNumber, JS_NewInt32 (ctx, col_num)) + || JS_SetPropertyInternal (ctx, err_ref.val, key_columnNumber, JS_NewInt32 (ctx, col_num)) < 0) { + JS_PopGCRef (ctx, &err_ref); return; } } @@ -4163,7 +4285,8 @@ static void build_backtrace (JSContext *ctx, JSValue error_obj, const char *file else str = JS_NewString (ctx, (char *)dbuf.buf); dbuf_free (&dbuf); - JS_SetPropertyInternal (ctx, error_obj, JS_KEY_stack, str); + JS_SetPropertyInternal (ctx, err_ref.val, JS_KEY_stack, str); + JS_PopGCRef (ctx, &err_ref); } /* Note: it is important that no exception is returned by this function */ @@ -6618,12 +6741,20 @@ static JSValue js_closure (JSContext *ctx, JSValue bfunc, JSVarRef **cur_var_ref JSFunctionBytecode *b; JSValue func_obj; JSFunction *f; + JSGCRef bfunc_ref; + + /* Protect bfunc from GC during function allocation */ + JS_PUSH_VALUE (ctx, bfunc); - b = JS_VALUE_GET_PTR (bfunc); func_obj = js_new_function (ctx, JS_FUNC_KIND_BYTECODE); if (JS_IsException (func_obj)) { + JS_POP_VALUE (ctx, bfunc); return JS_EXCEPTION; } + + JS_POP_VALUE (ctx, bfunc); + b = JS_VALUE_GET_PTR (bfunc); + func_obj = js_closure2 (ctx, func_obj, b, cur_var_refs, sf); if (JS_IsException (func_obj)) { /* bfunc has been freed */ @@ -6853,8 +6984,8 @@ static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue return JS_ThrowTypeError (caller_ctx, "not a function"); } f = JS_VALUE_GET_FUNCTION (func_obj); - /* Strict arity enforcement: too many arguments throws */ - if (unlikely (argc > f->length)) { + /* Strict arity enforcement: too many arguments throws (length < 0 means variadic) */ + if (unlikely (f->length >= 0 && argc > f->length)) { char buf[KEY_GET_STR_BUF_SIZE]; return JS_ThrowTypeError ( caller_ctx, "too many arguments for %s: expected %d, got %d", JS_KeyGetStr (caller_ctx, buf, KEY_GET_STR_BUF_SIZE, f->name), f->length, argc); @@ -7167,7 +7298,6 @@ restart: ret_val = JS_CallInternal (ctx, call_argv[-1], JS_NULL, call_argc, call_argv, 0); if (unlikely (JS_IsException (ret_val))) goto exception; if (opcode == OP_tail_call) goto done; - for (i = -1; i < call_argc; i++) sp -= call_argc + 1; *sp++ = ret_val; } @@ -9115,6 +9245,33 @@ typedef struct JSFunctionDef { int source_len; } JSFunctionDef; +/* Scan a single JSFunctionDef's cpool and recursively scan its children */ +static void gc_scan_parser_fd (JSContext *ctx, JSFunctionDef *fd, + uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + if (!fd) return; + /* Scan constant pool */ + for (int i = 0; i < fd->cpool_count; i++) { + fd->cpool[i] = gc_copy_value (ctx, fd->cpool[i], from_base, from_end, to_base, to_free, to_end); + } + /* Scan func_name */ + fd->func_name = gc_copy_value (ctx, fd->func_name, from_base, from_end, to_base, to_free, to_end); + /* Scan filename */ + fd->filename = gc_copy_value (ctx, fd->filename, from_base, from_end, to_base, to_free, to_end); + /* Recursively scan child functions */ + struct list_head *el; + list_for_each (el, &fd->child_list) { + JSFunctionDef *child = list_entry (el, JSFunctionDef, link); + gc_scan_parser_fd (ctx, child, from_base, from_end, to_base, to_free, to_end); + } +} + +/* Scan parser's cpool values during GC - called when parsing is in progress */ +static void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + gc_scan_parser_fd (ctx, ctx->current_parse_fd, from_base, from_end, to_base, to_free, to_end); +} + typedef struct JSToken { int val; const uint8_t *ptr; /* position in the source */ @@ -10854,7 +11011,7 @@ static int new_label_fd (JSFunctionDef *fd) { int label; LabelSlot *ls; - if (js_resize_array (fd->ctx, (void *)&fd->label_slots, sizeof (fd->label_slots[0]), &fd->label_size, fd->label_count + 1)) + if (pjs_resize_array ((void **)&fd->label_slots, sizeof (fd->label_slots[0]), &fd->label_size, fd->label_count + 1)) return -1; label = fd->label_count++; ls = &fd->label_slots[label]; @@ -10909,7 +11066,8 @@ static int emit_goto (JSParseState *s, int opcode, int label) { /* return the constant pool index. 'val' is not duplicated. */ static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val) { - if (js_resize_array (ctx, (void *)&fd->cpool, sizeof (fd->cpool[0]), &fd->cpool_size, fd->cpool_count + 1)) + (void)ctx; + if (pjs_resize_array ((void **)&fd->cpool, sizeof (fd->cpool[0]), &fd->cpool_size, fd->cpool_count + 1)) return -1; fd->cpool[fd->cpool_count++] = val; return fd->cpool_count - 1; @@ -11015,11 +11173,11 @@ static int push_scope (JSParseState *s) { /* XXX: potential arithmetic overflow */ new_size = max_int (fd->scope_count + 1, fd->scope_size * 3 / 2); if (fd->scopes == fd->def_scope_array) { - new_buf = js_realloc (s->ctx, NULL, new_size * sizeof (*fd->scopes)); + new_buf = pjs_malloc (new_size * sizeof (*fd->scopes)); if (!new_buf) return -1; memcpy (new_buf, fd->scopes, fd->scope_count * sizeof (*fd->scopes)); } else { - new_buf = js_realloc (s->ctx, fd->scopes, new_size * sizeof (*fd->scopes)); + new_buf = pjs_realloc (fd->scopes, new_size * sizeof (*fd->scopes)); if (!new_buf) return -1; } fd->scopes = new_buf; @@ -11073,7 +11231,7 @@ static int add_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { JS_ThrowInternalError (ctx, "too many local variables"); return -1; } - if (js_resize_array (ctx, (void **)&fd->vars, sizeof (fd->vars[0]), &fd->var_size, fd->var_count + 1)) + if (pjs_resize_array ((void **)&fd->vars, sizeof (fd->vars[0]), &fd->var_size, fd->var_count + 1)) return -1; vd = &fd->vars[fd->var_count++]; memset (vd, 0, sizeof (*vd)); @@ -11113,7 +11271,7 @@ static int add_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) { JS_ThrowInternalError (ctx, "too many arguments"); return -1; } - if (js_resize_array (ctx, (void **)&fd->args, sizeof (fd->args[0]), &fd->arg_size, fd->arg_count + 1)) + if (pjs_resize_array ((void **)&fd->args, sizeof (fd->args[0]), &fd->arg_size, fd->arg_count + 1)) return -1; vd = &fd->args[fd->arg_count++]; memset (vd, 0, sizeof (*vd)); @@ -11125,8 +11283,9 @@ static int add_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) { /* add a global variable definition */ static JSGlobalVar *add_global_var (JSContext *ctx, JSFunctionDef *s, JSValue name) { JSGlobalVar *hf; + (void)ctx; - if (js_resize_array (ctx, (void **)&s->global_vars, sizeof (s->global_vars[0]), &s->global_var_size, s->global_var_count + 1)) + if (pjs_resize_array ((void **)&s->global_vars, sizeof (s->global_vars[0]), &s->global_var_size, s->global_var_count + 1)) return NULL; hf = &s->global_vars[s->global_var_count++]; hf->cpool_idx = -1; @@ -13936,7 +14095,7 @@ static JSFunctionDef * js_new_function_def (JSContext *ctx, JSFunctionDef *parent, BOOL is_eval, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache) { JSFunctionDef *fd; - fd = js_mallocz (ctx, sizeof (*fd)); + fd = pjs_mallocz (sizeof (*fd)); if (!fd) return NULL; fd->ctx = ctx; @@ -14027,42 +14186,43 @@ static void js_free_function_def (JSContext *ctx, JSFunctionDef *fd) { free_bytecode_atoms (ctx->rt, fd->byte_code.buf, fd->byte_code.size, fd->use_short_opcodes); dbuf_free (&fd->byte_code); - js_free (ctx, fd->jump_slots); - js_free (ctx, fd->label_slots); - js_free (ctx, fd->line_number_slots); + pjs_free (fd->jump_slots); + pjs_free (fd->label_slots); + pjs_free (fd->line_number_slots); for (i = 0; i < fd->cpool_count; i++) { } - js_free (ctx, fd->cpool); + pjs_free (fd->cpool); for (i = 0; i < fd->var_count; i++) { } - js_free (ctx, fd->vars); + pjs_free (fd->vars); for (i = 0; i < fd->arg_count; i++) { } - js_free (ctx, fd->args); + pjs_free (fd->args); for (i = 0; i < fd->global_var_count; i++) { } - js_free (ctx, fd->global_vars); + pjs_free (fd->global_vars); for (i = 0; i < fd->closure_var_count; i++) { JSClosureVar *cv = &fd->closure_var[i]; + (void)cv; } - js_free (ctx, fd->closure_var); + pjs_free (fd->closure_var); - if (fd->scopes != fd->def_scope_array) js_free (ctx, fd->scopes); + if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes); dbuf_free (&fd->pc2line); - js_free (ctx, fd->source); + pjs_free (fd->source); if (fd->parent) { /* remove in parent list */ list_del (&fd->link); } - js_free (ctx, fd); + pjs_free (fd); } #ifdef DUMP_BYTECODE @@ -14461,7 +14621,7 @@ static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOO return -1; } - if (js_resize_array (ctx, (void **)&s->closure_var, sizeof (s->closure_var[0]), &s->closure_var_size, s->closure_var_count + 1)) + if (pjs_resize_array ((void **)&s->closure_var, sizeof (s->closure_var[0]), &s->closure_var_size, s->closure_var_count + 1)) return -1; cv = &s->closure_var[s->closure_var_count++]; cv->is_local = is_local; @@ -16092,13 +16252,13 @@ static __exception int resolve_labels (JSContext *ctx, JSFunctionDef *s) { #if SHORT_OPCODES if (s->jump_size) { s->jump_slots - = js_mallocz (s->ctx, sizeof (*s->jump_slots) * s->jump_size); + = pjs_mallocz (sizeof (*s->jump_slots) * s->jump_size); if (s->jump_slots == NULL) return -1; } #endif /* XXX: Should skip this phase if not generating SHORT_OPCODES */ if (s->line_number_size && !s->strip_debug) { - s->line_number_slots = js_mallocz (s->ctx, sizeof (*s->line_number_slots) * s->line_number_size); + s->line_number_slots = pjs_mallocz (sizeof (*s->line_number_slots) * s->line_number_size); if (s->line_number_slots == NULL) return -1; s->line_number_last = s->source_pos; s->line_number_last_pc = 0; @@ -16725,14 +16885,14 @@ static __exception int resolve_labels (JSContext *ctx, JSFunctionDef *s) { } } } - js_free (ctx, s->jump_slots); + pjs_free (s->jump_slots); s->jump_slots = NULL; #endif - js_free (ctx, s->label_slots); + pjs_free (s->label_slots); s->label_slots = NULL; /* XXX: should delay until copying to runtime bytecode function */ compute_pc2line_info (s); - js_free (ctx, s->line_number_slots); + pjs_free (s->line_number_slots); s->line_number_slots = NULL; /* set the new byte code */ dbuf_free (&s->byte_code); @@ -16792,7 +16952,7 @@ static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int s->catch_pos_tab[pos] = catch_pos; /* queue the new PC to explore */ - if (js_resize_array (ctx, (void **)&s->pc_stack, sizeof (s->pc_stack[0]), &s->pc_stack_size, s->pc_stack_len + 1)) + if (pjs_resize_array ((void **)&s->pc_stack, sizeof (s->pc_stack[0]), &s->pc_stack_size, s->pc_stack_len + 1)) return -1; s->pc_stack[s->pc_stack_len++] = pos; return 0; @@ -16808,12 +16968,12 @@ static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, in s->bc_len = fd->byte_code.size; /* bc_len > 0 */ s->stack_level_tab - = js_malloc (ctx, sizeof (s->stack_level_tab[0]) * s->bc_len); + = pjs_malloc (sizeof (s->stack_level_tab[0]) * s->bc_len); if (!s->stack_level_tab) return -1; for (i = 0; i < s->bc_len; i++) s->stack_level_tab[i] = 0xffff; s->pc_stack = NULL; - s->catch_pos_tab = js_malloc (ctx, sizeof (s->catch_pos_tab[0]) * s->bc_len); + s->catch_pos_tab = pjs_malloc (sizeof (s->catch_pos_tab[0]) * s->bc_len); if (!s->catch_pos_tab) goto fail; s->stack_len_max = 0; @@ -16942,15 +17102,15 @@ static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, in if (ss_check (ctx, s, pos_next, op, stack_len, catch_pos)) goto fail; done_insn:; } - js_free (ctx, s->pc_stack); - js_free (ctx, s->catch_pos_tab); - js_free (ctx, s->stack_level_tab); + pjs_free (s->pc_stack); + pjs_free (s->catch_pos_tab); + pjs_free (s->stack_level_tab); *pstack_size = s->stack_len_max; return 0; fail: - js_free (ctx, s->pc_stack); - js_free (ctx, s->catch_pos_tab); - js_free (ctx, s->stack_level_tab); + pjs_free (s->pc_stack); + pjs_free (s->catch_pos_tab); + pjs_free (s->stack_level_tab); *pstack_size = 0; return -1; } @@ -17049,7 +17209,8 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { byte_code_offset = function_size; function_size += fd->byte_code.size; - b = js_mallocz (ctx, function_size); + /* Bytecode is immutable - allocate outside GC heap */ + b = pjs_mallocz (function_size); if (!b) goto fail; b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); @@ -17104,20 +17265,21 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { // DynBuf pc2line; // compute_pc2line_info(fd, &pc2line); // js_free(ctx, fd->line_number_slots) - b->debug.pc2line_buf = js_realloc (ctx, fd->pc2line.buf, fd->pc2line.size); - if (!b->debug.pc2line_buf) b->debug.pc2line_buf = fd->pc2line.buf; + /* pc2line.buf is allocated by dbuf (system realloc), so just use it directly */ + b->debug.pc2line_buf = fd->pc2line.buf; + fd->pc2line.buf = NULL; /* Transfer ownership */ b->debug.pc2line_len = fd->pc2line.size; b->debug.source = fd->source; b->debug.source_len = fd->source_len; } - if (fd->scopes != fd->def_scope_array) js_free (ctx, fd->scopes); + if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes); b->closure_var_count = fd->closure_var_count; if (b->closure_var_count) { b->closure_var = (void *)((uint8_t *)b + closure_var_offset); memcpy (b->closure_var, fd->closure_var, b->closure_var_count * sizeof (*b->closure_var)); } - js_free (ctx, fd->closure_var); + pjs_free (fd->closure_var); fd->closure_var = NULL; b->has_prototype = fd->has_prototype; @@ -17740,6 +17902,7 @@ static JSValue __JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char * fd = js_new_function_def (ctx, NULL, TRUE, FALSE, filename, s->buf_start, &s->get_line_col_cache); if (!fd) goto fail1; s->cur_func = fd; + ctx->current_parse_fd = fd; /* Set GC root for parser's cpool */ fd->eval_type = eval_type; fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT); fd->js_mode = js_mode; @@ -17755,11 +17918,13 @@ static JSValue __JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char * if (err) { fail: free_token (s, &s->token); + ctx->current_parse_fd = NULL; /* Clear GC root before freeing */ js_free_function_def (ctx, fd); goto fail1; } /* create the function object and all the enclosed functions */ + ctx->current_parse_fd = NULL; /* Clear GC root - fd ownership transfers to js_create_function */ fun_obj = js_create_function (ctx, fd); if (JS_IsException (fun_obj)) goto fail1; /* Could add a flag to avoid resolution if necessary */ @@ -22544,6 +22709,20 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, return pretext_end (ctx, result); } +/* print(args...) - print arguments to stdout */ +static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + for (int i = 0; i < argc; i++) { + const char *str = JS_ToCString (ctx, argv[i]); + if (str) { + fputs (str, stdout); + JS_FreeCString (ctx, str); + } + if (i < argc - 1) fputc (' ', stdout); + } + fputc ('\n', stdout); + return JS_NULL; +} + /* ---------------------------------------------------------------------------- * array function and sub-functions * ---------------------------------------------------------------------------- @@ -25377,6 +25556,9 @@ void JS_AddIntrinsicBaseObjects (JSContext *ctx) { js_set_global_cfunc(ctx, "push", js_cell_push, 2); js_set_global_cfunc(ctx, "pop", js_cell_pop, 1); js_set_global_cfunc(ctx, "meme", js_cell_meme, 2); + + /* I/O functions */ + js_set_global_cfunc(ctx, "print", js_print, -1); /* variadic: length < 0 means no arg limit */ } }