cisen / blog

Time waits for no one.
134 stars 20 forks source link

JerryScript 源码问答 #535

Open cisen opened 5 years ago

cisen commented 5 years ago

总结

优化

难点

注意

问答

lexer

保留关键字如何匹配的?

参数名如何确定?

内存是如何申请的?

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

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的哪里?

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

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

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

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

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

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

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

有两种内存方式?

什么东西存全局heap?

jmem封装相关

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

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

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

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

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

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

// 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的值绝对是否释放该对象

加法和赋值的实现流程?

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

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

this是如何实现的?

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

创建栈帧的有几种情况

null,NaN和undefined有什么区别?

typeof的实现

==和===的运算逻辑

为什么null instanceof Object为false

为什么null == undefined为true

为什么false == undefined为false

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

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

this_binding是什么?

super函数做了什么?

如何创建属性prop的?

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

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

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

promise是如何实现的?

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

super函数做了什么?

箭头函数有何特别

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

闭包的本质是prev_context_p指针?

prototype本质是什么?

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

valueOf和toString函数有何不同

var,let,const有何不同

instanseof做了什么?

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

number的转化规则:

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

undefined和null有何不同?

/**
 * 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编码是在哪里生成?被存储到哪里的?

如何改变this?

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

js如何实现继承的?

__proto__是什么?

constructor构造函数是什么?

为什么typeof null是object,undefined是undefined

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

变量名存储在哪里了?

// 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是什么?
```c
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对象时什么?

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

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

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

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

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

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

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

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

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

ESM ( ECMAScript Module) 原理和流程?

ECM 第二步connect做了什么?

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

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

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

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

其他发现

变量提升的原因

cisen commented 5 years ago

对象是什么?

/**
 * 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;