7.7 QuickJS核心代码流程 解释执行 JS_EvalFunctionInternal

函数对象字节码信息结构体是 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;
}

2 解析

解析1

如果 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 函数执行导出的这些全局变量的初始化。

解析2

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 转换成对象等。

3 补充

补充1

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 是函数内常量池。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值