From 4493dd74cc365b98a9231b6387f251387ae321f6 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sun, 17 Nov 2024 11:35:05 -0600 Subject: [PATCH] add debugging info gathering --- meson.build | 21 +++- meson.options | 3 +- quickjs.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++++-- quickjs.h | 16 ++- 4 files changed, 356 insertions(+), 21 deletions(-) diff --git a/meson.build b/meson.build index fec0c91..d7dd47e 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,9 @@ project('quickjs', 'c') cc = meson.get_compiler('c') -m_dep = cc.find_library('m', required:false) + +deps = [] +deps += cc.find_library('m', required:false) add_project_arguments('-DCONFIG_VERSION="2024-02-14"', language : 'c') @@ -9,18 +11,26 @@ if get_option('bignum') add_project_arguments('-DCONFIG_BIGNUM', language : 'c') endif +if get_option('trace') + add_project_arguments('-DTRACY_ENABLE', language:'c') + deps += dependency('tracy', static:true) +endif + lib_sources = ['libbf.c', 'libregexp.c', 'quickjs.c', 'libunicode.c', 'cutils.c'] libquickjs = library('quickjs', lib_sources, - dependencies: m_dep + dependencies: deps ) -quickjs_dep = declare_dependency(link_with: libquickjs, include_directories: include_directories('.'), dependencies: m_dep) +quickjs_dep = declare_dependency(link_with: libquickjs, include_directories: include_directories('.'), dependencies: deps) + +threads = dependency('threads') qjsc = executable('qjsc', 'qjsc.c', 'quickjs-libc.c', - dependencies: quickjs_dep + dependencies: [quickjs_dep,threads], + build_by_default:false ) qjscalc_c = custom_target( @@ -39,5 +49,6 @@ qjsrepl_c = custom_target( qjs = executable('qjs', 'qjs.c', 'quickjs-libc.c', qjscalc_c, qjsrepl_c, - dependencies: quickjs_dep + dependencies: [quickjs_dep, threads], + build_by_default:false ) \ No newline at end of file diff --git a/meson.options b/meson.options index 165d9d6..a8b1c86 100644 --- a/meson.options +++ b/meson.options @@ -1 +1,2 @@ -option('bignum', type:'boolean', value:true) \ No newline at end of file +option('bignum', type:'boolean', value:true) +option('trace', type:'boolean', value:false) \ No newline at end of file diff --git a/quickjs.c b/quickjs.c index 8449855..134b2d8 100644 --- a/quickjs.c +++ b/quickjs.c @@ -33,6 +33,10 @@ #include #include +#ifdef TRACY_ENABLE +#include +#endif + #if defined(__APPLE__) #include #elif defined(__linux__) || defined(__GLIBC__) @@ -1063,6 +1067,7 @@ enum OPCodeEnum { OP_TEMP_END, }; + static int JS_InitAtoms(JSRuntime *rt); static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, int atom_type); @@ -1302,15 +1307,6 @@ static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; ////// CUSTOM DUMP FUNCTIONS -void JS_DumpMyValue(JSRuntime *rt, JSValue v) -{ - uint32_t tag = JS_VALUE_GET_TAG(v); - if (tag == JS_TAG_OBJECT) - JS_DumpObject(dumpout, rt, JS_VALUE_GET_OBJ(v)); - else - JS_DumpValueShort(dumpout, rt, v); -} - void JS_PrintShapes(JSRuntime *rt) { JS_DumpShapes(rt); @@ -1335,7 +1331,7 @@ static void js_trigger_gc(JSRuntime *rt, size_t size) fprintf(dumpout, "GC: size=%" PRIu64 "\n", (uint64_t)rt->malloc_state.malloc_size); #endif - JS_RunGC(rt); + JS_RunGC(rt, NULL); rt->malloc_gc_threshold = rt->malloc_state.malloc_size + (rt->malloc_state.malloc_size >> 1); } @@ -1693,6 +1689,9 @@ static void *js_def_malloc(JSMallocState *s, size_t size) s->malloc_count++; s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; +#ifdef TRACY_ENABLE + TracyCAllocN(ptr,js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "quickjs"); +#endif return ptr; } @@ -1703,6 +1702,9 @@ static void js_def_free(JSMallocState *s, void *ptr) s->malloc_count--; s->malloc_size -= js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; +#ifdef TRACY_ENABLE + TracyCFreeN(ptr, "quickjs"); +#endif free(ptr); } @@ -1719,17 +1721,26 @@ static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size) if (size == 0) { s->malloc_count--; s->malloc_size -= old_size + MALLOC_OVERHEAD; +#ifdef TRACY_ENABLE + TracyCFreeN(ptr, "quickjs"); +#endif free(ptr); return NULL; } if (s->malloc_size + size - old_size > s->malloc_limit) return NULL; - + +#ifdef TRACY_ENABLE + TracyCFreeN(ptr, "quickjs"); +#endif ptr = realloc(ptr, size); if (!ptr) return NULL; s->malloc_size += js_def_malloc_usable_size(ptr) - old_size; +#ifdef TRACY_ENABLE + TracyCAllocN(ptr,js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "quickjs"); +#endif return ptr; } @@ -6231,8 +6242,22 @@ static void gc_free_cycles(JSRuntime *rt) init_list_head(&rt->gc_zero_ref_count_list); } +struct gc_object { + void *address; + int refs; + void *shape; + int shrf; + int hashed; + void *class; + JSAtom classname; +}; + static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects) { +#ifdef TRACY_ENABLE + TracyCZone(js_gc, 1) +#endif + if (remove_weak_objects) { /* free the weakly referenced object or symbol structures, delete the associated Map/Set entries and queue the finalization @@ -6242,7 +6267,7 @@ static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects) double n = stm_now(); double size = rt->malloc_state.malloc_size; - + /* decrement the reference of the children of each object. mark = 1 after this pass. */ gc_decref(rt); @@ -6250,8 +6275,27 @@ static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects) /* keep the GC objects with a non zero refcount and their childs */ gc_scan(rt); +/*if (ctx) { + ret = JS_NewArray(ctx); + int idx = 0; + // Fill with release information + struct list_head *el; + JSGCObjectHeader *p; + list_for_each(el, &rt->tmp_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) continue; + JSValue dump = js_dump_object(ctx, (JSObject*)p); + JS_SetPropertyUint32(ctx, ret, idx++, dump); + } +} +*/ /* free the GC objects in a cycle */ gc_free_cycles(rt); + +#ifdef TRACY_ENABLE + TracyCZoneEnd(js_gc) +#endif + return ret; } void JS_RunGC(JSRuntime *rt) @@ -6929,7 +6973,7 @@ static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, /* in order to avoid executing arbitrary code during the stack trace generation, we only look at simple 'name' properties containing a string. */ -static const char *get_func_name(JSContext *ctx, JSValueConst func) +const char *get_func_name(JSContext *ctx, JSValueConst func) { JSProperty *pr; JSShapeProperty *prs; @@ -6948,6 +6992,16 @@ static const char *get_func_name(JSContext *ctx, JSValueConst func) return JS_ToCString(ctx, val); } +int js_fn_linenum(JSContext *js, JSValueConst fn) +{ + return JS_VALUE_GET_OBJ(fn)->u.func.function_bytecode->debug.line_num; +} + +JSAtom js_fn_filename(JSContext *js, JSValueConst fn) +{ + return JS_VALUE_GET_OBJ(fn)->u.func.function_bytecode->debug.filename; +} + #define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) /* if filename != NULL, an additional level is added with the filename @@ -13032,9 +13086,53 @@ static __maybe_unused void JS_DumpObjectHeader(FILE *fp, JSRuntime *rt) "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); } +JSValue js_dump_object(JSContext *ctx, JSObject *p) +{ + JSValue ret = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, ret, "address", JS_NewInt32(ctx,(int)p)); + JS_SetPropertyStr(ctx, ret, "refs", JS_NewInt32(ctx, p->header.ref_count)); + + JSShape *sh = p->shape; + if (sh) { + JS_SetPropertyStr(ctx, ret, "shape", JS_NewInt32(ctx,(int)sh)); + JS_SetPropertyStr(ctx, ret, "hashed", JS_NewBool(ctx,sh->is_hashed)); + JS_SetPropertyStr(ctx, ret, "class", JS_NewInt32(ctx,(int)sh->proto)); + JS_SetPropertyStr(ctx, ret, "shape_refs", JS_NewInt32(ctx,sh->header.ref_count)); + } + JS_SetPropertyStr(ctx, ret, "class_name", JS_AtomToString(ctx, JS_GetRuntime(ctx)->class_array[p->class_id].class_name)); + + return ret; +} + +struct gc_object js_dump_gc_object(JSContext *ctx, JSObject *p) +{ + struct gc_object ret = {0}; + ret.address = p; + ret.refs = p->header.ref_count; + + JSShape *sh = p->shape; + if (sh) { + ret.shape = sh; + ret.hashed = sh->is_hashed; + ret.class = sh->proto; + ret.shrf = sh->header.ref_count; + ret.classname = JS_GetRuntime(ctx)->class_array[p->class_id].class_name; + } + + return ret; +} + +JSValue js_dump_value(JSContext *ctx, JSValue v) +{ + return js_dump_object(ctx, JS_VALUE_GET_OBJ(v)); +} + /* for debug only: dump an object without side effect */ static __maybe_unused void JS_DumpObject(FILE *fp, JSRuntime *rt, JSObject *p) { + struct gc_object obj; + obj.address = (void*)p; + obj.refs = p->header.ref_count; uint32_t i; char atom_buf[ATOM_GET_STR_BUF_SIZE]; JSShape *sh; @@ -13048,6 +13146,9 @@ static __maybe_unused void JS_DumpObject(FILE *fp, JSRuntime *rt, JSObject *p) (void *)p, p->header.ref_count); if (sh) { + obj.shrf = sh->header.ref_count; + obj.hashed = sh->is_hashed; + obj.class = sh->proto; fprintf(fp, "%3d%c %14p ", sh->header.ref_count, " *"[sh->is_hashed], @@ -13139,29 +13240,39 @@ static __maybe_unused void JS_DumpObject(FILE *fp, JSRuntime *rt, JSObject *p) static __maybe_unused void JS_DumpGCObject(FILE *fp, JSRuntime *rt, JSGCObjectHeader *p) { + struct gc_object obj; + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { JS_DumpObject(fp, rt, (JSObject *)p); } else { +// obj.address = p; +// obj.refs = p->ref_count; fprintf(fp, "%14p %4d ", (void *)p, p->ref_count); switch(p->gc_obj_type) { case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: +// obj.type = JS_NewAtomString(ctx,"function_bytecode"); fprintf(fp, "[function bytecode]"); break; case JS_GC_OBJ_TYPE_SHAPE: +// obj.type = JS_NewAtomString(ctx,"shape"); fprintf(fp, "[shape]"); break; case JS_GC_OBJ_TYPE_VAR_REF: +// obj.type = JS_NewAtomString(ctx,"var_ref"); fprintf(fp, "[var_ref]"); break; case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: +// obj.type = JS_NewAtomString(ctx,"async_function"); fprintf(fp, "[async_function]"); break; case JS_GC_OBJ_TYPE_JS_CONTEXT: +// obj.type = JS_NewAtomString(ctx,"context"); fprintf(fp, "[js_context]"); break; default: +// obj.type = JS_NewAtomString(ctx,"unknown"); fprintf(fp, "[unknown %d]", p->gc_obj_type); break; } @@ -16097,6 +16208,16 @@ static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, sf->arg_buf = (JSValue*)arg_buf; func = p->u.cfunc.c_function; + +#ifdef TRACY_ENABLE + JSValue js_name = JS_GetPropertyStr(ctx, func_obj, "name"); + const char *ccname = JS_ToCString(ctx, js_name); + const char *file = ""; + TracyCZoneCtx tracy_ctx = ___tracy_emit_zone_begin_alloc(___tracy_alloc_srcloc(1, file, strlen(file), ccname, strlen(ccname), (int)ccname), 1); + JS_FreeCString(ctx,ccname); + JS_FreeValue(ctx, js_name); +#endif + switch(cproto) { case JS_CFUNC_constructor: case JS_CFUNC_constructor_or_func: @@ -16181,6 +16302,11 @@ static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, } rt->current_stack_frame = sf->prev_frame; + +#ifdef TRACY_ENABLE + ___tracy_emit_zone_end(tracy_ctx); +#endif + return ret_val; } @@ -16312,6 +16438,23 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } b = p->u.func.function_bytecode; + // IS A FUNCTION ****** TRACE HERE ****** +#ifdef TRACY_ENABLE + const char *fn_src = JS_AtomToCString(caller_ctx, js_fn_filename(caller_ctx,func_obj)); + const char *js_func_name = get_func_name(caller_ctx, func_obj); + const char *fn_name; + if (!js_func_name || js_func_name[0] == '\0') + fn_name = ""; + else + fn_name = js_func_name; + + uint64_t srcloc; + srcloc = ___tracy_alloc_srcloc(js_fn_linenum(caller_ctx,func_obj), fn_src, strlen(fn_src), fn_name, strlen(fn_name), (int)fn_src); + + TracyCZoneCtx tracy_ctx = ___tracy_emit_zone_begin_alloc(srcloc,1); + JS_FreeCString(caller_ctx,js_func_name); +#endif + if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { arg_allocated_size = b->arg_count; } else { @@ -18865,6 +19008,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } } rt->current_stack_frame = sf->prev_frame; + +#ifdef TRACY_ENABLE + ___tracy_emit_zone_end(tracy_ctx); +#endif + return ret_val; } @@ -54779,4 +54927,167 @@ void JS_AddIntrinsicWeakRef(JSContext *ctx) js_finrec_proto_funcs, countof(js_finrec_proto_funcs)); JS_NewGlobalCConstructor(ctx, "FinalizationRegistry", js_finrec_constructor, 1, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY]); + +} + +uint32_t js_debugger_stack_depth(JSContext *ctx) { + uint32_t stack_index = 0; + JSStackFrame *sf = ctx->rt->current_stack_frame; + while (sf != NULL) { + sf = sf->prev_frame; + stack_index++; + } + return stack_index; +} + +JSValue js_debugger_backtrace_fns(JSContext *ctx, const uint8_t *cur_pc) +{ + JSValue ret = JS_NewArray(ctx); + JSStackFrame *sf; + const char *func_name_str; + JSObject *p; + uint32_t stack_index = 0; + + for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + uint32_t id = stack_index++; + JS_SetPropertyUint32(ctx, ret, id, JS_DupValue(ctx,sf->cur_func)); + } + return ret; +} + +JSValue js_debugger_build_backtrace(JSContext *ctx, const uint8_t *cur_pc) +{ + JSStackFrame *sf; + const char *func_name_str; + JSObject *p; + JSValue ret = JS_NewArray(ctx); + uint32_t stack_index = 0; + + for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + JSValue current_frame = JS_NewObject(ctx); + + uint32_t id = stack_index++; + JS_SetPropertyStr(ctx, current_frame, "id", JS_NewUint32(ctx, id)); + + func_name_str = get_func_name(ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + JS_SetPropertyStr(ctx, current_frame, "name", JS_NewString(ctx, "")); + else + JS_SetPropertyStr(ctx, current_frame, "name", JS_NewString(ctx, func_name_str)); + JS_FreeCString(ctx, func_name_str); + + p = JS_VALUE_GET_OBJ(sf->cur_func); + if (p && js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b; + int line_num1; + + b = p->u.func.function_bytecode; + if (b->has_debug) { + const uint8_t *pc = sf != ctx->rt->current_stack_frame || !cur_pc ? sf->cur_pc : cur_pc; + line_num1 = find_line_num(ctx, b, pc - b->byte_code_buf - 1); + JS_SetPropertyStr(ctx, current_frame, "filename", JS_AtomToString(ctx, b->debug.filename)); + if (line_num1 != -1) + JS_SetPropertyStr(ctx, current_frame, "line", JS_NewUint32(ctx, line_num1)); + } + } else { + JS_SetPropertyStr(ctx, current_frame, "name", JS_NewString(ctx, "(native)")); + } + JS_SetPropertyUint32(ctx, ret, id, current_frame); + } + return ret; +} + +JSValue js_debugger_fn_info(JSContext *ctx, JSValue fn) +{ + JSValue ret = JS_NewObject(ctx); + JSObject *f = JS_VALUE_GET_OBJ(fn); + if (!f || !js_class_has_bytecode(f->class_id)) + goto done; + + JSFunctionBytecode *b = f->u.func.function_bytecode; + if (b->has_debug) { + JS_SetPropertyStr(ctx, ret, "filename", JS_AtomToString(ctx, b->debug.filename)); + JS_SetPropertyStr(ctx, ret, "line", JS_NewInt32(ctx,b->debug.line_num)); + } + done: + return ret; +} + +JSValue js_debugger_local_variables(JSContext *ctx, int stack_index) { + JSValue ret = JS_NewObject(ctx); + + JSStackFrame *sf; + int cur_index = 0; + + for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + // this val is one frame up + if (cur_index == stack_index - 1) { + JSObject *f = JS_VALUE_GET_OBJ(sf->cur_func); + if (f && js_class_has_bytecode(f->class_id)) { + JSFunctionBytecode *b = f->u.func.function_bytecode; + + JSValue this_obj = sf->var_buf[b->var_count]; + // only provide a this if it is not the global object. + if (JS_VALUE_GET_OBJ(this_obj) != JS_VALUE_GET_OBJ(ctx->global_obj)) + JS_SetPropertyStr(ctx, ret, "this", JS_DupValue(ctx, this_obj)); + } + } + + if (cur_index < stack_index) { + cur_index++; + continue; + } + + JSObject *f = JS_VALUE_GET_OBJ(sf->cur_func); + if (!f || !js_class_has_bytecode(f->class_id)) + goto done; + JSFunctionBytecode *b = f->u.func.function_bytecode; + + for (uint32_t i = 0; i < b->arg_count + b->var_count; i++) { + JSValue var_val; + if (i < b->arg_count) + var_val = sf->arg_buf[i]; + else + var_val = sf->var_buf[i - b->arg_count]; + + if (JS_IsUninitialized(var_val)) + continue; + + JSVarDef *vd = b->vardefs + i; + JS_SetProperty(ctx, ret, vd->var_name, JS_DupValue(ctx, var_val)); + } + + break; + } + +done: + return ret; +} + +JSValue js_debugger_closure_variables(JSContext *ctx, JSValue fn) { + JSValue ret = JS_NewObject(ctx); + JSObject *f = JS_VALUE_GET_OBJ(fn); + if (!f || !js_class_has_bytecode(f->class_id)) + goto done; + + JSFunctionBytecode *b = f->u.func.function_bytecode; + + for (uint32_t i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cvar = b->closure_var + i; + JSValue var_val; + JSVarRef *var_ref = NULL; + if (f->u.func.var_refs) + var_ref = f->u.func.var_refs[i]; + if (!var_ref || !var_ref->pvalue) + continue; + var_val = *var_ref->pvalue; + + if (JS_IsUninitialized(var_val)) + continue; + + JS_SetProperty(ctx, ret, cvar->var_name, JS_DupValue(ctx, var_val)); + } + +done: + return ret; } diff --git a/quickjs.h b/quickjs.h index 8da88db..1105825 100644 --- a/quickjs.h +++ b/quickjs.h @@ -255,6 +255,10 @@ typedef struct JSValue { #define JS_NAN (JSValue){ .u.float64 = JS_FLOAT64_NAN, JS_TAG_FLOAT64 } +int js_fn_linenum(JSContext *js, JSValueConst fn); +JSAtom js_fn_filename(JSContext *js, JSValueConst fn); +const char *get_func_name(JSContext *js, JSValueConst fn); + static inline JSValue __JS_NewFloat64(JSContext *ctx, double d) { JSValue v; @@ -388,7 +392,7 @@ void *JS_GetRuntimeOpaque(JSRuntime *rt); void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque); typedef void JS_MarkFunc(JSRuntime *rt, JSGCObjectHeader *gp); void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); -void JS_RunGC(JSRuntime *rt); +JSValue JS_RunGC(JSRuntime *rt, JSContext *ctx); JS_BOOL JS_IsLiveObject(JSRuntime *rt, JSValueConst obj); JSContext *JS_NewContext(JSRuntime *rt); @@ -454,7 +458,6 @@ typedef struct JSMemoryUsage { void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s); void JS_FillMemoryState(JSRuntime *rt, JSMemoryUsage *s); void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); -void JS_DumpMyValue(JSRuntime *rt, JSValue v); double JS_MyValueSize(JSRuntime *rt, JSValue v); /* atom support */ @@ -1126,6 +1129,15 @@ int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name, int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, const JSCFunctionListEntry *tab, int len); +JSValue js_debugger_closure_variables(JSContext *js, JSValue fn); +JSValue js_debugger_local_variables(JSContext *ctx, int stack_index); +JSValue js_debugger_build_backtrace(JSContext *ctx, const uint8_t *cur_pc); +JSValue js_debugger_fn_info(JSContext *ctx, JSValue fn); +JSValue js_debugger_backtrace_fns(JSContext *ctx, const uint8_t *cur_pc); +uint32_t js_debugger_stack_depth(JSContext *ctx); +JSValue js_dump_value(JSContext *ctx, JSValue v); +JSValue js_dump_object(JSContext *ctx, JSObject *p); + #undef js_unlikely #undef js_force_inline