add debugging info gathering

This commit is contained in:
2024-11-17 11:35:05 -06:00
parent 0a4037cd63
commit 4493dd74cc
4 changed files with 356 additions and 21 deletions

View File

@@ -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
)

View File

@@ -1 +1,2 @@
option('bignum', type:'boolean', value:true)
option('bignum', type:'boolean', value:true)
option('trace', type:'boolean', value:false)

337
quickjs.c
View File

@@ -33,6 +33,10 @@
#include <fenv.h>
#include <math.h>
#ifdef TRACY_ENABLE
#include <tracy/TracyC.h>
#endif
#if defined(__APPLE__)
#include <malloc/malloc.h>
#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 = "<native C>";
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 = "<anonymous>";
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, "<anonymous>"));
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;
}

View File

@@ -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