当前位置: 首页 > 工具软件 > QuickJS > 使用案例 >

7.6 QuickJS核心代码流程 js_create_function创建函数对象以及包含的子函数

夏理
2023-12-01

js_create_function 函数会从 JSFunctionDef (2.2)中创建一个函数对象和子函数,然后释放 JSFunctionDef。函数体如下:

/* create a function object from a function definition. The function
   definition is freed. All the child functions are also created. It
   must be done this way to resolve all the variables. */
static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
{
    JSValue func_obj;
    JSFunctionBytecode *b;
    struct list_head *el, *el1;
    int stack_size, scope, idx;
    int function_size, byte_code_offset, cpool_offset;
    int closure_var_offset, vardefs_offset;

    //解析1
    for (scope = 0; scope < fd->scope_count; scope++) {
        fd->scopes[scope].first = -1;
    }
    if (fd->has_parameter_expressions) {
        /* special end of variable list marker for the argument scope */
        fd->scopes[ARG_SCOPE_INDEX].first = ARG_SCOPE_END;
    }
    for (idx = 0; idx < fd->var_count; idx++) {
        JSVarDef *vd = &fd->vars[idx];
        vd->scope_next = fd->scopes[vd->scope_level].first;
        fd->scopes[vd->scope_level].first = idx;
    }
    for (scope = 2; scope < fd->scope_count; scope++) {
        JSVarScope *sd = &fd->scopes[scope];
        if (sd->first < 0)
            sd->first = fd->scopes[sd->parent].first;
    }
    for (idx = 0; idx < fd->var_count; idx++) {
        JSVarDef *vd = &fd->vars[idx];
        if (vd->scope_next < 0 && vd->scope_level > 1) {
            scope = fd->scopes[vd->scope_level].parent;
            vd->scope_next = fd->scopes[scope].first;
        }
    }

    /* if the function contains an eval call, the closure variables
       are used to compile the eval and they must be ordered by scope,
       so it is necessary to create the closure variables before any
       other variable lookup is done. */
    if (fd->has_eval_call)
        add_eval_variables(ctx, fd);

    /* add the module global variables in the closure */
    if (fd->module) {
        if (add_module_variables(ctx, fd))
            goto fail;
    }

    /* first create all the child functions */
    list_for_each_safe(el, el1, &fd->child_list) {
        JSFunctionDef *fd1;
        int cpool_idx;

        fd1 = list_entry(el, JSFunctionDef, link);
        cpool_idx = fd1->parent_cpool_idx;
        func_obj = js_create_function(ctx, fd1);
        if (JS_IsException(func_obj))
            goto fail;
        /* save it in the constant pool */
        assert(cpool_idx >= 0);
        fd->cpool[cpool_idx] = func_obj;
    }

#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4)
    if (!(fd->js_mode & JS_MODE_STRIP)) {
        printf("pass 1\n");
        dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size,
                       fd->args, fd->arg_count, fd->vars, fd->var_count,
                       fd->closure_var, fd->closure_var_count,
                       fd->cpool, fd->cpool_count, fd->source, fd->line_num,
                       fd->label_slots, NULL);
        printf("\n");
    }
#endif

    if (resolve_variables(ctx, fd))
        goto fail;

#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2)
    if (!(fd->js_mode & JS_MODE_STRIP)) {
        printf("pass 2\n");
        dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size,
                       fd->args, fd->arg_count, fd->vars, fd->var_count,
                       fd->closure_var, fd->closure_var_count,
                       fd->cpool, fd->cpool_count, fd->source, fd->line_num,
                       fd->label_slots, NULL);
        printf("\n");
    }
#endif

    if (resolve_labels(ctx, fd))
        goto fail;

    if (compute_stack_size(ctx, fd, &stack_size) < 0)
        goto fail;

    if (fd->js_mode & JS_MODE_STRIP) {
        function_size = offsetof(JSFunctionBytecode, debug);
    } else {
        function_size = sizeof(*b);
    }
    cpool_offset = function_size;
    function_size += fd->cpool_count * sizeof(*fd->cpool);
    vardefs_offset = function_size;
    if (!(fd->js_mode & JS_MODE_STRIP) || fd->has_eval_call) {
        function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs);
    }
    closure_var_offset = function_size;
    function_size += fd->closure_var_count * sizeof(*fd->closure_var);
    byte_code_offset = function_size;
    function_size += fd->byte_code.size;

    b = js_mallocz(ctx, function_size);
    if (!b)
        goto fail;
    b->header.ref_count = 1;

    b->byte_code_buf = (void *)((uint8_t*)b + byte_code_offset);
    b->byte_code_len = fd->byte_code.size;
    memcpy(b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size);
    js_free(ctx, fd->byte_code.buf);
    fd->byte_code.buf = NULL;

    b->func_name = fd->func_name;
    if (fd->arg_count + fd->var_count > 0) {
        if ((fd->js_mode & JS_MODE_STRIP) && !fd->has_eval_call) {
            /* Strip variable definitions not needed at runtime */
            int i;
            for(i = 0; i < fd->var_count; i++) {
                JS_FreeAtom(ctx, fd->vars[i].var_name);
            }
            for(i = 0; i < fd->arg_count; i++) {
                JS_FreeAtom(ctx, fd->args[i].var_name);
            }
            for(i = 0; i < fd->closure_var_count; i++) {
                JS_FreeAtom(ctx, fd->closure_var[i].var_name);
                fd->closure_var[i].var_name = JS_ATOM_NULL;
            }
        } else {
            b->vardefs = (void *)((uint8_t*)b + vardefs_offset);
            memcpy(b->vardefs, fd->args, fd->arg_count * sizeof(fd->args[0]));
            memcpy(b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof(fd->vars[0]));
        }
        b->var_count = fd->var_count;
        b->arg_count = fd->arg_count;
        b->defined_arg_count = fd->defined_arg_count;
        js_free(ctx, fd->args);
        js_free(ctx, fd->vars);
    }
    b->cpool_count = fd->cpool_count;
    if (b->cpool_count) {
        b->cpool = (void *)((uint8_t*)b + cpool_offset);
        memcpy(b->cpool, fd->cpool, b->cpool_count * sizeof(*b->cpool));
    }
    js_free(ctx, fd->cpool);
    fd->cpool = NULL;

    b->stack_size = stack_size;

    if (fd->js_mode & JS_MODE_STRIP) {
        JS_FreeAtom(ctx, fd->filename);
        dbuf_free(&fd->pc2line);    // probably useless
    } else {
        /* XXX: source and pc2line info should be packed at the end of the
           JSFunctionBytecode structure, avoiding allocation overhead
         */
        b->has_debug = 1;
        b->debug.filename = fd->filename;
        b->debug.line_num = fd->line_num;

        //DynBuf pc2line;
        //compute_pc2line_info(fd, &pc2line);
        //js_free(ctx, fd->line_number_slots)
        b->debug.pc2line_buf = js_realloc(ctx, fd->pc2line.buf, fd->pc2line.size);
        if (!b->debug.pc2line_buf)
            b->debug.pc2line_buf = fd->pc2line.buf;
        b->debug.pc2line_len = fd->pc2line.size;
        b->debug.source = fd->source;
        b->debug.source_len = fd->source_len;
    }
    if (fd->scopes != fd->def_scope_array)
        js_free(ctx, fd->scopes);

    b->closure_var_count = fd->closure_var_count;
    if (b->closure_var_count) {
        b->closure_var = (void *)((uint8_t*)b + closure_var_offset);
        memcpy(b->closure_var, fd->closure_var, b->closure_var_count * sizeof(*b->closure_var));
    }
    js_free(ctx, fd->closure_var);
    fd->closure_var = NULL;

    b->has_prototype = fd->has_prototype;
    b->has_simple_parameter_list = fd->has_simple_parameter_list;
    b->js_mode = fd->js_mode;
    b->is_derived_class_constructor = fd->is_derived_class_constructor;
    b->func_kind = fd->func_kind;
    b->need_home_object = (fd->home_object_var_idx >= 0 ||
                           fd->need_home_object);
    b->new_target_allowed = fd->new_target_allowed;
    b->super_call_allowed = fd->super_call_allowed;
    b->super_allowed = fd->super_allowed;
    b->arguments_allowed = fd->arguments_allowed;
    b->backtrace_barrier = fd->backtrace_barrier;
    b->realm = JS_DupContext(ctx);

    add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE);
    
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1)
    if (!(fd->js_mode & JS_MODE_STRIP)) {
        js_dump_function_bytecode(ctx, b);
    }
#endif

    if (fd->parent) {
        /* remove from parent list */
        list_del(&fd->link);
    }

    js_free(ctx, fd);
    return JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b);
 fail:
    js_free_function_def(ctx, fd);
    return JS_EXCEPTION;
}

pass 1

js_create_function 函数还会使用 add_module_variables 函数添加模块闭包中的全局变量。import 其他模块的全局会通过 js_parse_import 函数作为闭包变量添加进来,add_module_variables 函数是添加 module 里的全局变量,将模块的全局变量通过 add_closure_var 函数加到当前函数定义里,并处理类型是 JS_EXPORT_TYPE_LOCAL 的 exports 的变量。add_module_variables 函数会先添加通过 js_parse_import 函数导入作为闭包变量的全局变量,添加闭包变量调用的是 add_closure_var 函数。遍历 fd->child_list 递归使用 js_create_function 函数创建所有子函数,将创建的子函数对象保存在 fd->cpool 里,cpool 是一个 JSValue 结构体,JSValue 是最基本的单位,这个结构体会有一个 tag 来标示 JSValue 的类型,值是保存在 JSValueUnion 里,值可以是整型和浮点,也可以是一个对象的指针,指针指向的对象是由引用计数来进行管理的,引用计数结构体是 JSRefCountHeader。这里 JSValue 的值是个数组。

2 解析

解析1

函数最开始重新计算作用域的关联,通过四步将作用域和变量关联起来,方便在作用域和父作用域中查找变量。

  • 遍历JSFunctionDef中的作用域,设置链表头
  • 遍历变量列表,将变量对应作用域

第二步遍历变量列表,将变量对应作用域层级链表头放到变量的 scope_next 里,并将变量所在列表索引放到变量对应作用域层级链表头里。第三步再遍历作用域,将没有变量的作用域指向父作用域的链表。第四步将当前作用域和父作用域变量链表连起来。通过 fd->has_eval_call 来看是否有调用 eval,如果有,通过 add_eval_variables 来添加 eval 变量。如果函数里有 eval 调用,add_eval_variables 函数会将 eval 里的闭合变量按照作用域排序。add_eval_variables 函数会为 eval 定义一个给参数作用域用的额外的变量对象,还有需定义可能会使用的 arguments,还在参数作用域加个 arguments binding,另外,eval 可以使用 enclosing 函数的所有变量,因此需要都加到闭包里。

 类似资料: