- read

Let’s Understand Chrome V8 — Chapter 16: What is Runtime? Why is it important?

灰豆 Huidou 4

Welcome to other chapters of Let’s Understand Chrome V8

From the view of implementation: the builtins are implemented by Runtime, Torque (CodeStubAssembler), JavaScript, and ASM. Below is the official description of the 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.

Why is it important? If you understand it means that you know the design and implementation of the quarter of the builtins.

In this article, I will talk about the initialization and call of Runtime. By learning them, we will have a comprehensive understanding of the workflow of Runtime.

1. Initialization

Runtime is the foundation component of V8, which is initialized during the V8 startup and is managed by the ExternalReferenceTable which is a pointer array for holding external resources. The below Init() is responsible for the initialization of ExternalReferenceTable.

In the above code, lines 10~20 include the initialization of many foundation components, such as Interpreter and compiler_dispatcher. Line 31 is the ExternalReferenceTable that holds Runtime, see below.

In the above code, the 7th line is the initialization of the Runtime AddRuntimeFunctions where is below.

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].

As mentioned above, the ExternalReferenceTable is a pointer array, which is not only to hold the Runtime but also other stuff. We will talk about the stuff in the future.

Figure 1 is adding a Runtime function into the ExternalReferenceTable and gives you a chance to watch runtime_functions in the local variable window.

The 11th line Create() creates an entry according to the Runtime ID and finally stores it into the ExternalReferenceTable, see below.

The Create() calls the FunctionForId() to return a kIntrinsicFunctions which is defined in below.

What is the kIntrinsicFunctions? Actually, it is an array in which every element is a 6-tuple. In the below 6-tuple, the first is a unique enum ID, the third is a mnemonic which does not help much, the fourth is the function address, and the last two are parameter counter and return counter. As you see, it is adding the Runtime DebugPrint, namely Initialization.

Let’s look at the ExternalReferenceTable.

Line 7–17 defines all Builtins managed by ExternalReferenceTable, namely all Runtimes. Line 25–35 defines all private methods of ExternalReferenceTable. Take a look at line 35, it’s the array that holds the Runtime address. The Address is ‘using Address = uintptr_t’.

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.

2. Calling Runtime

In the last article, I wrote a Runtime function — MyRunctimeFunction, and also described Runtime’s definition.

Here, I’ll talk about the CallRuntime which is common usage in bytecodes and which can help you to understand the interaction between bytecode and runtime.

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.

In CallRuntimeImpl, line 11 takes out the result size where is stored in the kIntrinsicFunctions; line14 calls CallRuntimeWithCEntryImpl.

In CallRuntimeWithCEntryImpl, line 26 counts the arguments; line 28–31 adds the arguments and context to the inputs array; line 33 calls the specific Runtime.

Note: Sea of Nodes is the prerequisite knowledge if you want to fully understand the principle of CallRuntimeImpl.

Debugging CallRuntimeImpl can only be done in the assembly environment, I have some experience and skills but is boring and complicated, if you want to know, plz contact me.

Okay, that wraps it up for this share. I’ll see you guys next time, take care!

My blog is cncyclops.com. Please reach out to me if you have any issues.

WeChat: qq9123013 Email: [email protected]

More content at PlainEnglish.io. Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, and Discord.