quickjs.c有五万多行代码。我们可以通过QuickJS解析执行JS代码的过程出发来分析QuickJS源码:
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
js_std_add_helpers(ctx, 0, NULL);
const char *scripts = "console.log('hello quickjs')";
JS_Eval(ctx, scripts, strlen(scripts), "main", 0);
上面代码中rt和ctx是先构造一个JS运行时和上下文环境,js_std_add_helpers是调用C的std方法帮助在控制台输出调试信息。
JS_NewRuntime
函数会新建一个JSRuntime *rt
。结构体JSRuntime
定义如下:
struct JSRuntime {
//内存分配函数
JSMallocFunctions mf;
//定义了内存分配的一系列信息
JSMallocState malloc_state;
const char *rt_info;
int atom_hash_size; /* power of two */
int atom_count;
int atom_size;
int atom_count_resize; /* resize hash table at this count */
uint32_t *atom_hash;
JSAtomStruct **atom_array;
int atom_free_index; /* 0 = none */
int class_count; /* size of class_array */
JSClass *class_array;
struct list_head context_list; /* list of JSContext.link */
/* list of JSGCObjectHeader.link. List of allocated GC objects (used
by the garbage collector) */
struct list_head gc_obj_list;
/* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */
struct list_head gc_zero_ref_count_list;
struct list_head tmp_obj_list; /* used during GC */
JSGCPhaseEnum gc_phase : 8;
size_t malloc_gc_threshold;
#ifdef DUMP_LEAKS
struct list_head string_list; /* list of JSString.link */
#endif
/* stack limitation */
uintptr_t stack_size; /* in bytes, 0 if no limit */
uintptr_t stack_top;
uintptr_t stack_limit; /* lower stack limit */
JSValue current_exception;
/* true if inside an out of memory error, to avoid recursing */
BOOL in_out_of_memory : 8;
struct JSStackFrame *current_stack_frame;
JSInterruptHandler *interrupt_handler;
void *interrupt_opaque;
JSHostPromiseRejectionTracker *host_promise_rejection_tracker;
void *host_promise_rejection_tracker_opaque;
struct list_head job_list; /* list of JSJobEntry.link */
JSModuleNormalizeFunc *module_normalize_func;
JSModuleLoaderFunc *module_loader_func;
void *module_loader_opaque;
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
/* used to allocate, free and clone SharedArrayBuffers */
JSSharedArrayBufferFunctions sab_funcs;
/* Shape hash table */
int shape_hash_bits;
int shape_hash_size;
int shape_hash_count; /* number of hashed shapes */
JSShape **shape_hash;
#ifdef CONFIG_BIGNUM
bf_context_t bf_ctx;
JSNumericOperations bigint_ops;
JSNumericOperations bigfloat_ops;
JSNumericOperations bigdecimal_ops;
uint32_t operator_count;
#endif
void *user_opaque;
};
JSMallocState结构体定义如下:
typedef struct JSMallocState {
size_t malloc_count;
size_t malloc_size;
size_t malloc_limit;
void *opaque; /* user opaque */
} JSMallocState;
JSMallocFunctions结构体定义如下:
typedef struct JSMallocFunctions {
void *(*js_malloc)(JSMallocState *s, size_t size);
void (*js_free)(JSMallocState *s, void *ptr);
void *(*js_realloc)(JSMallocState *s, void *ptr, size_t size);
size_t (*js_malloc_usable_size)(const void *ptr);
} JSMallocFunctions;
其中,包含如下内容:
int atom_size
和原子结构数组指针JSAtomStruct **atom_array
JSClass *class_array
uintptr_t stack_top
uintptr_t stack_size
(大小以字节进行衡量)BOOL in_out_of_memory : 8
struct JSStackFrame *current_stack_frame
JSInterruptHandler *interrupt_handler
JSModuleLoaderFunc *module_loader_func
JSSharedArrayBufferFunctions sab_funcs
JSShape **shape_hash
其函数主体如下:
JSRuntime *JS_NewRuntime(void){
return JS_NewRuntime2(&def_malloc_funcs, NULL);
}
JS_NewRuntime2函数定义如下:
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque){
JSRuntime *rt;
JSMallocState ms;
memset(&ms, 0, sizeof(ms));
ms.opaque = opaque;
ms.malloc_limit = -1;
rt = mf->js_malloc(&ms, sizeof(JSRuntime));
if (!rt)
return NULL;
memset(rt, 0, sizeof(*rt));
rt->mf = *mf;
if (!rt->mf.js_malloc_usable_size) {
/* use dummy function if none provided */
rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown;
}
rt->malloc_state = ms;
rt->malloc_gc_threshold = 256 * 1024;
#ifdef CONFIG_BIGNUM
bf_context_init(&rt->bf_ctx, js_bf_realloc, rt);
set_dummy_numeric_ops(&rt->bigint_ops);
set_dummy_numeric_ops(&rt->bigfloat_ops);
set_dummy_numeric_ops(&rt->bigdecimal_ops);
#endif
init_list_head(&rt->context_list);
init_list_head(&rt->gc_obj_list);
init_list_head(&rt->gc_zero_ref_count_list);
rt->gc_phase = JS_GC_PHASE_NONE;
#ifdef DUMP_LEAKS
init_list_head(&rt->string_list);
#endif
init_list_head(&rt->job_list);
if (JS_InitAtoms(rt))
goto fail;
/* create the object, array and function classes */
if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT,
countof(js_std_class_def)) < 0)
goto fail;
rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods;
rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods;
rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods;
rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function;
rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call;
rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function;
rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call;
if (init_shape_hash(rt))
goto fail;
rt->stack_size = JS_DEFAULT_STACK_SIZE;
JS_UpdateStackTop(rt);
rt->current_exception = JS_NULL;
return rt;
fail:
JS_FreeRuntime(rt);
return NULL;
}
JS_NewRuntime2 函数对 JSRuntime 和 JSMallocState 初始化使用的是 memset 函数,这个函数一般是对比较大的结构体进行初始化,因为是直接操作内存,所以很快。
JS_NewRuntime2 函数使用 JSMallocFunctions 结构体来记录 JSMallocState 分配内存状态(?)。
JSRuntime2 里用 context_list 记录所有的上下文,gc_obj_list 记录分配的 GC 对象,在调用 JS_FreeValueRT 函数时如果 JSValue 的 tag 是字节码,同时不在 GC 清理周期内时,会将 GC 对象加到 gc_zero_ref_count_list 里。
JS_NewRuntime函数最后会用init_class_range函数创建对象、数组和函数类,每个类新建用的是JS_NewClass1函数,函数定义如下:
static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, int start, int count){
JSClassDef cm_s, *cm = &cm_s;
int i, class_id;
for(i = 0; i < count; i++) {
class_id = i + start;
memset(cm, 0, sizeof(*cm));
cm->finalizer = tab[i].finalizer;
cm->gc_mark = tab[i].gc_mark;
if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0)
return -1;
}
return 0;
}
/* Throw out of memory in case of error */
void *js_malloc(JSContext *ctx, size_t size)
{
void *ptr;
ptr = js_malloc_rt(ctx->rt, size);
if (unlikely(!ptr)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
return ptr;
}
void *js_malloc_rt(JSRuntime *rt, size_t size){
return rt->mf.js_malloc(&rt->malloc_state, size);
}