函数对象字节码信息结构体是 JSFunctionBytecode,js 函数在运行时的数据结构是 JSFunctionBytecode,创建函数就是初始化 JSFunctionBytecode 结构体,并设置里面所需的字段,这个过程就是将扫描代码生成的临时 JSFunctionDef 对应到 JSFunctionBytecode 中,由 js_create_function 函数负责处理。JSFunctionBytecode 结构体里的 byte_code_buf 字段是函数对象自己字节码的指针,vardefs 里是函数的参数和局部变量;closure_var 是用于存放外部可见的闭包变量,closure_var 通过 add_closure_var 函数进行添加,add_closure_var 函数会把要用到的变量添加成闭包变量,通过 get_closure_var2 函数往上层级递归给每层级函数添加闭包变量,直到找到目标函数;stack_size 是指堆栈的大小,stack_size 主要作用是为初始化栈时能够减少内存占用;cpool 是函数内常量池。
QuickJS 解释执行的函数是 JS_EvalFunctionInternal,函数体如下:
static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj,
JSValueConst this_obj,
JSVarRef **var_refs, JSStackFrame *sf)
{
JSValue ret_val;
uint32_t tag;
//tag表示函数对象是带字节码的JS_TAG_FUNCTION_BYTECODE还是JS_TAG_MODULE
tag = JS_VALUE_GET_TAG(fun_obj);
if (tag == JS_TAG_FUNCTION_BYTECODE) {
//解析2
fun_obj = js_closure(ctx, fun_obj, var_refs, sf);
ret_val = JS_CallFree(ctx, fun_obj, this_obj, 0, NULL);
} else if (tag == JS_TAG_MODULE) {
//解析1
JSModuleDef *m;
m = JS_VALUE_GET_PTR(fun_obj);
/* the module refcount should be >= 2 */
JS_FreeValue(ctx, fun_obj);
if (js_create_module_function(ctx, m) < 0)
goto fail;
if (js_link_module(ctx, m) < 0)
goto fail;
ret_val = js_evaluate_module(ctx, m);
if (JS_IsException(ret_val)) {
fail:
js_free_modules(ctx, JS_FREE_MODULE_NOT_EVALUATED);
return JS_EXCEPTION;
}
} else {
JS_FreeValue(ctx, fun_obj);
ret_val = JS_ThrowTypeError(ctx, "bytecode function expected");
}
return ret_val;
}
如果 tag 是 JS_TAG_MODULE ,那么表示当前函数对象是一个模块,因此需要按照模块的方式处理,JS_EvalFunctionInternal 函数会先调用 js_create_module_function 找出模块中导出的变量,js_create_module_function 函数调用链如下:
js_create_module_function -> js_create_module_bytecode_function -> js_create_module_var
执行完 js_create_module_function,会调用 js_link_module 函数,js_link_module 函数会处理所有要导入的变量,将其保存在 JSModuleDef 的 req_module_entries 数组里供解释执行时使用。然后 js_link_module 还会检查间接的导入。最后将导出的变量保存在模块 JSExportEntry 的 export_entries 数组里,然后使用 JS_Call 函数执行导出的这些全局变量的初始化。
JS_EvalFunctionInteral 函数对于 tag 是 JS_TAG_FUNCTION_BYTECODE 的函数对象会调用 js_closure 和 JS_CallFree 两个函数进行字节码的解释执行。
js_closure 函数最终会生成函数闭包对象,这个对象会设置属性。js_closure 还会生成设置变量属性,比如来源和类型,并存在当前函数的 var_refs 里,变量处理使用的是 js_closure2 函数。js_closure 先使用 JS_NewObjectClass 将 func_obj 类型由 js_closure2 函数会遍历闭包变量,通过 get_var_ref 函数来看闭包变量来自哪一层上下文,最后存在当前函数的 JSVarRef 里,JSVarRef 表变量引用。在 js_closure2 函数中,当发现自由变量的 is_local 为 false 也就是变量来自外层,就会直接从 js_closure2 函数传入的上层的参数 cur_var_refs 里取出变量,存在 var_refs 里,当 JS_CallInternal 执行时当遇到 OP_get_var_ref 字节码操作符时函数闭包会直接通过索引从 var_refs 里取出变量并通过 JS_DupValue 将引用加1,同时把引用加到栈顶。OP_put_var_ref 使用 set_value 函数将栈顶值更新到 var_refs 对应索引位置,并 pop 栈顶值。js_function_set_properties 函数设置属性。
创建了闭包环境,接下来执行 JS_Call 函数,JS_Call 函数会逐个执行字节码的指令。JS_Call 会调用 JS_CallInternal 函数,QuickJS 的字节码执行架构是基于栈指令的,JavaScriptCore 是基于寄存器解释执行的,关于 JavaScriptCore 的详细介绍可以参看我以前的文章《深入剖析 JavaScriptCore》。JSStackFrame 是模拟栈,在一个运行时只会有一个模拟栈在使用,generator 通过保存一个还在运行中没结束的栈来达到挂起的目的。
JS_CallInternal 会对字节码的每条指令都进行解释执行。会先为变量、存放数据的栈还有参数等进行内存的分配。然后再对每条字节码指令一个一个进行解释执行,指令类型有 push 开头的入栈指令。goto、if 打头的跳转指令。call 开头的调用指令。交换类的指令有 swap、nip、dup、perm、drop 等。用于计算的指令有加减乘除、一元计算、逻辑计算等。处理指令时生成 JSValue 用的是 JS_MKVAL 和 JS_MKPTR 这两个宏。新建字符串类型用的是 JS_NewString,对象是 JS_NewObject,数组是 JS_NewArray等。类型转换使用的函数有 JS_toString 来转换成字符串类型,JS_ToNumeric 转换成数字,JS_ToObject 转换成对象等。
JSFunctionBytecode结构体定义:
typedef struct JSFunctionBytecode {
JSGCObjectHeader header; /* must come first */
uint8_t js_mode;
uint8_t has_prototype : 1; /* true if a prototype field is necessary */
uint8_t has_simple_parameter_list : 1;
uint8_t is_derived_class_constructor : 1;
/* true if home_object needs to be initialized */
uint8_t need_home_object : 1;
uint8_t func_kind : 2;
uint8_t new_target_allowed : 1;
uint8_t super_call_allowed : 1;
uint8_t super_allowed : 1;
uint8_t arguments_allowed : 1;
uint8_t has_debug : 1;
uint8_t backtrace_barrier : 1; /* stop backtrace on this function */
uint8_t read_only_bytecode : 1;
/* XXX: 4 bits available */
//函数对象自己字节码的指针
uint8_t *byte_code_buf; /* (self pointer) */
int byte_code_len;
JSAtom func_name;
//函数的参数和局部变量(arg_count + var_count) (self pointer)
JSVarDef *vardefs;
JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */
uint16_t arg_count;
uint16_t var_count;
uint16_t defined_arg_count; /* for length function property */
uint16_t stack_size; /* maximum stack size */
JSContext *realm; /* function realm */
JSValue *cpool; /* constant pool (self pointer) */
int cpool_count;
int closure_var_count;
struct {
/* debug info, move to separate structure to save memory? */
JSAtom filename;
int line_num;
int source_len;
int pc2line_len;
uint8_t *pc2line_buf;
char *source;
} debug;
} JSFunctionBytecode;
js函数在运行时的数据结构就是JSFunctionBytecode,创建函数就是初始化JSFunctionBytecode结构体,并设置里面所需的字段,这个过程就是将扫描代码生成的临时 JSFunctionDef 对应到 JSFunctionBytecode 中,由 js_create_function 函数负责处理。
closure_var 是用于存放外部可见的闭包变量,closure_var 通过 add_closure_var 函数进行添加,add_closure_var 函数会把要用到的变量添加成闭包变量,通过 get_closure_var2 函数往上层级递归给每层级函数添加闭包变量,直到找到目标函数;stack_size 是指堆栈的大小,stack_size 主要作用是为初始化栈时能够减少内存占用;cpool 是函数内常量池。