visit
Welcome to other chapters of Let’s Understand Chrome V8
V8’s builtins can be implemented using a number of different methods (each with different trade-offs):
Platform-dependent assembly language: can be highly efficient, but need manual ports to all platforms and are difficult to maintain.
C++: very similar in style to runtime functions and has access to V8’s powerful runtime functionality, but usually not suited to performance-sensitive areas.
JavaScript: concise and readable code, access to fast intrinsics, but frequent usage of slow runtime calls, subject to unpredictable performance through type pollution, and subtle issues around (complicated and non-obvious) JS semantics.
CodeStubAssembler: provides efficient low-level functionality that is very close to assembly language while remaining platform-independent and preserving readability.
What is Runtime? It is a method for implementing V8 builtins.
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. //omit...........................
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. //omit...............
33. }
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. }
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. }
The AddRuntimeFunctions has a parameter index. In my V8, there are 468 runtime functions, the first function is at ExternalReferenceTable[index =430], the last one is at ExternalReferenceTable[430+468–1].
The 11th line Create() creates an entry according to the Runtime ID and finally stores it into the ExternalReferenceTable, see below.
1. ExternalReference ExternalReference::Create(Runtime::FunctionId id) {
2. return Create(Runtime::FunctionForId(id));
3. }
4. //separation........................
5. const Runtime::Function* Runtime::FunctionForId(Runtime::FunctionId id) {
6. return &(kIntrinsicFunctions[static_cast<int>(id)]);
7. }
8. //separation.......................
9. ExternalReference ExternalReference::Create(const Runtime::Function* f) {
10. return ExternalReference(
11. Redirect(f->entry, BuiltinCallTypeForResultSize(f->result_size)));
12. }
#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 []={
//.....................
{
Runtime::kDebugPrint, Runtime::RUNTIME, "DebugPrint", (reinterpret_cast<v8::internal::Address>(Runtime_DebugPrint)),
1, 1
},
//.....................
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. //...........omit.........................
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. };
Figure 2 gives three important points, first is the function Add(); second, helps you to watch the variable ref_addr_; last is the call stack which can help you to debug this code.
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. //.....separation.........................
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. //.....separation.........................
17. TNode<Type> HeapConstant(Handle<Type> object) {
18. return UncheckedCast<Type>(UntypedHeapConstant(object));
19. }
20. //...separation.........................
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. }
In line 2, the first parameter is FunctionID which is the enum ID motioned above; the second is the current context that will be explained in the future; the last is an args list passed to a specific Runtime function.
Note: Sea of Nodes is the prerequisite knowledge if you want to fully understand the principle of CallRuntimeImpl.
Okay, that wraps it up for this share. I’ll see you guys next time, take care!
WeChat: qq9123013 Email: [email protected]