2

《Chrome V8原理讲解》第十六篇 运行时辅助类,详解加载与调用过程

 2 years ago
source link: https://zhuanlan.zhihu.com/p/431621841
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

《Chrome V8原理讲解》第十六篇 运行时辅助类,详解加载与调用过程

chrome v8连载,3~4天一篇,持续更新中...

本系列的前十三篇文章,讲解了V8执行Javascript时最基础的工作流程和原理,包括词法分析、语法分析、字节码生成、Builtins方法、ignition执行单元,等等,达到了从零做起,入门学习的目的。
接下来的文章将以问题为导向讲解V8源码,例如:以闭包技术、或垃圾回收(GC)为专题讲解V8中的相关源码。V8代码过于庞大,以问题为导向可以使得学习主题更加明确、效果更好。同时,我争取做到每篇文章是一个独立的知识点,方便大家阅读。
读者可以把想学的内容在文末评论区留言,我汇总后出专题文章。

运行时辅助类(Runtime)在Javascript执行期提供众多辅助功能,如属性访问,新建对象、正则表达式等。上篇文章对“Runtime是什么?怎么用了?”做了详细介绍,本文将重点说明“他是怎么来的?他存储在哪?”,也就是Runtime在V8中的初始化过程,初始化完后的存储位置,以及在BytecodeHandler中的调用细节。

2 Runtime初始过程

Runtime属于V8的基础组件,在创建Isolate时完成初始化,下面是初始化阶段的源码:

1.   bool Isolate::Init(ReadOnlyDeserializer* read_only_deserializer,
2.                      StartupDeserializer* startup_deserializer) {
3.     TRACE_ISOLATE(init);
4.     const bool create_heap_objects = (read_only_deserializer == nullptr);
5.     // We either have both or neither.
6.     DCHECK_EQ(create_heap_objects, startup_deserializer == nullptr);
7.     base::ElapsedTimer timer;
8.    //省略很多...............................
9.      handle_scope_implementer_ = new HandleScopeImplementer(this);
10.      load_stub_cache_ = new StubCache(this);
11.      store_stub_cache_ = new StubCache(this);
12.      materialized_object_store_ = new MaterializedObjectStore(this);
13.      regexp_stack_ = new RegExpStack();
14.      regexp_stack_->isolate_ = this;
15.      date_cache_ = new DateCache();
16.      heap_profiler_ = new HeapProfiler(heap());
17.      interpreter_ = new interpreter::Interpreter(this);
18.      compiler_dispatcher_ =
19.          new CompilerDispatcher(this, V8::GetCurrentPlatform(), FLAG_stack_size);
20.      // Enable logging before setting up the heap
21.      logger_->SetUp(this);
22.      {  // NOLINT
23.        ExecutionAccess lock(this);
24.        stack_guard()->InitThread(lock);
25.      }
26.      // SetUp the object heap.
27.      DCHECK(!heap_.HasBeenSetUp());
28.      heap_.SetUp();
29.      ReadOnlyHeap::SetUp(this, read_only_deserializer);
30.      heap_.SetUpSpaces();
31.      isolate_data_.external_reference_table()->Init(this);
32.      //省略很多...................
33.    }

上述代码是isolate的初始化入口,行10~20包括了很多重要组件的初始化工作,例如Interpretercompiler_dispatcher等等,后续文章都会讲到。Runtime的初始化由external负责,代码31行Init()方法中完成相关工作,源码如下:

1.  void ExternalReferenceTable::Init(Isolate* isolate) {
2.    int index = 0;
3.    // kNullAddress is preserved through serialization/deserialization.
4.    Add(kNullAddress, &index);
5.    AddReferences(isolate, &index);
6.    AddBuiltins(&index);
7.    AddRuntimeFunctions(&index);
8.    AddIsolateAddresses(isolate, &index);
9.    AddAccessors(&index);
10.    AddStubCache(isolate, &index);
11.    AddNativeCodeStatsCounters(isolate, &index);
12.    is_initialized_ = static_cast<uint32_t>(true);
13.    CHECK_EQ(kSize, index);
14.  }

代码4行~11行是由external类负责管理的初始化,这其中包括了我们多次提到的BuiltinsAddRuntimeFunctions(&index)是Runtime的初始化函数,代码如下:

1.  void ExternalReferenceTable::AddRuntimeFunctions(int* index) {
2.    CHECK_EQ(kSpecialReferenceCount + kExternalReferenceCount +
3.                 kBuiltinsReferenceCount,
4.             *index);
5.    static constexpr Runtime::FunctionId runtime_functions[] = {
6.  #define RUNTIME_ENTRY(name, ...) Runtime::k##name,
7.        FOR_EACH_INTRINSIC(RUNTIME_ENTRY)
8.  #undef RUNTIME_ENTRY
9.    };
10.    for (Runtime::FunctionId fId : runtime_functions) {
11.      Add(ExternalReference::Create(fId).address(), index);
12.    }
13.    CHECK_EQ(kSpecialReferenceCount + kExternalReferenceCount +
14.                 kBuiltinsReferenceCount + kRuntimeReferenceCount,
15.             *index);
16.  }

它只有一个参数index,在本文所用的V8版本中它的值是430,代表下标,ExternalReferenceTable是表结构,它的核心成员是地址指针数组ref_addr_,index代表它的下标,表示Runtime函数在ExternalReferenceTable成员变量ref_addr_中的位置,本文所用V8中有468个Runtime方法,初始化完成后存储在ref_addr_的430~430+468-1这个区间内。ExternalReferenceTable表结构非常简单,稍后给出。

代码5行定义了Runtime的枚举变量,其中涉及的宏模板请自行展开,图1给出部分枚举变量。代码11行通过for循环添加函数,Create()方法根据Runtime Id创建表项,最终添加到ExternalReferenceTable表中,代码如下:

1.  ExternalReference ExternalReference::Create(Runtime::FunctionId id) {
2.    return Create(Runtime::FunctionForId(id));
3.  }
4.  //分隔线........................
5.  const Runtime::Function* Runtime::FunctionForId(Runtime::FunctionId id) {
6.    return &(kIntrinsicFunctions[static_cast<int>(id)]);
7.  }
8.  //分隔线.......................
9.  ExternalReference ExternalReference::Create(const Runtime::Function* f) {
10.    return ExternalReference(
11.        Redirect(f->entry, BuiltinCallTypeForResultSize(f->result_size)));
12.  }

上述代码是三个函数,依次调用。第一个函数Create(Runtime::FunctionId id)的参数是图1中的枚举值;第二个函数FunctionForId(Runtime::FunctionId id)的返回值是kIntrinsicFunctions类型的数据,该数据是以下几个宏代码的展开结果。

#define FUNCTION_ADDR(f) (reinterpret_cast<v8::internal::Address>(f))
#define F(name, number_of_args, result_size)                                  \
  {                                                                           \
    Runtime::k##name, Runtime::RUNTIME, #name, FUNCTION_ADDR(Runtime_##name), \
        number_of_args, result_size                                           \
  }                                                                           \
  ,


#define I(name, number_of_args, result_size)                       \
  {                                                                \
    Runtime::kInline##name, Runtime::INLINE, "_" #name,            \
        FUNCTION_ADDR(Runtime_##name), number_of_args, result_size \
  }                                                                \
  ,

static const Runtime::Function kIntrinsicFunctions[] = {
    FOR_EACH_INTRINSIC(F) FOR_EACH_INLINE_INTRINSIC(I)};

#undef I
#undef F

kIntrinsicFunctions数据是什么类型?它是数组,每个成员又是函数名,参数个数,返回值个数组成的另一个数组。上一篇文章中,我定义的MyRuntime方法,格式是:F(MyRuntime,1,1),正好和这个数据格式对应,下面是kIntrinsicFunctions展开的样例:

kIntrinsicFunctions []={
  //.....................
  {                                                                           
    Runtime::kDebugPrint, Runtime::RUNTIME, "DebugPrint", (reinterpret_cast<v8::internal::Address>(Runtime_DebugPrint)), 
        1, 1 
  },
  //.....................

kIntrinsicFunctions是一个二维数组,上述代码展示了其中的一组数据。下面是void ExternalReferenceTable::AddRuntimeFunctions(int* index)方法中11行Add()方法的源码:

void ExternalReferenceTable::Add(Address address, int* index) {
  ref_addr_[(*index)++] = address;
}

index每次添加后会增1,ref_addr_是什么呢?它是ExternalReferenceTable的成员变量,地址指针数组,下面是ExternalReferenceTable源码:

1.  class ExternalReferenceTable {
2.   public:
3.    static constexpr int kSpecialReferenceCount = 1;
4.    static constexpr int kExternalReferenceCount =
5.        ExternalReference::kExternalReferenceCount;
6.    static constexpr int kBuiltinsReferenceCount =
7.  #define COUNT_C_BUILTIN(...) +1
8.        BUILTIN_LIST_C(COUNT_C_BUILTIN);
9.  #undef COUNT_C_BUILTIN
10.     static constexpr int kRuntimeReferenceCount =
11.         Runtime::kNumFunctions -
12.         Runtime::kNumInlineFunctions;  // Don't count dupe kInline... functions.
13.     static constexpr int kIsolateAddressReferenceCount = kIsolateAddressCount;
14.     static constexpr int kAccessorReferenceCount =
15.         Accessors::kAccessorInfoCount + Accessors::kAccessorSetterCount;
16.     static constexpr int kStubCacheReferenceCount = 12;
17.     static constexpr int kStatsCountersReferenceCount =
18.   #define SC(...) +1
19.         STATS_COUNTER_NATIVE_CODE_LIST(SC);
20.   #undef SC
21.   //..............省略很多............................
22.     ExternalReferenceTable() = default;
23.     void Init(Isolate* isolate);
24.    private:
25.     void Add(Address address, int* index);
26.     void AddReferences(Isolate* isolate, int* index);
27.     void AddBuiltins(int* index);
28.     void AddRuntimeFunctions(int* index);
29.     void AddIsolateAddresses(Isolate* isolate, int* index);
30.     void AddAccessors(int* index);
31.     void AddStubCache(Isolate* isolate, int* index);
32.     Address GetStatsCounterAddress(StatsCounter* counter);
33.     void AddNativeCodeStatsCounters(Isolate* isolate, int* index);
34.     STATIC_ASSERT(sizeof(Address) == kEntrySize);
35.     Address ref_addr_[kSize];
36.     static const char* const ref_name_[kSize];
37.     uint32_t is_initialized_ = 0;
38.     uint32_t dummy_stats_counter_ = 0;
39.     DISALLOW_COPY_AND_ASSIGN(ExternalReferenceTable);
40.   };

代码7行给出了Builtin统计时使用的宏模板,代码行25~35说明了ExternalReferenceTable负责初始化和管理哪些数据。初始化后的数据,也就是函数地址,保存在代码35行的ref_addr_数组中,这个数组的类型Addressusing Address = uintptr_t;typedef unsigned __int64 uintptr_t;

图2中给出了三个关键信息,Add()方法的调用位置,对应的函数调用堆栈,以及展示了部分ref_addr_的内容。
总结,ExternalReferenceTable最重要的成员是ref_addr_,它本质是一个指针数组,Rumtime函数保存在下标430开始的成员中,调用时用下标值索引函数地址。

3 Runtime调用

CallRuntime()CallRuntimeWithCEntry()负责调用Runtime功能,上篇文章给出了调用样例并做了实验,本文不再赘述。我们以CallRuntime()为例讲解,源码如下:

1.   template <class... TArgs>
2.   TNode<Object> CallRuntime(Runtime::FunctionId function,
3.                             SloppyTNode<Object> context, TArgs... args) {
4.     return CallRuntimeImpl(function, context,
5.                            {implicit_cast<SloppyTNode<Object>>(args)...});
6.   }
7.  //分隔线.........................
8.   TNode<Object> CodeAssembler::CallRuntimeImpl(
9.       Runtime::FunctionId function, TNode<Object> context,
10.        std::initializer_list<TNode<Object>> args) {
11.      int result_size = Runtime::FunctionForId(function)->result_size;
12.      TNode<Code> centry =
13.          HeapConstant(CodeFactory::RuntimeCEntry(isolate(), result_size));
14.      return CallRuntimeWithCEntryImpl(function, centry, context, args);
15.    }
16.  //分隔线.........................
17.    TNode<Type> HeapConstant(Handle<Type> object) {
18.        return UncheckedCast<Type>(UntypedHeapConstant(object));
19.      }
20.  //分隔线.........................
21.  TNode<Object> CodeAssembler::CallRuntimeWithCEntryImpl(
22.        Runtime::FunctionId function, TNode<Code> centry, TNode<Object> context,
23.        std::initializer_list<TNode<Object>> args) {
24.      constexpr size_t kMaxNumArgs = 6;
25.      DCHECK_GE(kMaxNumArgs, args.size());
26.      int argc = static_cast<int>(args.size());
27         auto call_descriptor = Linkage::GetRuntimeCallDescriptor(zone(), function, argc, Operator::kNoProperties,
                      Runtime::MayAllocate(function) ? CallDescriptor::kNoFlags
                                     : CallDescriptor::kNoAllocate);
28.      for (auto arg : args) inputs.Add(arg);
29.      inputs.Add(ref);
30.      inputs.Add(arity);
31.      inputs.Add(context);
32.      CallPrologue();
33.      Node* return_value =
34.          raw_assembler()->CallN(call_descriptor, inputs.size(), inputs.data());
35.      HandleException(return_value);
36.      CallEpilogue();
37.      return UncheckedCast<Object>(return_value);
38.    }

代码2行,CallRuntime()声明中,可以看到它有三个参数,第一个是FunctionId枚举类型,前面提到过;第二个参数是context,第三参数args是传给Runtime函数的参数列表。CallRuntime()调用CallRuntimeImpl(),在CallRuntimeImpl()内部读取堆中的常量数据(HeapConstant),代码18行,该数据中保存了函数与下标的对应关系,用FunctionId在该表中查到对应的函数地址,代码27行建立调用描述符(参见之前的文章),最终在代码34行调用Runtime函数。
上述代码是Builtin,不能在C++级别做Debug,无法给出调用堆栈等调试信息。也许你会有疑问:为什么不用上篇文章中提到的RUNTIME_DebugPrint或是自定义Runtime功能做断点?答:我们现在讲的就是Runtime的调用过程,没办法使用Runtime调试自己:(((。
好了,今天到这里,下次见。

恳请读者批评指正、提出宝贵意见
微信:qq9123013 备注:v8交流 邮箱:[email protected]

本文由灰豆原创发布

转载出处:https://www.anquanke.com/post/id/256988


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK