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

boa 源码相关 #758

Open
cisen opened this issue Nov 5, 2019 · 3 comments
Open

boa 源码相关 #758

cisen opened this issue Nov 5, 2019 · 3 comments

Comments

@cisen
Copy link
Owner

cisen commented Nov 5, 2019

总结

feature

支持

  • 作用域链
  • let/const 块作用域

不支持

  • +号违法,
  • 不支持在代码后面添加注释
  • 不支持class和原型链

总体流程

  • 说明
    • 没有虚拟机,token直接调run转化为rust运行
  • 在lib.rs,先生成realm全局环境变量,然后在parse,和run

环境

问答

boa原型链的遍历在哪里?

如何/在哪里 实现作用域链上变量的遍历的?

  • 就是在词法环境链LexicalEnvironment里上搜索的
  • 初始化的会生成两个的全局环境,一个备份存储到global_env作为根对象,另外一个作为词法环境的第一个环境存储到realm.environment链中
function aa() {
    var a = 1;
    console.log('1', a);
    function bb() {
        console.log('2', a);
    }
    bb();
}
// 入口,遍历到exprDef,定义变量的时候就调用get_binding_value遍历获取值
// src\lib\exec.rs
ExprDef::Local(ref name) => {
                let val = self.realm.environment.get_binding_value(name);
                Ok(val)
            }
// src\lib\environment\lexical_environment.rs
// 通过iter和get_outer_environment循环遍历外部环境
pub fn environments(&self) -> impl Iterator<Item = Environment> {
        std::iter::successors(Some(self.get_current_environment_ref().clone()), |env| {
            env.borrow().get_outer_environment()
        })
    }
// 这里是调用搜索环境的方法
pub fn get_binding_value(&self, name: &str) -> Value {
        self.environments()
            .find(|env| env.borrow().has_binding(name))
            .map(|env| env.borrow().get_binding_value(name, false))
            .unwrap_or_else(|| Gc::new(ValueData::Undefined))
    }

realm如何增删改查的?

pub struct Realm {
    // 存储全局的引用数据,比如Array Boolean JSON
    pub global_obj: Value,
    // 包括全局的this指向,全局的var变量池,声明式的变量池,给函数调用的全局对象
    pub global_env: Gc<GcCell<Box<GlobalEnvironmentRecord>>>,
    // 词法环境,用于存储所有编译过程中的变量。作用域链搜索变量也是通过这个此法环境
    pub environment: LexicalEnvironment,
}
  • 指向exec的时候let realm = Realm::create();创建,也是parse之前。然后存到Executor里面,供全局使用
  • realm就是全局环境。包括内置的全局对象(Array,Boolean),作用域链/词法环境(environment),全局环境global_env

realm里面的global_env和environment有啥用途区别?

console/JSON之类的全局对象是如何获取的?

  • 总结的就是定义的时候就不同,调用的时候根据定义的数据调用不同的方法
  • 定义的时候就不一样
pub fn create_constructor(global: &Value) -> Value {
    let console = ValueData::new_obj(Some(global));
    console.set_field_slice("log", to_value(log as NativeFunctionData));
    console.set_field_slice("error", to_value(error as NativeFunctionData));
    console.set_field_slice("exception", to_value(error as NativeFunctionData));
    console
}
  • 调用的时候
/// https://tc39.es/ecma262/#sec-call
    /// 函数call的实现
    pub(crate) fn call(&mut self, f: &Value, v: &Value, arguments_list: Vec<Value>) -> ResultValue {
        // All functions should be objects, and eventually will be.
        // 所有函数都应该或者最终变成对象
        // During this transition call will support both native functions and function objects
        // 在此过渡期间,调用将支持原生函数和函数对象
        match (*f).deref() {
            ValueData::Object(ref obj) => {
                let func: Value = obj.borrow_mut().deref_mut().get_internal_slot("call");
                if !func.is_undefined() {
                    return self.call(&func, v, arguments_list);
                }
                // TODO: error object should be here
                Err(Gc::new(ValueData::Undefined))
            }
            // 根据deref智能指针找到原定义方法
            ValueData::Function(ref inner_func) => match *inner_func.deref().borrow() {
                // 调用原生函数
                Function::NativeFunc(ref ntv) => {
                    let func = ntv.data;
                    func(v, &arguments_list, self)
                }
                Function::RegularFunc(ref data) => {
                    let env = &mut self.realm.environment;
                    // New target (second argument) is only needed for constructors, just pass undefined
                    let undefined = Gc::new(ValueData::Undefined);
                    env.push(new_function_environment(
                        f.clone(),
                        undefined,
                        Some(env.get_current_environment_ref().clone()),
                    ));
                    for i in 0..data.args.len() {
                        let name = data.args.get(i).expect("Could not get data argument");
                        let expr: &Value = arguments_list.get(i).expect("Could not get argument");
                        self.realm.environment.create_mutable_binding(
                            name.clone(),
                            false,
                            VariableScope::Function,
                        );
                        self.realm
                            .environment
                            .initialize_binding(name, expr.clone());
                    }

                    // Add arguments object
                    let arguments_obj = create_unmapped_arguments_object(arguments_list);
                    self.realm.environment.create_mutable_binding(
                        "arguments".to_string(),
                        false,
                        VariableScope::Function,
                    );
                    self.realm
                        .environment
                        .initialize_binding("arguments", arguments_obj);

                    let result = self.run(&data.expr);
                    self.realm.environment.pop();
                    result
                }
            },
            _ => Err(Gc::new(ValueData::Undefined)),
        }
    }

原生函数根JS函数有什么区别?

  • 原生函数叫NativeFunction, JS函数叫RegularFunction
  • 数据结构不一样
pub struct RegularFunction {
    /// The fields associated with the function
    /// 通用的对象,可以存储函数的所有对象属性
    pub object: Object,
    /// This function's expression
    /// 函数的表达式
    pub expr: Expr,
    /// The argument names of the function
    /// 函数的参数名
    pub args: Vec<String>,
}

impl RegularFunction {
    /// Make a new regular function
    #[allow(clippy::cast_possible_wrap)]
    pub fn new(expr: Expr, args: Vec<String>) -> Self {
        let mut object = Object::default();
        object.properties.insert(
            "arguments".to_string(),
            Property::default().value(Gc::new(ValueData::Integer(args.len() as i32))),
        );
        Self { object, expr, args }
    }
}

#[derive(Finalize, Clone)]
/// Represents a native javascript function in memory
/// 代表原生js函数的内存数据
pub struct NativeFunction {
    /// The fields associated with the function
    /// 函数使用对象保存的字段
    pub object: Object,
    /// The callable function data
    pub data: NativeFunctionData,
}

impl NativeFunction {
    /// Make a new native function with the given function data
    pub fn new(data: NativeFunctionData) -> Self {
        let object = Object::default();
        Self { object, data }
    }
}

JS定义的普通函数是如何存储的?数据结构是什么?

  • 根据上面的定义,主要有三个部分:形参名称/对象/expression表达式。表达式比较复杂

GC在rust里面时如何实现的?

ifelse/switch/case如何实现的?

  • switch
    • 首先parse的时候校验合法性,switch(){}只能是这种格式。所以检测到switch关键字后,就会调expect_punc函数就会去截取计算后面的第一个token,并比较是否是期待的。不符合的就会报ParseError错误,switch的token生成是在Keyword::Switch => {
    • 校验完合法性,lexer阶段还会去匹配case和default,并把生成的token存入Parser.tokens里面
    • 执行阶段就很简单了,将swich/case使用rust判断,然后执行各自的表达式就完成了。执行在ExprDef::Switch(ref val_e, ref vals, ref default) => {
  • if/else
    • 目前没有实现elseif,其实实现也不难。如何判断多层大括号{}的完整性?
    • 并不用判断每层的完整性,因为第二层对于第一层都只是一个expr,只需要递归判断本层括号的完整性就可以了。关键字的合法性判断参考上面switch
    • else的执行也是通过执行判断条件语句,然后执行expr或者else里面的expr就可以了

let/const在{}块作用域如何保证不提升和局部性(比如在if里面)?

  • 保证不提升的实现
  • 首先执行阶段,var根let/const定义就不同,var塞入环境的是
self.realm.environment.create_mutable_binding(
                        name.clone(),
                        false,
                        VariableScope::Function,
                    );

而let/const的是:

self.realm.environment.create_immutable_binding(
                        name.clone(),
                        false,
                        VariableScope::Block,
                    );

然后塞入环境的变量数组里面是根据block还是function的,最后寻找的时候也是从块作用域开始的

pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) {
        match scope {
            // let/const则直接塞入自己的环境
            VariableScope::Block => self
                .get_current_environment()
                .borrow_mut()
                .create_mutable_binding(name, deletion),
            VariableScope::Function => {
                // Find the first function or global environment (from the top of the stack)
                // 如果是function类型的,则向上找到第一个函数或者全局环境,然后把变量塞入
                let env = self
                    .environments()
                    .find(|env| match env.borrow().get_environment_type() {
                        EnvironmentType::Function | EnvironmentType::Global => true,
                        _ => false,
                    })
                    .expect("No function or global environment");

                env.borrow_mut().create_mutable_binding(name, deletion);
            }
        }
    }
  • 保证局部性就很好理解,因为冒泡是从块作用域开始的,变量是存储到块作用域的。不同块作用域的互相不发现

凡是{}都会创建一个环境?

  • 对的,会创建一个LexicalEnvironment用于存储该环境下的变量。看这里:
pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) {
        match scope {
            // let/const则直接塞入自己的环境
            VariableScope::Block => self
                .get_current_environment()
                .borrow_mut()
                .create_mutable_binding(name, deletion),
            VariableScope::Function => {
                // Find the first function or global environment (from the top of the stack)
                // 如果是function类型的,则向上找到第一个函数或者全局环境,然后把变量塞入
                let env = self
                    .environments()
                    .find(|env| match env.borrow().get_environment_type() {
                        EnvironmentType::Function | EnvironmentType::Global => true,
                        _ => false,
                    })
                    .expect("No function or global environment");

                env.borrow_mut().create_mutable_binding(name, deletion);
            }
        }
    }

.号和[]是如何取到值的?这两个有什么区别?

  • 只有一个区别![]的取值需要把key转化为字符串!
  • 取值实现代码是:
// .点号取值运算,获取对象aa.key的key的值。
            ExprDef::GetConstField(ref obj, ref field) => {
                let val_obj = self.run(obj)?;
                Ok(val_obj.borrow().get_field_slice(field))
            }
            // []取值运算,获取aa[key]中key对应的值
            ExprDef::GetField(ref obj, ref field) => {
                let val_obj = self.run(obj)?;
                let val_field = self.run(field)?;
                Ok(val_obj
                    .borrow()
                     // 这里调用to_string,就是[]和.的区别
                    .get_field_slice(&val_field.borrow().to_string()))
            }
  • get_field_slice函数调用的是get_field来取值,对get_field函数会取值的
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
    /// 读取对象的property和值,并判断undefined
    /// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]]
    /// 根据get_prop函数读取对象的属性, get_prop函数应该返回[[Get]]的结果,如果没有则返回[[Value]]的结果
    /// TODO: this function should use the get Value if its set
    /// 没做:如果设置了,这个函数应该使用get value
    pub fn get_field(&self, field: Value) -> Value {
        match *field {
            // Our field will either be a String or a Symbol
            // key必须是一个字符串或者symbol
            ValueData::String(ref s) => {
                match self.get_prop(s) {
                    Some(prop) => {
                        // If the Property has [[Get]] set to a function, we should run that and return the Value
                        // 如果property有[[Get]], 而且[[Get]]一个函数(getter),我们应该执行getter并且返回该值
                        let prop_getter = match prop.get {
                            Some(_) => None,
                            None => None,
                        };

                        // If the getter is populated, use that. If not use [[Value]] instead
                        // 如果gettter已经设置,则直接使用,否则使用[[Value]]代替
                        match prop_getter {
                            Some(val) => val,
                            None => {
                                let val = prop
                                    .value
                                    .as_ref()
                                    .expect("Could not get property as reference");
                                val.clone()
                            }
                        }
                    }
                    None => Gc::new(ValueData::Undefined),
                }
            }
            ValueData::Symbol(_) => unimplemented!(),
            _ => Gc::new(ValueData::Undefined),
        }
    }

.和[]设置值是把值添加到可枚举的对象中?

  • 先断点进去ExprDef::Assign,再在set_field_slice函数打断点

嵌套self.run递归如何传递参数。比如aa.bb = 1;bb如何传递给assign?

  • 每一种expr存储的数据是不一样的,所以在run的match的时候,可以获得不同的参数
  • 这些不同的参数是parse的时候,通过正则匹配符号,获得所需要的前后参数,然后存入expr即可

Object.to_string函数做了什么

  • to_string根toString匹配是在object.create_constructor函数里面。调用GC封装携带的to_string函数
  • to_value这块比较复杂,根据数据的类型ToValue,string,number啊等之类的实现不同的to_value函数。这里是字符串类型,所以调用impl ToValue for String里面的函数
  • 实际上就是JS的值转化为rust的值,将rust值转化为js值使用的是FromValue

Array.prototype.join的原理

  1. 判断参数是否合理
  2. 读取js数组的长度
  3. 根据其长度递增遍历数组,每个元素都使用rust的to_string方法转换为字符串
  4. 将收集的字符串数组使用rust原生的join方法转换为字符串并返回

rust已经有所有权了,还需要JS的GC吗?

@cisen cisen changed the title boa TC39成员编写的实现部分特性的JS引擎 boa 源码相关 Nov 7, 2019
@cisen
Copy link
Owner Author

cisen commented Nov 19, 2019

参与

  • string对象会出现value_of调用自己的to_string函数

array

toString

Array.prototype.toString ( )
当调用toString 方法,采用如下步骤:

  1. 令 array 为用this 值调用ToObject 的结果。
  2. 令 func 为以"join" 作为参数调用array 的[[Get]] 内部方法的结果。
  3. 如果 IsCallable(func/join) 是false, 则令func 为标准内置方法Object.prototype.toString (15.2.4.2)。
  4. 提供 array 作为this 值并以空参数列表调用func 的[[Call]] 内部方法,返回结果。

toString 函数被有意设计成通用的;它的this 值并非必须是数组对象。因此,它可以作为方法转移到其他类型的对象中。一个宿主对象是否可以正确应用这个toString 函数是依赖于实现的。

英文标准描述

15.4.4.2 Array.prototype.toString ( )
When the toString method is called, the following steps are taken:

  1. Let array be the result of calling ToObject on the this value.
  2. Let func be the result of calling the [[Get]] internal method of array with argument "join".
  3. If IsCallable(func) is false, then let func be the standard built-in method Object.prototype.toString (15.2.4.2).
  4. Return the result of calling the [[Call]] internal method of func providing array as the this value and an empty arguments list.
    NOTE The toString function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the toString function can be applied successfully to a host object is implementation-dependent.
/**
 * The Array.prototype object's 'toString' routine
 * 数组的toString函数
 *
 * See also:
 *          ECMA-262 v5, 15.4.4.2
 *
 * @return ecma value
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_builtin_array_prototype_object_to_string (ecma_value_t this_arg, /**< this argument */
                                               ecma_object_t *obj_p) /**< array object */

{
  /* 2. */
  // 获取join函数
  ecma_value_t join_value = ecma_op_object_get_by_magic_id (obj_p, LIT_MAGIC_STRING_JOIN);
  // 如果不存在join函数
  if (ECMA_IS_VALUE_ERROR (join_value))
  {
    return join_value;
  }
  // 如果不运行调用join函数
  if (!ecma_op_is_callable (join_value))
  {
    /* 3. */
    ecma_free_value (join_value);
    return ecma_builtin_helper_object_to_string (this_arg);
  }

  /* 4. */
  ecma_object_t *join_func_obj_p = ecma_get_object_from_value (join_value);

  ecma_value_t ret_value = ecma_op_function_call (join_func_obj_p, this_arg, NULL, 0);

  ecma_deref_object (join_func_obj_p);

  return ret_value;
} /* ecma_builtin_array_prototype_object_to_string */

rust

  • 操作JS对象的方法有
    • remove_prop
    • get_field_slice
    • set_prop
    • set_field_slice
    • get_binding_value
    • has_field
    • set_internal_method
    • to_value
    • is_function
    • src\lib\builtins\value.rs有很多操作JS的方法

Object

toString

  • 在中文标准第172页,英文标准115页
    Object.prototype.toString ( )
    当调用toString 方法,采用如下步骤:
  1. 如果 this 的值是undefined, 返回"[object Undefined]".
  2. 如果 this 的值是null, 返回"[object Null]".
  3. 令 O 为以this 作为参数调用ToObject 的结果.
  4. 令 class 为O 的[[Class]] 内部属性的值.
  5. 返回三个字符串 "[object ", class, and "]" 连起来的字符串.

15.2.4.2 Object.prototype.toString ( )
When the toString method is called, the following steps are taken:

  1. If the this value is undefined, return "[object Undefined]".
  2. If the this value is null, return "[object Null]".
  3. Let O be the result of calling ToObject passing the this value as the argument.
  4. Let class be the value of the [[Class]] internal property of O.
  5. Return the String value that is the result of concatenating the three Strings "[object ", class, and "]".

jerryscript的实现

/**
 * Common implementation of the Object.prototype.toString routine
 *
 * See also:
 *          ECMA-262 v5, 15.2.4.2
 *
 * Used by:
 *         - The Object.prototype.toString routine.
 *         - The Array.prototype.toString routine as fallback.
 *
 * @return ecma value
 *         Returned value must be freed with ecma_free_value.
 */

ecma_value_t
ecma_builtin_helper_object_to_string (const ecma_value_t this_arg) /**< this argument */
{
  lit_magic_string_id_t type_string;

  if (ecma_is_value_undefined (this_arg))
  {
    type_string = LIT_MAGIC_STRING_UNDEFINED_UL;
  }
  else if (ecma_is_value_null (this_arg))
  {
    type_string = LIT_MAGIC_STRING_NULL_UL;
  }
  else
  {
    ecma_value_t obj_this = ecma_op_to_object (this_arg);

    if (ECMA_IS_VALUE_ERROR (obj_this))
    {
      return obj_this;
    }

    JERRY_ASSERT (ecma_is_value_object (obj_this));

    ecma_object_t *obj_p = ecma_get_object_from_value (obj_this);

    type_string = ecma_object_get_class_name (obj_p);

#if ENABLED (JERRY_ES2015_BUILTIN_SYMBOL)
    ecma_value_t tag_value = ecma_op_object_get_by_symbol_id (obj_p, LIT_MAGIC_STRING_TO_STRING_TAG);

    if (ECMA_IS_VALUE_ERROR (tag_value))
    {
      ecma_deref_object (obj_p);
      return tag_value;
    }

    if (ecma_is_value_string (tag_value))
    {
      ecma_deref_object (obj_p);
      return ecma_builtin_helper_object_to_string_tag_helper (tag_value);
    }

    ecma_free_value (tag_value);
#endif /* ENABLED (JERRY_ES2015_BUILTIN_SYMBOL) */

    ecma_deref_object (obj_p);
  }

  ecma_string_t *ret_string_p;

  /* Building string "[object #type#]" where type is 'Undefined',
     'Null' or one of possible object's classes.
     The string with null character is maximum 27 characters long. */
  const lit_utf8_size_t buffer_size = 27;
  JERRY_VLA (lit_utf8_byte_t, str_buffer, buffer_size);

  lit_utf8_byte_t *buffer_ptr = str_buffer;

  const lit_magic_string_id_t magic_string_ids[] =
  {
    LIT_MAGIC_STRING_LEFT_SQUARE_CHAR,
    LIT_MAGIC_STRING_OBJECT,
    LIT_MAGIC_STRING_SPACE_CHAR,
    type_string,
    LIT_MAGIC_STRING_RIGHT_SQUARE_CHAR
  };

  for (uint32_t i = 0; i < sizeof (magic_string_ids) / sizeof (lit_magic_string_id_t); ++i)
  {
    buffer_ptr = lit_copy_magic_string_to_buffer (magic_string_ids[i], buffer_ptr,
                                                  (lit_utf8_size_t) ((str_buffer + buffer_size) - buffer_ptr));
    JERRY_ASSERT (buffer_ptr <= str_buffer + buffer_size);
  }

  ret_string_p = ecma_new_ecma_string_from_utf8 (str_buffer, (lit_utf8_size_t) (buffer_ptr - str_buffer));

  return ecma_make_string_value (ret_string_p);
} /* ecma_builtin_helper_object_to_string */

其他模块的toString实现原理

String

  • 调用new String(arg)的时候会把参数从保存到该对象的StringData字段下面
  • 调用toString的是再把它copy并返回出来

number

  • 调用toString的时候先转化为rust的数字,然后再使用rust的format函数转化为字符串返回
  • 注意Number对象才有toString方法

Boolean

  • Boolean对象才有toString方法
  • Boolean对象根String对象一样,把值存储到该对象的BooleanData字段下面。不一样的是,Boolean对象存储的是值,只是toString的时候先转化为rust的boolean值再转化为string返回

Regexp

  • 正则也是,定义的时候就把正则参数转化为字符串存储到内部对象的OriginalSource属性中,转化为字符串的时候直接取出来就好

jerryscript的实现

  • 数组
    • jerryscript是调用数组的join方法,将数组转化为字符串的
  • 正则
    • 正则根boa一样,都是存储patter字符串,然后再转化

@cisen
Copy link
Owner Author

cisen commented Jan 22, 2020

bugfix

new Array

@cisen
Copy link
Owner Author

cisen commented Feb 15, 2020

pr

pr的检查命令

cargo fmt --all -- --check
cargo clippy -- --verbose
cargo install critcmp
cargo bench -p Boa -- --save-baseline changes

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