Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JerryScript 源码问答 #535

Open
cisen opened this issue Jun 29, 2019 · 1 comment
Open

JerryScript 源码问答 #535

cisen opened this issue Jun 29, 2019 · 1 comment

Comments

@cisen
Copy link
Owner

cisen commented Jun 29, 2019

总结

  • 编程思想:所有值都要有一个常量名,不会使用0,1这种无意义容易被遗忘含义的数据
  • 所有的数据
  • 一共有三种码:
    • Lexer token types,token的类型,lexer阶段产生
    • CBC Opcode,压缩字节码,lexer之后产生
    • VM_OC,虚拟机执行命令,执行的时候由CBC码转化而来
  • 转化大概是这样子的:根据代码字符(以合法字符和非合法来断词)匹配token.type(就是token),然后根据token.type生成CBC(last_cbc对象和)和literal_pool。然后执行代码的时候再用CBC转成OC操作符。lexer转化为opcode如下面注意第二点。
  • 为什么需要CBC呢?因为跨平台,jvm就是这样的
  • 基本上是以《编译原理》这本书设计的
  • cbc是一个独立的链表,只存储cbc编码。bytecode_header_p只存了CBC的头。cbc和literal的交会主要在vm_run函数。bytecode_header_p就是全局context:jerry_global_heap.first,是vm获取cbc和literal_pool的入口。cbc和literal是存储在同一块栈上的,cbc在底部,literal在上面?
  • 其实就是实现LL(1)文法,然后将token转化为cbc编码和literal_pool.
  • lex阶段和vm阶段关键的数据存储主要时三个对象:byte_code,literal_pool,stack,register三个链表
    • byte_code是vm执行命令
    • literal_pool就是stack?literal_pool仅出现在lex阶段
    • stack就是aa, 123之类的常量列表
    • register_p是执行的时候的缓存,跟cpu寄存器的定义很像,register_p就是stack里面拿出部分来进行运算的。所以其实所有参数名都是存放到这里的
  • 全局变量有解释阶段parser_context, 运行阶段jerry_context,栈帧vm_frame_ctx_t
    • parser_context是parser阶段主要存储区,包括parser时的临时变量和paresr后产出的CBC链表和literal_pool变量链表。
      • cbc链表代表操作符,逻辑运算,literal_pool代表变量存储。比如parser_parse_var_statement先通过lexer_expect_identifier确定变量名,然后再通过parser_parse_expression函数将操作符根据token类型,比如等号插入cbc链表。这里使用上下文无关文法,所以不需要关心作用域
      • 子函数parser时有自己的context:parser_saved_context_t,内部存储子作用域的cbc链表和参数池literal_pool
  • 支持ECM

优化

难点

  • literal数据结构和内存分配,栈和堆如何连接

注意

  • 默认是不支持箭头函数
  • lexer和opcode的转化函数:
//  LEXER_BIT_OR,                  /**< "|" (prec: 7) 32 */
#define LEXER_BINARY_OP_TOKEN_TO_OPCODE(token_type) \
   ((cbc_opcode_t) ((((token_type) - LEXER_BIT_OR) * 3) + CBC_BIT_OR))
  • opcode 是CBC_CODE, 其数值经过VM_OC_GET_ARGS_INDEX变成operands,然后在经过VM_OC_GROUP_GET_INDEX就可以获得VM_OC_CODE
  • VM_OC_CODE = vm_decode_table[CBC_CODE] & 111_1111

问答

lexer

保留关键字如何匹配的?

  • 关键字是按照词的字母数量存储在一个二位数组keyword_strings_list里面的,比如do就是在keyword_strings_list[0]里面,keyword_strings_list[3]就是存储for, var之类的
  • es内部保留的关键字仅包含0-9, a-z, $, _这几个符号,其他的就可以作为断词符号截取
  • 过了上面的筛选就到了单词比较,这里比较只需比较两个字母即可,因为相同字母数量保留词前两个字母绝对不同,所以每个关键字的寻找只要循环两遍,主要在lit_char_is_identifier_start函数

参数名如何确定?

  • lexer_parse_identifier函数,它同时确定关键字和参数名,第二参数是确定寻找关键字还是参数

内存是如何申请的?

  • 这里说的内存是全局的堆jerry_global_heap
  • 分配内存的函数是jmem_heap_alloc_block_internal
  • 基本逻辑就是校验空余空间大小,修改指针和等级申请的大小

内存优先级如何分,有什么用?

  • 这里说的内存是全局的堆jerry_global_heap
  • 优先级目前只有low和height之分,
  • 用途主要是内存不足的的时候,low的空间会被回收给height用,在jmem_run_free_unused_memory_callbacks函数
  • 内存的申请和自动回收都在jmem_heap_gc_and_alloc_block函数

var aa=1;这个parse阶段经历了什么?

  1. 过滤有效字母和符号,找到var,然后设置parser_context.token对象和last_cbc对象,注意byte_code对象本身就是一个链表
  2. 找到变量aa,从jerry_grlbal_heap里面申请一个lexer_lit_object_t大小的空间,然后填充,填充完将地址返回,挂载到parser_context的literal_pool栈中。parser_context.lit_object指向literal_pool栈的最顶上那个。
  3. 上面是生成词,生成词之后,如果是常量(比如变量名)会被放入常量池(literal_pool)里面。然后开始生成CBC链表。先是生成现在token的last_cbc_opcode,生成last_cbc_opcode。token阶段算是完成。然后是通过last_cbc_opcode更新parser_context.byte_code对象,主要在parser_flush_cbc (parser_context_t *context_p) 函数
  4. 所以parser_context.token对象只是一个临时值,真正lexer的结果是literal_pool和CBC链表

byte_code链表的执行入口在vm的哪里?

  • 在vm_loop函数的第二个while循环里面,*byte_code_p++就是读取整个parser_context.byte_code里面的链表,读取出来的CBC_CODE,根据CBC_OPCODE_LIST里面跟OC_CODE的匹配关系,循环变成c语言执行

byte_code链表和literal_pool链表都是存储在jerry_global_heap堆里面的?

  • 是的
  • 控制是通过parser_context的byte_code和literal_pool栈控制的

默认会生成一个全局的函数执行环境?

  • 是的,默认就是生成一个函数,然后执行。在parser函数的最后:
// 返回jerry_global_context.ecma_global_lex_env_p的指针
  ecma_object_t *lex_env_p = ecma_get_global_environment ();
  // 从全局堆申请内存创建一个函数对象,并设置作用域和字节码,数量+1
  ecma_object_t *func_obj_p = ecma_op_create_function_object (lex_env_p,
                                                              bytecode_data_p);

就是生成全局环境的

函数的作用域划分是如何实现的?

  • 就是循环执行vm_run函数,不断生成函数的栈帧,主要在jerry-core\ecma\operations\ecma-function-object.c
  • 作用域链的关键是frame_ctx.prev_context_p指向父级frame字段,其他相关字段有frame_ctx.context_depth当前context的深度

作用域链是如何通过栈帧实现的?变量堆是如何设置实现的?

实现在作用域链寻找变量的代码在哪里?

  • boa引擎是在执行的时候变量作用域链寻找的

箭头函数的栈帧有何不同?

ecma_op_create_arrow_function_object这个函数里面,vm调用这个函数使用的是父的作用域指针,this_binding也是父的

有两种内存方式?

  • 是的,栈和堆,由JERRY_SYSTEM_ALLOCATOR控制,cmake编译的时候就要设置好。默认使用栈存储jerry_global_heap
  • 主要是jmem_heap_free_block函数的最后和jmem_heap_alloc_block_internal的最后
  • 官方封装了一个jmem来管理内存

什么东西存全局heap?

  • byte_code链表
  • literal_pool链表

jmem封装相关

  • jmem只是根据jerry_global_heap的first里面的size和next_offset分配内存大小,然后返回地址
  • 全局的jerry_global_heap可以通过JERRY_HEAP_CONTEXT获取和设置
  • 全局的jerry_context也保存当前heap的部分消息:JERRY_CONTEXT (jmem_heap_allocated_size)jmem_heap_limit, jmem_heap_list_skip_p,jmem_free_8_byte_chunk_p

函数作用域和全局作用域有何不同?

  • 没有不同,全局作用域其实也是一个函数作用域。parser之后,jerry.run函数会使用ecma_get_object_from_value (func_val);构造一个函数对象,然后代码执行都是在这个函数对象里面的

大括号{}和小括号()有何不同?

为什么中括号[]和.号都可以获得对象的属性?而数组不行?

  • 创建数组的oc是VM_OC_PUSH_ARRAY

为什么变量声明比如var aa = 1的变量名被提升了,值aa=1没有被提升?而函数function bb(){}是函数名和内容都被提升了?

为什么要有token, CBC, VM_三种code

  • token是分解语言的通用处理,将一大串字符串转化为各种有意义的变量。不过为什么不直接转化为VM code?而是转化为CBC?
  • CBC是为了做Snapshot的?
  • VM是对虚拟机的抽象,虚拟机跟真实机器一样只能执行汇编或者机器码,VM正是汇编。

不引用的内存自动回收时如何实现的?

// jerry-core\ecma\base\ecma-globals.h
/**
 * Value for increasing or decreasing the object reference counter.
 * 增加或减少对象引用计数器的值。
 */
#define ECMA_OBJECT_REF_ONE (1u << 6)

/**
 * Maximum value of the object reference counter (1023).
 * 最大引用数量
 */
#define ECMA_OBJECT_MAX_REF (0x3ffu << 6)
typedef struct
{
  /** type : 4 bit : ecma_object_type_t or ecma_lexical_environment_type_t
                     depending on ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV
      flags : 2 bit : ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV,
                      ECMA_OBJECT_FLAG_EXTENSIBLE or ECMA_OBJECT_FLAG_NON_CLOSURE
      refs : 10 bit (max 1023) */
  // 2个字节,共16位,4位是对象类型,2位是标志,剩下10位是对象的指针?
 // 这个其实就是该对象被引用的次数,引用数小于ECMA_OBJECT_REF_ONE则会被gc回收
  uint16_t type_flags_refs;
} ecma_object_t;

看上面代码,type_flags_refs其实时对象被引用的次数,ecma_gc_run函数会根据type_flags_refs的值绝对是否释放该对象

加法和赋值的实现流程?

运算符的优先级时如何实现的?

  • 9+5*2可以转化为
    • 到+号时,时result = 9 +rightVal(到5时,判断下一个运算符)
    • rightVal = 5 * 2
    • 关于优先级的实现:LL(1),优先遍历和运算叶子节点

parser_context的literal_pool和stack有何关系?有何不同?

  • literal_pool是存储变量名的,遇到新变量名的时候就会插入
  • stack是存储说有常量的,比如123,‘aa’之类的,是跟随token生成和vm执行调用的

this是如何实现的?

  • this实现的关键是每个函数执行的时候ecma_op_function_call会执行vm_run,然后把this指向的对象作为参数传进去作为执行环境栈帧的this_binding指针
  • 运行的时候VM_OC_GET_THIS_LITERAL,会复制frame_ctx_p.this_binding
  • VM_OC_ASSIGN_PROP_THIS这个操作是设置prototype,比如function aa(){this.aa=22}
  • this会增加一次引用计数
  • ECMA_OBJECT_TYPE_ARROW_FUNCTION

new关键字是如何生成作用域(栈帧)的?

  • new会执行一个函数,执行函数都会创建栈帧(除了箭头函数),然后执行函数,调用opfunc_construct生成一个新对象,设置对象prototype_or_outer_reference_cp(proto)指针指向上一个prototype对象
  • 所以继承的原理就是new的时候创建的对象的prototype_or_outer_reference_cp指针会指向上一个prototype对象,而js的继承是通过Cat.prototype = new Animal();实现的
  • 注意ES5标准13.2.2If Type(result) is Object then return result.如果new 构造函数返回的是一个对象,则获取到的是该对象,而不是new的类对象(包括数组)。具体实现再ecma_op_function_construct函数的/* 9. */
  • 调用:
// new关键字
        case VM_OC_NEW:
        {
             // 声明执行的是VM_EXEC_CONSTRUCT
          frame_ctx_p->call_operation = VM_EXEC_CONSTRUCT;
           // 插入要执行的代码
          frame_ctx_p->byte_code_p = byte_code_start_p;
          frame_ctx_p->stack_top_p = stack_top_p;
          return ECMA_VALUE_UNDEFINED;
        }
  • new的调用栈(先执行VM_OC_NEW,在调用ecma_create_object)
ecma_create_object(ecma_object_t * prototype_object_p, size_t ext_object_size, ecma_object_type_t type) (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/jerry-core/ecma/base/ecma-helpers.c:81)
ecma_op_function_construct(ecma_object_t * func_obj_p, ecma_value_t this_arg_value, const ecma_value_t * arguments_list_p, ecma_length_t arguments_list_len) (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/jerry-core/ecma/operations/ecma-function-object.c:1111)
opfunc_construct(vm_frame_ctx_t * frame_ctx_p) (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/jerry-core/vm/vm.c:659)
vm_execute(vm_frame_ctx_t * frame_ctx_p, const ecma_value_t * arg_p, ecma_length_t arg_list_len) (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/jerry-core/vm/vm.c:3682)
vm_run(const ecma_compiled_code_t * bytecode_header_p, ecma_value_t this_binding_value, ecma_object_t * lex_env_p, uint32_t parse_opts, const ecma_value_t * arg_list_p, ecma_length_t arg_list_len) (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/jerry-core/vm/vm.c:3820)
vm_run_global(const ecma_compiled_code_t * bytecode_p) (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/jerry-core/vm/vm.c:270)
jerry_run(const jerry_value_t func_val) (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/jerry-core/api/jerry.c:573)
main() (/home/cisen/桌面/develop/jerryscript/sourcecode-jerryscript-/main.c:34)

创建栈帧的有几种情况

  • 整个js代码初始化的时候会生成全局栈帧
  • VM_EXEC_CALL->opfunc_call->ecma_op_function_call->vm_run
  • vm_run_eval,vm_run_module(ecma_module_connect_imports,import的时候), vm_run_global(全局,只执行一次)
  • ecma_op_function_call函数里面有两种调用vm_run的情况,分别是ECMA_OBJECT_TYPE_FUNCTION,和ECMA_OBJECT_TYPE_ARROW_FUNCTION

null,NaN和undefined有什么区别?

  • VM_OC_PUSH_NULL,null_token,VM_OC_PUSH_NULL
  • ECMA_VALUE_UNDEFINED
  • LIT_MAGIC_STRING_NAN,ECMA_BUILTIN_NUMBER_NAN

typeof的实现

  • jerry_value_get_type关键函数
/**
 * Get the literal id associated with the given ecma_value type.
 * This operation is equivalent to the JavaScript 'typeof' operator.
 *
 * @returns one of the following value:
 *          - LIT_MAGIC_STRING_UNDEFINED
 *          - LIT_MAGIC_STRING_OBJECT
 *          - LIT_MAGIC_STRING_BOOLEAN
 *          - LIT_MAGIC_STRING_NUMBER
 *          - LIT_MAGIC_STRING_STRING
 *          - LIT_MAGIC_STRING_FUNCTION
 */
lit_magic_string_id_t
ecma_get_typeof_lit_id (ecma_value_t value) /**< input ecma value */
{
  lit_magic_string_id_t ret_value = LIT_MAGIC_STRING__EMPTY;
  // undefined就是返回undefined
  if (ecma_is_value_undefined (value))
  {
    ret_value = LIT_MAGIC_STRING_UNDEFINED;
  }
  // 注意返回LIT_MAGIC_STRING_OBJECT,是对象
  else if (ecma_is_value_null (value))
  {
    ret_value = LIT_MAGIC_STRING_OBJECT;
  }
  else if (ecma_is_value_boolean (value))
  {
    ret_value = LIT_MAGIC_STRING_BOOLEAN;
  }
  else if (ecma_is_value_number (value))
  {
    ret_value = LIT_MAGIC_STRING_NUMBER;
  }
  else if (ecma_is_value_string (value))
  {
    ret_value = LIT_MAGIC_STRING_STRING;
  }
#if ENABLED (JERRY_ES2015_BUILTIN_SYMBOL)
  // es6的symbol
  else if (ecma_is_value_symbol (value))
  {
    ret_value = LIT_MAGIC_STRING_SYMBOL;
  }
#endif /* ENABLED (JERRY_ES2015_BUILTIN_SYMBOL) */
  // 其他全都是返回对象
  else
  {
    JERRY_ASSERT (ecma_is_value_object (value));
    // 如果对象的参数有一个[[Call]]内部方法,IsCallable 是true,其他是false
    // 这两种都是函数:var aa = function(){this.aa=23};typeof(aa); function aa(){this.aa=23};typeof(aa)
    // 对象是哪几种情况?不清楚,反正不是上面的都是对象
    ret_value = ecma_op_is_callable (value) ? LIT_MAGIC_STRING_FUNCTION : LIT_MAGIC_STRING_OBJECT;
  }

  JERRY_ASSERT (ret_value != LIT_MAGIC_STRING__EMPTY);

  return ret_value;
} /* ecma_get_typeof_lit_id */

==和===的运算逻辑

  • 关键函数:jerry_binary_operation。==是JERRY_BIN_OP_EQUAL,===是JERRY_BIN_OP_STRICT_EQUAL

  • ==判断总结

    • 如果是数字:
    • 如果两个值转化为ecma值相等,那就返回true
    • 如果一个是整数,一个不是返回false
    • 任何一方是NaN,直接返回false
  • 如果是字符串:

    • 如果地址相等,返回true
    • 两个字符串的hash值不等,返回false-
    • 使用c语言的memcmp函数比较,长度相等,相同字符位于相同位置
    • 注意两个字符串对象互不相等:new String("a") == "a" 和 "a" == new String("a")皆为 true。 new String("a") == new String("a")为 false。
    • 如果有一个是数字则转化为为数字比较
  • 如果任何一方是bool,则转化为数字(0/1)比较

  • 其他:

    • null跟undefined是相等的,因为会转化为bool来判断.·null == undefine是特殊处理的
  • toString和valueOf取值逻辑

    • 这两个的调用区分主要在ecma_op_general_object_default_value函数
    • 引用数据类型如果是数字(Number)和字符串(String),则使用toString,其余均使用valueOf取值
    • 然后Boolean,Number,Object,String,Symbol对象都有valueOf内置函数的实现代码,function,regexp,typedarray,Array没有
    • 有toString实现的有:Array,Boolean,Error,Function,Number,Object,Regexp,String,TypedArray,Symbol。
    • 只有String默认优先调用toString,其他全是默认使用valueOf
  • ===判断总结:

    • NaN,直接返回false
    • 数字,值相等,返回true
    • 字符串长度相等,相同字符位于相同位置返回true
    • bolean也只比较值
    • 如果 x 和 y 引用到同一个 Object 对象,返回 true,否则,返回 false

为什么null instanceof Object为false

  • 因为null没有prototype对象,也没有__proto__
  • 实际上null也不是继承自Object的。
  • null只是特殊处理了一下,比如typeof就针对null做特殊处理的,指定它返回‘object’

为什么null == undefined为true

为什么false == undefined为false

null是没有prototype和__proto__对象的嘛?

  • .号和[]如何实现的?
  • ecma_find_named_property函数,ecma_get_property_list函数通过property_list_or_bound_object_cp指针获取所有property。

箭头函数是跟父作用域绑一起的,在哪里实现的?

  • ecma_op_create_arrow_function_object这个函数里面,vm调用这个函数使用的是父的作用域指针,this_binding也是父的

this_binding是什么?

  • 指向某个对象的一个指针,默认执行全局环境this_binding = ecma_make_object_value (global_obj_p);

super函数做了什么?

  • 没有继承自父,那就不用调super
  • 没有super,默认是不会调用constructor的,super跟new有点像?super相当于完成一次实例化?
  • 调用super函数时执行vm_super_call
  • super是在class里面的,会调用ecma_op_function_construct执行[[Construct]]
  • 会用ecma_op_get_class_this_binding创建this_binding,然后使用ecma_op_set_class_this_binding绑定this_bingding
  • 使用ecma_op_set_class_prototype获取的prototype,然后修改到新类的属性[[Prototype]]上

如何创建属性prop的?

  • ecma_create_property

函数对象的[[Get]], [[Call]], [[Construct]], [[HasInstance]]的执行位置?

  • [[Construct]]在ecma_op_function_construct
  • [[Call]]在ecma_op_function_call

函数内没有使用var定义变量,该变量就被定义为全局变量是在哪里定义的?

promise的then方法为什么是异步的?但又比setTimeout快?

  • 因为promise内部的函数时同步执行的,在vm执行到then的时候,resolve和reject函数并不会被马上调用,而是插入job_queue_head_p堆的队列中。等执行完jerry_run之后,再调用jerry_run_all_enqueued_jobs执行队列代码。断点关键函数:ecma_promise_do_then
  • 详细见:ES6标准:25.4.5.3.1

promise是如何实现的?

  • ecma_op_create_promise_object
ecma_promise_do_then(ecma_value_t promise, ecma_value_t on_fulfilled, ecma_value_t on_rejected, ecma_value_t result_capability) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/ecma/operations/ecma-promise-object.c:667)
ecma_promise_then(ecma_value_t promise, ecma_value_t on_fulfilled, ecma_value_t on_rejected) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/ecma/operations/ecma-promise-object.c:771)
ecma_builtin_promise_prototype_then(ecma_value_t this_arg, ecma_value_t on_fulfilled, ecma_value_t on_rejected) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.c:51)
ecma_builtin_promise_prototype_dispatch_routine(uint16_t builtin_routine_id, ecma_value_t this_arg_value, const ecma_value_t * arguments_list, ecma_length_t arguments_number) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.inc.h:38)
ecma_builtin_dispatch_routine(ecma_builtin_id_t builtin_object_id, uint16_t builtin_routine_id, ecma_value_t this_arg_value, const ecma_value_t * arguments_list_p, ecma_length_t arguments_list_len) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/ecma/builtin-objects/ecma-builtins.c:1020)
ecma_builtin_dispatch_call(ecma_object_t * obj_p, ecma_value_t this_arg_value, const ecma_value_t * arguments_list_p, ecma_length_t arguments_list_len) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/ecma/builtin-objects/ecma-builtins.c:1045)
ecma_op_function_call(ecma_object_t * func_obj_p, ecma_value_t this_arg_value, const ecma_value_t * arguments_list_p, ecma_length_t arguments_list_len) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/ecma/operations/ecma-function-object.c:731)
opfunc_call(vm_frame_ctx_t * frame_ctx_p) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/vm/vm.c:571)
vm_execute(vm_frame_ctx_t * frame_ctx_p, const ecma_value_t * arg_p, ecma_length_t arg_list_len) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/vm/vm.c:3663)
vm_run(const ecma_compiled_code_t * bytecode_header_p, ecma_value_t this_binding_value, ecma_object_t * lex_env_p, uint32_t parse_opts, const ecma_value_t * arg_list_p, ecma_length_t arg_list_len) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/vm/vm.c:3814)
vm_run_global(const ecma_compiled_code_t * bytecode_p) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/vm/vm.c:270)
jerry_run(const jerry_value_t func_val) (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/jerry-core/api/jerry.c:573)
main() (/home/cisen/桌面/develop/jerryscript/demo-jerryscript/main.c:30)

对象在里面是如何存储的和实现的?

super函数做了什么?

  • 关键是vm_super_call函数
  • 寻找和执行父constructor,创建子property,并把this_binding指向子lex_env_p

箭头函数有何特别

  • 函数对象在ecma_op_create_arrow_function_object里面创建
  • 函数执行在ecma_op_function_callECMA_OBJECT_TYPE_ARROW_FUNCTION调用vm.run生成新栈帧执行

对象的本质时什么?如何存储的

闭包的本质是prev_context_p指针?

prototype本质是什么?

  • 本质是一个基础对象,详见ecma_create_object 函数
  • 每次实例化一个对象,该对象都会有一个prototype_or_outer_reference_cp(proto)的指针指向prototype对象
  • this是指向对象结构本身的,所以会优先寻找this的属性,然后再去找prototype对象的属性
  • prototype链表(原型链)是通过proto对象(跟别的对象一样)的property_list_or_bound_object_cp指针指向父原型对象
  • 创建原型对象的函数是ecma_create_property,主要就是连接到原型链上

内置对象如Date,Boolean,Number,String的内置prototype有何不同?

  • 详见ecma_instantiate_builtin函数

valueOf和toString函数有何不同

  • 这两个的调用区分主要在ecma_op_general_object_default_value函数
  • 引用数据类型如果是数字(Number)和字符串(String),则使用toString,其余均使用valueOf取值
  • 然后Boolean,Number,Object,String,Symbol对象都有valueOf内置函数的实现代码,function,regexp,typedarray,Array没有
  • 有toString实现的有:Array,Boolean,Error,Function,Number,Object,Regexp,String,TypedArray,Symbol。
  • 只有String默认优先调用toString,其他全是默认使用valueOf

var,let,const有何不同

instanseof做了什么?

  • VM_OC_INSTANCEOF,JERRY_BIN_OP_INSTANCEOF
  • opfunc_instanceof函数和ecma_op_function_has_instance(关键)
  • 其实就是不断向上遍历prototype_or_outer_reference_cpproto)指针指向的prototype对象,判断这个指针跟向上找到的prototype对象指针是否一样
  • 所以,其实就是遍历prototype原型链,看指针是否一样

加减乘除遇到字符串,数组,对象的时候会怎么处理?

  • 加减乘都会如果左右都是整数直接转为c操作,有一方为浮点,一方为整数,转化整数操作。
  • 除法直接调c的除法,错误返回
  • 加法比较复杂,操作码是VM_OC_ADD
    • 先判断两个是否都是整数或者浮点数,如果是则加起来
    • 如果不都是浮点数或者整数,则调用opfunc_addition
  • 加法,opfunc_addition的处理逻辑是
    • 如果左值是对象,则根据对象的[DefaultValue]获取值,如果值是错误则直接返回错误值
    • 如果右值是对象,根据对象的[DefaultValue]获取值,如果值是错误则直接释放左值后返回错误值
    • 如果左值或者右值筛选对象后有一个是字符串,则转化为字符串连接起来。如果有错误则直接返回
    • 如果不是对象也不是字符串,则直接转化为数字相加,非法数字则转化为NaN
    • + 'b'返回的是NaN
  • 减法,乘法一样会判断两个值是否都是整数或浮点数,然后调用do_number_arithmetic,除法直接调用do_number_arithmetic
  • do_number_arithmetic,其实就是只是用c的加减乘除操作左右值,错误则返回。所以只有+号是比较复杂的
  • NaN来自C语言自带

number的转化规则:

undefined NaN
null 0
true 和 false 1 and 0
string 去掉首尾空格后的纯数字字符串中含有的数字。如果去掉空格后的字符串只由空格字符组成,返回 0。如果字符串不是纯数字,则返回 NaN。

undefined和null有何不同?

  • 除了ECMA_VALUE不同,好像undefined跟null都差不多
  • null有LEXER_LIT_NULL,undefined没有lexer token
  • null还有ECMA_NULL_POINTER指针,undefined没有。NULL在c语言里面有语义,undefined没有
  • null也来自c语言,值见下面,判断的函数是ecma_is_value_null
  • undefined是ES特有的值,是ECMA_VALUE_UNDEFINED = ECMA_MAKE_VALUE (4),其他内置的值有:
/**
 * Simple ecma values
 */
enum
{
  /**
   * Empty value is implementation defined value, used for representing:
   *   - empty (uninitialized) values
   *   - immutable binding values
   *   - special register or stack values for vm
   * 空值是实现定义值,用于表示:
    *  - 空(未初始化)值
    *  - 不可变的绑定值
    *  -  vm的特殊寄存器或堆栈值
   */
  ECMA_VALUE_EMPTY = ECMA_MAKE_VALUE (0), /**< uninitialized value 8=1000  */
  ECMA_VALUE_ERROR = ECMA_MAKE_VALUE (1), /**< an error is currently thrown 24=11000 */
  ECMA_VALUE_FALSE = ECMA_MAKE_VALUE (2), /**< boolean false 40=101000 */
  ECMA_VALUE_TRUE = ECMA_MAKE_VALUE (3), /**< boolean true 56=111000 */
  ECMA_VALUE_UNDEFINED = ECMA_MAKE_VALUE (4), /**< undefined value 72=1001000 */
  ECMA_VALUE_NULL = ECMA_MAKE_VALUE (5), /**< null value 88=1011000*/
  ECMA_VALUE_ARRAY_HOLE = ECMA_MAKE_VALUE (6), /**< array hole, used for
                                                *   initialization of an array literal 104=1101000 */
  ECMA_VALUE_NOT_FOUND = ECMA_MAKE_VALUE (7), /**< a special value returned by
                                               *   ecma_op_object_find 120=1111000 */
  ECMA_VALUE_REGISTER_REF = ECMA_MAKE_VALUE (8), /**< register reference,
                                                  *   a special "base" value for vm 136=10001000 */
  ECMA_VALUE_IMPLICIT_CONSTRUCTOR = ECMA_MAKE_VALUE (9), /**< special value for bound class constructors 152=10011000 */
};

CBC编码是在哪里生成?被存储到哪里的?

  • parser_process_binary_opcodes函数,会把token转化为CBC,然后使用parser_emit_cbc函数中的parser_flush_cbc使用parser_emit_two_bytes类似的把CBC压入全局context的byte_code链表中
  • parser_emit_cbc是token转化为CBC的直接接口,在parser_parse_unary_expression函数里面遇到null,false,true等直接使用该函数生成token

如何改变this?

  • this只能在执行的时候修改,那就用apply和call
  • 底层实现其实就是通过将this指针传给vm.run函数调用就可以了

call和apply做了什么?如何实现绑定this的?

  • 其实没做什么,基本就是过滤好传入的参数,然后使用要绑定的this的对象作为lex_env,然后执行环境
  • 实现是在ecma_builtin_function_prototype_object_bindecma_builtin_function_prototype_object_call
  • 其实就是将第一个参数作为执行域传入所有函数通用的执行方法ecma_op_function_call里面
  • apply比call复杂点,因为它的数组参数处理麻烦点

js如何实现继承的?

  • 所以继承的原理就是new的时候创建的对象的prototype_or_outer_reference_cp指针会指向上一个prototype对象,而js的继承是通过Cat.prototype = new Animal();实现的

__proto__是什么?

  • JerryScript好像是没有实现__proto__的,ES5.1标准内页没有该指针
  • 应该是chrome环境给prototype对象的指针(constructor也是),指向继承对象的prototype对象
  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto - MDN上面说该属性已经被遗弃,不要再使用。
  • 所以默认prototype只有constructor指针
  • 目前推荐使用:Object.prototype.isPrototypeOf(), Object.getPrototypeOf(), Object.setPrototypeOf()
  • 实例化对象是没有prototype对象的,只有函数有。实例化对象可以通过instanceof获取实例化的类

constructor 构造函数是什么?

为什么typeof null是object,undefined是undefined

  • 写死的
/**
 * Get the literal id associated with the given ecma_value type.
 * This operation is equivalent to the JavaScript 'typeof' operator.
 *
 * @returns one of the following value:
 *          - LIT_MAGIC_STRING_UNDEFINED
 *          - LIT_MAGIC_STRING_OBJECT
 *          - LIT_MAGIC_STRING_BOOLEAN
 *          - LIT_MAGIC_STRING_NUMBER
 *          - LIT_MAGIC_STRING_STRING
 *          - LIT_MAGIC_STRING_FUNCTION
 */
lit_magic_string_id_t
ecma_get_typeof_lit_id (ecma_value_t value) /**< input ecma value */
{
  lit_magic_string_id_t ret_value = LIT_MAGIC_STRING__EMPTY;
  // undefined就是返回undefined
  if (ecma_is_value_undefined (value))
  {
    ret_value = LIT_MAGIC_STRING_UNDEFINED;
  }
  // 注意返回LIT_MAGIC_STRING_OBJECT,是对象
  else if (ecma_is_value_null (value))
  {
    ret_value = LIT_MAGIC_STRING_OBJECT;
  }
  else if (ecma_is_value_boolean (value))
  {
    ret_value = LIT_MAGIC_STRING_BOOLEAN;
  }
  else if (ecma_is_value_number (value))
  {
    ret_value = LIT_MAGIC_STRING_NUMBER;
  }
  else if (ecma_is_value_string (value))
  {
    ret_value = LIT_MAGIC_STRING_STRING;
  }
#if ENABLED (JERRY_ES2015_BUILTIN_SYMBOL)
  // es6的symbol
  else if (ecma_is_value_symbol (value))
  {
    ret_value = LIT_MAGIC_STRING_SYMBOL;
  }
#endif /* ENABLED (JERRY_ES2015_BUILTIN_SYMBOL) */
  // 其他全都是返回对象
  else
  {
    JERRY_ASSERT (ecma_is_value_object (value));
    // 如果对象的参数有一个[[Call]]内部方法,IsCallable 是true,其他是false
    // 这两种都是函数:var aa = function(){this.aa=23};typeof(aa); function aa(){this.aa=23};typeof(aa)
    // 对象是哪几种情况?不清楚,反正不是上面的都是对象
    ret_value = ecma_op_is_callable (value) ? LIT_MAGIC_STRING_FUNCTION : LIT_MAGIC_STRING_OBJECT;
  }

  JERRY_ASSERT (ret_value != LIT_MAGIC_STRING__EMPTY);

  return ret_value;
} /* ecma_get_typeof_lit_id */

各种函数结构

// 正常函数
struct
    {
      ecma_value_t scope_cp; /**< function scope 函数的作用域 */
      ecma_value_t bytecode_cp; /**< function byte code 函数的字节码 */
    } function;
/**
     * Description of bound function object.
     * 绑定函数对象的描述。
     */
    struct
    {
      ecma_value_t target_function; /**< target function 目标函数 */
      ecma_value_t args_len_or_this; /**< length of arguments or this value 这个值参数的数量 */
    } bound_function;
/**
 * Description of arrow function objects.
 * 箭头函数对象的描述
 */
typedef struct
{
  ecma_object_t object; /**< object header */
  ecma_value_t this_binding; /**< value of 'this' binding this绑定的地方 */
  jmem_cpointer_t scope_cp; /**< function scope 函数作用域 */
  jmem_cpointer_t bytecode_cp; /**< function byte code 函数字节码 */
} ecma_arrow_function_t;

词法环境(Lexical Environments)是什么?

  • 词法环境是一个用于定义特定变量和函数标识符在ECMAScript代码的词法嵌套结构上关联关系的规范类型。一个词法环境由一个环境记录项和可能为空的外部词法环境引用构成。 https://segmentfault.com/a/1190000006719728

变量名存储在哪里了?

  • 关键在lexer_process_char_literallexer_process_char_literal
    • 首先变量的数据存储(lexer_literal_t)和变量池(literal_pool)是分开的,变量池是一个双向链表,存储着变量对象的地址
    • 创建变量对象时,先使用parser_list_append函数分配一个节点内存,然后再在lexer_process_char_literal函数里面插入变量数据
  • 注意的是:变量名并不会直接保存,只是在(lexer_literal_t)中保存了它首字母的位置和变量名的长度,在根据位置和长度确定变量名
/**
 * Literal data.
 * 常量数据结构,单个token的数据结构
 * 6+2+1+1 = 10或者12个字节.linux默认对齐数是8,所以体积是16字节
 */
typedef struct
{
  // 6个字节
  union
  {
    ecma_value_t value;                  /**< literal value (not processed by the parser) 常量值(未由解析器处理) 4个字节 */
    const uint8_t *char_p;               /**< character value 字母值 1个字节,变量名字符串开始的弟子 */
    ecma_compiled_code_t *bytecode_p;    /**< compiled function or regexp pointer 编译函数或正则表达式的指针 6字节 */
    uint32_t source_data;                /**< encoded source literal 编码的源常量 4个字节 */
  } u;

// 2个字节或者4个字节
#ifdef PARSER_DUMP_BYTE_CODE
  struct
#else /* !PARSER_DUMP_BYTE_CODE */
  union
#endif /* PARSER_DUMP_BYTE_CODE */
  {
    prop_length_t length;                /**< length of ident / string literal 关键字的长度 2个字节,变量名字符窜的长度,再根据char_p地址确定变量名 */
    uint16_t index;                      /**< real index during post processing 在提交过程中真实的index 2个字节 */
  } prop;

  uint8_t type;                          /**< type of the literal 常量的类型 1个字节 */
  uint8_t status_flags;                  /**< status flags 状态标志 1个字节 */
} lexer_literal_t;

如何搜索有没有某一个变量呢?

  • lexer_process_char_literal函数内通过while循环不断使用parser_list_iterator_next遍历literal_pool变量池,比较类型,变量指针位置,变量名长度来判断是否有没有定义了该变量

变量池literal_pool和cbc链表有何关系?

  • 基本没关系,cbc链表使用上下文无关文法,所以不需要关心作用域。
  • parser_parse_var_statement先通过lexer_expect_identifier确定变量名,然后再通过parser_parse_expression函数将操作符根据token类型,比如等号插入cbc链表。这里使用上下文无关文法,所以不需要关心作用域
  • literal_pool跟cbc没说明关系?

如何确定该变量是哪个作用域的?

  • 确定变量是否有定义,在哪里定义是parser阶段就确定的。如果使用了为定义参数会在parser阶段就报错
  • 子函数parser时有自己的context作用域:parser_saved_context_t,内部存储子作用域的cbc链表和参数池literal_pool。通过这样将变量名分开,子作用域通过prev_context_p连接到父context

ecma_compiled_code_t对应的值是什么?跟cbc和literal_pool有什么关系?

  • ecma_compiled_code_t只是用来描述块代码的CBC属性的,存储在块代码所属的变量名中的
  • 全局的函数根本不需要全局地址,因为全局函数肯定是内存第一个,它只需要使用ecma_compiled_code_t来描述编译后的CBC结果就好
  • 编译后的代码使用parser_malloc分配global_heap的内存,并返回地址给ecma_compiled_code_t。具体见parser_post_processing函数的compiled_code_p = (ecma_compiled_code_t *) parser_malloc (context_p, total_size);
  • ecma_compiled_code_t是块代码转为用cbc描述,被存储到literal中
  • 所有的parse_context都是被存储到literal中的

parser父context和子context的关系?深层嵌套和兄弟context如何联系在一起?

  • parser的context只会在parser的时候临时创建,parser完这个函数就会删除该函数的context
  • 首先并不会出现同时parser兄弟函数的情况,也就是parser最多只会出现父子context深层嵌套关系,不会出现多兄弟context情况
  • 只有父子关系,那就好办了。通过prev_context_p和last_context_p就创建好了context链表,实现父子关系,具体见parser_save_context
  • parser后context存储到变量里面

parer_context的cbc链表和literal_pool链表是如何存储到global_heap里面的?

  • literal_pool每个变量初始化的时候先通过parser_malloc从global_heap申请内存,再初始化。所以,变量池早就已经再global_heap里面了。比如:parser_list_append
  • cbc链表也是,比如:parser_emit_two_bytes就使用了parser_cbc_stream_alloc_page申请global_heap内存

parser之后,所有的结果比如cbc链表和变量池被保存到了哪里?

  • parse之后只有一个ecma_compiled_code_t,这个会被存储到变量(就是literal_pool的一个literal)中。最后只返回一个全局的根函数(整个js就一个函数),其他的context都可以通过在存储在父变量词中的函数名获得

stack是什么?

void parser_stack_init (parser_context_t *context_p);
void parser_stack_free (parser_context_t *context_p);
void parser_stack_push_uint8 (parser_context_t *context_p, uint8_t uint8_value);
void parser_stack_pop_uint8 (parser_context_t *context_p);
void parser_stack_push_uint16 (parser_context_t *context_p, uint16_t uint16_value);
uint16_t parser_stack_pop_uint16 (parser_context_t *context_p);
void parser_stack_push (parser_context_t *context_p, const void *data_p, uint32_t length);
void parser_stack_pop (parser_context_t *context_p, void *data_p, uint32_t length);
void parser_stack_iterator_skip (parser_stack_iterator_t *iterator, size_t length);
void parser_stack_iterator_read (parser_stack_iterator_t *iterator, void *data_p, size_t length);
void parser_stack_iterator_write (parser_stack_iterator_t *iterator, const void *data_p, size_t length);

为什么需要parser_restore_contextparser_save_context

arguments对象时什么?

  • 创建arguments对象的是ecma_op_create_arguments_object函数,具体执行逻辑:
      1. 先获取prototype对象,然后在prototype对象里面创建arguments对象
      1. 存储函数形参列表
      1. 创建根据是否严格模式创建collee,并指向执行环境
      1. 根据是否严格模式给执行环境创建argument参数
    • 所以,总体流程只是创建对象,存储实参,创建collee,给执行环境添加argument变量
  • ECMA标准里面是这么说的
    • 有内部属性[[Class]]为“Arguments”,有内置属性[[Prototype]]对象
  • argument是典型的内置扩展对象,跟prototype对象一样

iterator是什么?数组继承自Iterator?还有什么继承自Iterator

ES自带的bind函数做了什么?

  • 实现在ecma_builtin_function_prototype_object_bind函数
  • 创建一个prototype对象
  • 利用创建的prototype对象,创建一个新的空函数
  • 修改新函数的ext_function_p->u.bound_function.target_function指向就函数地址
  • 修改新函数this指针ext_function_p->u.bound_function.args_len_or_this指向目标对象

ES自带的bind跟call/apply有何不同?

  • bind需要自己创建一个新的函数,call/apply不用
  • 其实区别还是很大的,call/apply是在参数上旧指明执行环境的this指针,apply是创建一个新的函数再绑定this指针到新对象。麻烦一点

为什么[].slice.call()可以分解类数组之类的非数组?

  • 注意,slice函数比较特别,好像数组,typeArray,string都有该函数方法
  • slice实现在ecma_builtin_array_prototype_object_slice
  • 执行逻辑大概是
    • 找到要截取的开始位置start和结束位置end
    • 创建新的数组
    • 循环遍历start和end之间,取出数组旧值,然后放入新数组中,这里要说一点,数组的取值跟对象的取值使用的是同一个函数ecma_op_object_find,所以函数的取值跟对象的取值基本是一样的。虽然·ecma_op_object_find底层会根据具体对象类型来取值
  • 所以其实旧只是依赖len来确定开始和结束位置

函数取值跟对象的取值有何不同?一个是.一个是[]

  • 其实都是调用同样的函数ecma_op_object_find, 只是底层实现会有一点点不同。主要是在ecma_op_object_find_own函数的ECMA_OBJECT_TYPE_CLASS

为什么使用对象池可以大幅度提升性能?

  • 比如event池:继承研究 #14
  • 根据jerryscript的规则,每次创建对象(申请内存)都要遍历一次所有对象的链表,所以就会很耗性能。但是如果不申请内存,就不会执行GC,性能就会大幅提升

indexOf是如何比较是否是其中一员的?

  • 比如[].indexOf.call(ul, li)这种寻找如何实现的?
  • 代码实现在
    • 字符串:ecma_builtin_string_prototype_object_index_of
    • 数组:ecma_builtin_array_prototype_object_index_of
  • 数组indexOf的实现
    • 遍历对象,然后使用ecma_op_strict_equality_compare函数比较列表的item和要比较的目标
    • ecma_op_strict_equality_compare就是===判断,对象的话直接比较内存指针。
    • 所以li标签可以使用indexOf找到在ul的位置

for in/for of实现有何不同?

  • for in的实现在opfunc_for_in函数,for of的实现不同,没有实现函数只是在vm.c文件中通过VM_OC_FOR_OF_CREATE_CONTEXT多种操作码实现
  • for-in跟for-of一样通过多个VM操作码实现的,比如:VM_OC_FOR_IN_CREATE_CONTEXT, VM_OC_FOR_IN_GET_NEXT, VM_OC_FOR_IN_HAS_NEXT
  • for-of通过ecma_op_iterator_value获取iterator的值和ecma_op_iterator_stepecma_op_get_iterator
  • for-of是通过遍历iterator实现的。for-in是通过遍历内存实现,主要实现都是在vm虚拟机
  • opfunc_for_in逻辑说明
    • 会读取属性和prototype对象的值

Array.prototype.reduce为什么可以配合promise实现顺序调用?为什么reduce的部分执行可以在微任务里面?

  • ecma_builtin_typedarray_prototype_reduce调用ecma_builtin_typedarray_prototype_reduce_with_direction
  • ecma_builtin_typedarray_prototype_reduce_with_direction的逻辑是
    • 先判断两个参数的合法性(一个回调函数,一个初始值)
    • 开始死循环,设置初始index为0
    • 通过ecma_op_typedarray_get_index_prop循环获取数组的值
    • 通过ecma_op_function_call调用函数获取返回的计算结果,并作为toltal或者accumulator,用于下次的ecma_op_function_call调用
  • 总结就是reduce跟其他普通函数一样使用ecma_op_function_call调用promise时,会把任务插入微队列里面,然后再循环执行

ESM ( ECMAScript Module) 原理和流程?

  • ES6 Module/import/export/require 模块源码和使用相关,以及动态导入import() #633

  • 关键字:JERRY_ES2015_MODULE_SYSTEM, 关键文件jerry-core\ecma\base\ecma-module.cjerry-core\parser\js\js-parser-module.c,执行模块文件:vm_run->ecma_module_connect_imports->ecma_module_evaluate->vm_run_module,

  • 对于ES模块,这个过程有三个步骤。

    • 构造-查找、下载和解析所有文件到Module Records, 生成以文件路径为key的module map
    • 实例化-在内存中查找位置以将所有export的values放入当前作用域(但不填充值)。然后使导出和导入都指向内存中的这些位置。这叫做链接。
    • 运行-运行代码以填充变量的实际值。
      68747470733a2f2f327234733970317969316661326a64376a34337a706838722d7770656e67696e652e6e6574646e612d73736c2e636f6d2f66696c65732f323031382f30332f30375f335f7068617365732d353030783138342e706e67
  • 使用parser_module_add_names_to_node函数将模块名构造一个module record添加到module map

  • 在parse阶段,import/export都会将模块名添加到以文件路径为key的module map上 (登记模块名)

  • vm_run执行字节码前,连接module map模块到各个模块的执行域。这里会采用深度优先后序遍历

  • 继续深度优先后续遍历执行依赖模块代码,执行完之后再执行当前文件名字节码。注意这里只运行导出的模块,不导出的不会运行。因为它时根据变量池的变量来取导出作用域执行。没获取到的模块时不会运行的。但其父作用域还是会被执行

ECM 第二步connect做了什么?

  • ES6 Module/import/export/require 模块源码和使用相关,以及动态导入import() #633
  • ecma_module_connect_imports函数
  • vm_run执行字节码前,根据导入名称遍历所有已经parse的export的作用域和其变量池,直到找到对应的导出模块并返回
  • 注意这里只运行导出的模块,不导出的不会运行。因为它时根据变量池的变量来取导出作用域执行。没获取到的模块时不会运行的。但是因为导出函数会依赖外部变量,所以导出变量的父作用域还是会被执行

如何保证每个module只执行一次?

  • 使用以文件路径为key的module map

new A()是先执行()还是new?为什么

为什么自动执行函数访问不到外部的变量?

var name = 'World!';
(function () {
  if (typeof name === 'undefined') {
    var name = 'Jack';
    console.log('Goodbye ' + name);
  } else {
    console.log('Hello ' + name);
  }
})();

其他发现

变量提升的原因

  • lexer阶段就把所有变量放到literal_pool里面,然后生成CBC,执行vm的时候直接就能从pool里面找到变量名
@cisen
Copy link
Owner Author

cisen commented Jul 24, 2019

对象是什么?

  • 所有对象都有type_flags_refs记录被引用的次数
  • 说有对象创建的时候都会执行ecma_init_gc_info函数,将新对象插入全局带回收链表JERRY_CONTEXT (ecma_gc_objects_p),gc回收就是遍历ecma_gc_objects_p链表看看那个对象的引用为0
  • 执行ecma_gc_run函数回收对象内存的情况有:
    • 手动调用对外apijerry_gc回收
    • 注册ecma_free_unused_memory函数到全局的context,jmem_free_unused_memory_callback,每次分配内存的时候(jmem_heap_gc_and_alloc_block),判断总共内存是否超出限制,如果超出则执行回收没用内存,也会根据优先级来回收
    • 手动调用对外apijerry_cleanup也可以触发ecma_finalize回收内存,ecma_finalize这个函数不单单清理内存还清理很多缓存
/**
 * Description of ECMA-object or lexical environment
 * (depending on is_lexical_environment).
 * 
 * ECMA对象或词汇环境的描述
  (取决于is_lexical_environment)。
 */
typedef struct
{
  /** type : 4 bit : ecma_object_type_t or ecma_lexical_environment_type_t
                     depending on ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV
      flags : 2 bit : ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV,
                      ECMA_OBJECT_FLAG_EXTENSIBLE or ECMA_OBJECT_FLAG_NON_CLOSURE
      refs : 10 bit (max 1023) */
  // 2个字节,共16位,4位是对象类型,2位是标志,剩下10位是对象的指针?
  // 这个其实就是该对象被引用的次数,引用数小于ECMA_OBJECT_REF_ONE则会被gc回收
  uint16_t type_flags_refs;

  /** next in the object chain maintained by the garbage collector 接下来在垃圾收集器维护的对象链中 */
  // uint16_t 2字节
  jmem_cpointer_t gc_next_cp;

  /** compressed pointer to property list or bound object 压缩指向属性列表或绑定对象的指针 */
  jmem_cpointer_t property_list_or_bound_object_cp;

  /** object prototype or outer reference 对象原型或外部引用 */
  jmem_cpointer_t prototype_or_outer_reference_cp;
} ecma_object_t;

/**
 * Description of built-in properties of an object.
 */
typedef struct
{
  uint8_t id; /**< built-in id */
  uint8_t length_and_bitset_size; /**< length for built-in functions and
                                   *   bit set size for all built-ins */
  uint16_t routine_id; /**< routine id for built-in functions */
  uint32_t instantiated_bitset[1]; /**< bit set for instantiated properties */
} ecma_built_in_props_t;

/**
 * Start position of bit set size in length_and_bitset_size field.
 */
#define ECMA_BUILT_IN_BITSET_SHIFT 5

/**
 * Description of extended ECMA-object.
 * 扩展ECMA对象的描述。
 *
 * The extended object is an object with extra fields.
 * 扩展对象是具有额外字段的对象。
 */
typedef struct
{
  ecma_object_t object; /**< object header 对象头部*/

  /**
   * Description of extra fields. These extra fields depend on the object type.
   * 其他字段的描述。这些字段有对象类型决定
   */
  union
  {
    ecma_built_in_props_t built_in; /**< built-in object part 内置对象部分 */

    /**
     * Description of objects with class.
     * class的对象描述
     */
    struct
    {
      uint16_t class_id; /**< class id of the object 对象的class id */
      uint16_t extra_info; /**< extra information for the object
                            *   e.g. array buffer type info (external/internal)  对象的额外信息,例如 数组缓冲区类型信息(外部/内部)*/

      /**
       * Description of extra fields. These extra fields depend on the class_id.
       * 其他字段。这些字段由class_id决定
       */
      union
      {
        ecma_value_t value; /**< value of the object (e.g. boolean, number, string, etc.) 对象的值(比如 boolean, number, string) */
        uint32_t length; /**< length related property (e.g. length of ArrayBuffer)  长度相关属性(例如ArrayBuffer的长度) */
      } u;
    } class_prop;

    /**
     * Description of function objects.
     * 函数对象的描述
     */
    struct
    {
      ecma_value_t scope_cp; /**< function scope 函数的作用域 */
      ecma_value_t bytecode_cp; /**< function byte code 函数的字节码 */
    } function;

    /**
     * Description of array objects.
     * 数组对象的描述
     */
    struct
    {
      uint32_t length; /**< length property value 值的长度 */
      ecma_property_t length_prop; /**< length property 长度属性 */
    } array;

    /**
     * Description of pseudo array objects.
     * 伪数组对象的描述。 Arguments, TypedArray, ArrayIterator
     */
    struct
    {
      uint8_t type; /**< pseudo array type, e.g. Arguments, TypedArray, ArrayIterator  伪数组类型,比如 Arguments, TypedArray, ArrayIterator */
      uint8_t extra_info; /**< extra information about the object. 这种对象的其他信息
                           *   e.g. element_width_shift for typed arrays, 比如element_width_shift用于类型化数组
                           *        [[IterationKind]] property for %Iterator% */
      union
      {
        uint16_t length; /**< for arguments: length of names 给arguments,名字的长度 */
        uint16_t class_id; /**< for typedarray: the specific class name  给typedarray,具体的类名称*/
        uint16_t iterator_index; /**< for %Iterator%: [[%Iterator%NextIndex]] property 给Iterator,[[%Iterator%NextIndex]] */
      } u1;
      union
      {
        ecma_value_t lex_env_cp; /**< for arguments: lexical environment 给arguments, lexical的环境 */
        ecma_value_t arraybuffer; /**< for typedarray: internal arraybuffer 给typedarray, 内部arraybuffer */
        ecma_value_t iterated_value; /**< for %Iterator%: [[IteratedObject]] property 给Iterator,[[%Iterator%NextIndex]] */
      } u2;
    } pseudo_array;

    /**
     * Description of bound function object.
     * 绑定函数对象的描述。
     */
    struct
    {
      ecma_value_t target_function; /**< target function 目标函数 */
      ecma_value_t args_len_or_this; /**< length of arguments or this value 这个值参数的数量 */
    } bound_function;

    ecma_external_handler_t external_handler_cb; /**< external function 外部函数 */
  } u;
} ecma_extended_object_t;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant