深刻V8引擎-枚举+位运算实现参数配置

不知不觉都快月底了,看了看上一篇仍是6号写的,惭愧惭愧,说好的坚持。为了证实没有偷懒(其实仍是沉迷了一会dota2),先上一个图自证清白。ajax

基本上从初始化引擎,到Isolate、handleScope、Context一直到编译其实都有记录,可是实在是无从下手。虽然说个人博客也没有什么教学意义,可是至少也须要有一个中心和结论。很遗憾,上述的每一步都并互有关联,也就是单独拿出来写毫无心义。而从总体架构来阐述,而后细化到这每一步,我又尚未到那个境界。所以,综合考虑下,决定先暂时放弃逐步解析,优先产出一些有意义的东西。数组

这一篇的内容属于V8中(或许是C++独有)使用比较广泛的一个技巧,不少模块都有使用。架构

当初在入门学JS的时候,到了ajax那里,跟着视频学封装。老师讲,若是参数过多,就包装成封装一个对象,这样只须要一个参数就能够了。当时我想着,一个对象也好麻烦啊,还不如封装的时候本身定义一下,若是传1,就表明是"GET"请求,传2,就表明"POST"等等。没想到,当初天真的想法,居然在C++里面实现了。app

 

下面开始正文,首先须要简单介绍一下枚举,话说各位用过TS的大佬应该都懂,或者接触过protobuf这些数据格式化库也有。枚举在不少语言中都有,定义简单说就是一系列的常量集合,一般用来作简单配置。若是没有指定值,那么就是0、一、2...依次增长,举例以下。函数

enum fruit {
  apple,
  banana,
  pear,
  orange = 5,
};

int main(int argc, const char * argv[]) {
  cout << "enum apple is " << fruit::apple << endl;
  cout << "enum banana is " << fruit::banana << endl;
  cout << "enum pear is " << fruit::pear << endl;
  cout << "enum orange is " << fruit::orange << endl;
  return 0;
}

这里咱们定义了一个枚举类型,依次打印每个值,会获得0、一、2,而第四个因为手动指定了值,因此会获得5。若是不去手动指定值,从JS的角度来看枚举有点相似于一个颠倒形式的数组,好比说定义['apple', 'banana', 'pear'],经过下标0、一、2能够取到对应的值,而枚举偏偏相反,经过枚举值取到的是"下标"。大部分简单的配置状况下,是不用去关心枚举具体的值。这样,关于枚举就介绍完了,很简单。ui

接下来,来看看V8是如何利用这个数据类型来实现参数配置。在对JS源码字符串的编译过程当中,有一个类十分重要,负责记录String => AST的过程,名为ParseInfo,这里不去探究转换过程,单纯看一下这个类的标记配置相关,类定义以下。spa

namespace v8 {
namespace interval {

// A container for the inputs, configuration options, and outputs of parsing.
/**
 * 有5个构造函数和大量私有属性
 */
class ParseInfo {
  public:
    explicit ParseInfo(AccountingAllocator* zone_allocator);
    explicit ParseInfo(Isolate*);
    ParseInfo(Isolate*, AccountingAllocator* zone_allocator);
    ParseInfo(Isolate* isolate, Handle<Script> script);
    ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared);
  private:
    // Various configuration flags for parsing.
    enum Flag {
      kToplevel = 1 << 0,
      kEager = 1 << 1,
      kEval = 1 << 2,
      kStrictMode = 1 << 3,
      kNative = 1 << 4,
      // ...more
    };
    unsigned flags_;
    void SetFlag(Flag f) { flags_ |= f; }
    void SetFlag(Flag f, bool v) { flags_ = v ? flags_ | f : flags_ & ~f; }
    bool GetFlag(Flag f) const { return (flags_ & f) != 0; }
};

省略了不少代码,这个类真的超级大,特别是构造函数,虽然说内部走的Isolate那一个,可是变向的调用会走全套构造。目前只须要关心私有属性枚举Flag和其相关的三个方法,Flag负责标记编译的代码的一些特征,好比说[native code]、module、IIFE、'strict mode'等等。code

枚举Flag的定义有点意思,除去了正常的语义化集合,每一项都给了具体的值,依次为一、二、四、8...,后面会解释缘由。flags_就表明了整个Flag的配置,类型比较狗,只注明了一个无符号类型,大部门状况下编译器会认为是一个unsigned int。剩下的三个方法则是根据参数来调整flag_的值,具体实现很是简单,可是理解起来有点恶心,全是位运算。视频

若是要理解这个操做的原理,须要从二进制的角度来理解,枚举类型的每个值,其实表明的是二进制的一、十、100、1000等等,因此flags_其实也须要从二进制来理解,默认状况是一个全0的数。这样再来看SetFlag方法,假设解析中发现字符串"strict mode",此时须要调用SetFlag(Flag::kStrictMode)来设置参数,或运算表示只要有一个是1即置1,因此flags_的第4位会被置位1,值就变成了1000。对象

那么GetFlag就很好理解了,传入一个Flag枚举值,因为与运算须要两个都是1才会为真,而传入的总为1,因此只要flag_对应的位为1(即被设置过)就会返回真。

而SetFlag的重载方法则是一个扩展,当第二个参数为true时,使用与单参数一致。当第二个参数为false时,会将该位置0,也就是取消这个配置。

这样,用一个数字就能够表明很是多的编译参数。在应用时,直接取出数字对应位数的值,若是为1,说明该配置为真,不然为假,即简单,又很高效。固然,这个方法的局限性也很明显,只能针对布尔值的配置,若是是复杂类型那仍是须要一个xxxoptions的类来管理。

 

由于实在太简单了,因此我也懒得画图,应该能理解吧。

相关文章
相关标签/搜索