By Xu Lun, F(X)Team, Ali Tao Department
In previous articles, we introduced various manipulations at the AST level of JS. Once the AST is practiced, there is only one step left to execute, and that is to convert to intermediate code, either interpreted bytecode, or IR for the compiler.
Taking V8 as an example, let’s first take a look at v8’s operating architecture:
There are three unfamiliar names in the picture: Ignition, crankshaft and Turbofan.
Ignition is the V8 interpreter, Crankshaft is the older generation compiler, and TurboFan is the newer generation compiler. So what we call bytecode corresponds to igintion bytecode, and the compiled intermediate code is TurboFan IR. In this article, we’ll focus on Ignition bytecode.
Using the V8 D8 tool, we can easily see the ignition bytecode sequence by adding the –print-bytecode parameter:
./d8 --print-bytecode
Copy the code
If you don’t have a D8 tool, you can use your Node:
node --print-bytecode
Copy the code
So now we’re ready to go on a fun trip to ignition.
The accumulator loads instructions
Let’s start with the smallest statement.
undefined;
Copy the code
This is really small enough. Let’s look at the corresponding three bytecode instructions:
0x63e081d407a @ 0 : 0e LdaUndefined
0x63e081d407b @ 1 : c4 Star0
0x63e081d407c @ 2 : a9 Return
Copy the code
As a statement, it has a return value. This statement, of course, returns undefined.
Ld* belongs to the instruction set loaded into the accumulator. Star is register operation instruction. Return is the Return instruction.
When we type undefined again; “, because the bytecode has been converted, the bytecode will not be converted again, directly run the generated bytecode.
Also, for true, there is LdaTrue bytecode.
0x63e081d42d6 @ 0 : 11 LdaTrue
0x63e081d42d7 @ 1 : c4 Star0
0x63e081d42d8 @ 2 : a9 Return
Copy the code
Corresponding LdaNull null:
0x63e081d43e6 @ 0 : 0f LdaNull
0x63e081d43e7 @ 1 : c4 Star0
0x63e081d43e8 @ 2 : a9 Return
Copy the code
For 0, there is a special LdaZero directive:
0x63e081d4772 @ 0 : 0c LdaZero
0x63e081d4773 @ 1 : c4 Star0
0x63e081d4774 @ 2 : a9 Return
Copy the code
If it is 1, there is a LoadSmi directive:
0x63e081d4856 @ 0 : 0d 01 LdaSmi [1]
0x63e081d4858 @ 2 : c4 Star0
0x63e081d4859 @ 3 : a9 Return
Copy the code
LoadSmi is a two-byte instruction that is immediately followed by one byte of instruction code 0d. Let’s look at the case of -1:
0x63e081d493a @ 0 : 0d ff LdaSmi [-1]
0x63e081d493c @ 2 : c4 Star0
0x63e081d493d @ 3 : a9 Return
Copy the code
To load two bytes of immediate value, LdaSmi becomes the LDASmi. Wide instruction:
0x2c11081d556a @ 0 : 00 0d 10 27 LdaSmi.Wide [10000]
Copy the code
For the 4-byte case, there is also the Lda.ExtraWide directive. Such as:
let n1 = 100 _000_000;
Copy the code
The corresponding instruction is
0x2c11081d5456 @ 0 : 01 0d 00 e1 f5 05 LdaSmi.ExtraWide [100000000]
Copy the code
What if it was 1.1? The number 1.1 is stored on the heap, and the LdaConstant directive reads the value from the heap based on the index of the next byte.
Bytecode Age: 0 0x63e081d4a46 @ 0 : 13 00 LdaConstant [0] 0x63e081d4a48 @ 2 : c4 Star0 0x63e081d4a49 @ 3 : a9 Return Constant pool (size = 1) 0x63e081d4a0d: [FixedArray] in OldSpace - map: 0x063e08002209 <Map> - length: 1 0: 0 x063e081d4a19 < 1.1 > HeapNumberCopy the code
What if it’s 1 plus 1?
0x63e081d4c1a @ 0 : 0d 02 LdaSmi [2]
0x63e081d4c1c @ 2 : c4 Star0
0x63e081d4c1d @ 3 : a9 Return
Copy the code
By the time the bytecode is generated, the interpreter has already calculated the immediate number and does not waste any more instructions.
One interesting thing you should know is the 0.0 and -0.0 problem. For 0.0, the interpreter treats it as 0:
0x63e081d4e0a @ 0 : 0c LdaZero
0x63e081d4e0b @ 1 : c4 Star0
0x63e081d4e0c @ 2 : a9 Return
Copy the code
-0.0 is treated like a floating point constant, even though 0.0 is stored on the heap instead of -0.0:
0x63e081d4d26 @ 0 : 13 00 LdaConstant [0] 0x63e081d4d28 @ 2 : c4 Star0 0x63e081d4d29 @ 3 : a9 Return Constant pool (size = 1) 0x63e081d4ced: [FixedArray] in OldSpace - map: 0x063e08002209 <Map> - length: 1 0: 0 x063e081d4cf9 < 0.0 > HeapNumberCopy the code
Arithmetic instructions
Increment and decrement instructions
So let’s start introducing variables. For local scopes, this is similar to using only immediate numbers before. Such as:
{let a2=1};
Copy the code
Convert to bytecode as follows:
0x63e081d500e @ 0 : 0d 01 LdaSmi [1]
0x63e081d5010 @ 2 : c3 Star1
0x63e081d5011 @ 3 : 0e LdaUndefined
0x63e081d5012 @ 4 : a9 Return
Copy the code
If you define a variable globally:
let a1 = 0;
Copy the code
A new directive StaCurrentContextSlot is introduced:
0x63e081d4f06 @ 0 : 0c LdaZero
0x63e081d4f07 @ 1 : 25 02 StaCurrentContextSlot [2]
0x63e081d4f09 @ 3 : 0e LdaUndefined
0x63e081d4f0a @ 4 : a9 Return
Copy the code
All right, so let’s do some arithmetic. Let’s start with the increment operator
{let a3 = 1; a3 ++; }Copy the code
The corresponding instruction is Inc:
0x63e081d53ca @ 0 : 0d 01 LdaSmi [1]
0x63e081d53cc @ 2 : c3 Star1
0x63e081d53cd @ 3 : 75 00 ToNumeric [0]
0x63e081d53cf @ 5 : c2 Star2
0x63e081d53d0 @ 6 : 51 00 Inc [0]
0x63e081d53d2 @ 8 : c3 Star1
0x63e081d53d3 @ 9 : 19 f8 fa Mov r2, r0
0x63e081d53d6 @ 12 : 0b fa Ldar r0
0x63e081d53d8 @ 14 : a9 Return
Copy the code
It is worth noting that the current value is converted to a numeric type using the ToNumberic instruction before the arithmetic operation can be performed.
For the decrement operator
{let a4 = 100; a4--};
Copy the code
It was just replaced by Dec:
0x63e081d54da @ 0 : 0d 64 LdaSmi [100]
0x63e081d54dc @ 2 : c3 Star1
0x63e081d54dd @ 3 : 75 00 ToNumeric [0]
0x63e081d54df @ 5 : c2 Star2
0x63e081d54e0 @ 6 : 52 00 Dec [0]
0x63e081d54e2 @ 8 : c3 Star1
0x63e081d54e3 @ 9 : 19 f8 fa Mov r2, r0
0x63e081d54e6 @ 12 : 0b fa Ldar r0
0x63e081d54e8 @ 14 : a9 Return
Copy the code
Binary arithmetic operator
Let’s start with an additive:
{let a5 = 2; a5 = a5 + 2; }Copy the code
The AddSmi directive:
0x63e081d5712 @ 0 : 0d 02 LdaSmi [2]
0x63e081d5714 @ 2 : c3 Star1
0x63e081d5715 @ 3 : 45 02 00 AddSmi [2], [0]
0x63e081d5718 @ 6 : c3 Star1
0x63e081d5719 @ 7 : c4 Star0
0x63e081d571a @ 8 : a9 Return
Copy the code
If not for immediate numbers, but for two variables:
{let a6 = 1; let a7=2; let a8 = a6 + a7; }Copy the code
This is replaced with the Add command:
0x63e081d583a @ 0 : 0d 01 LdaSmi [1]
0x63e081d583c @ 2 : c3 Star1
0x63e081d583d @ 3 : 0d 02 LdaSmi [2]
0x63e081d583f @ 5 : c2 Star2
0x63e081d5840 @ 6 : 0b f8 Ldar r2
0x63e081d5842 @ 8 : 39 f9 00 Add r1, [0]
0x63e081d5845 @ 11 : c1 Star3
0x63e081d5846 @ 12 : 0e LdaUndefined
0x63e081d5847 @ 13 : a9 Return
Copy the code
Constants and variables
What instructions would be generated if we operated on constants:
const a11 = 0; a11 +=1;
Copy the code
We keep constant, or use the StaCurrentContextSlot instructions, but when loaded with LdaImmutableCurrentContextSlot, against AddSmi instructions, CallRuntime is called to invoke the runtime ThrowConstAssignError exception.
0x63e081d5b7a @ 0 : 0c LdaZero
0x63e081d5b7b @ 1 : 25 02 StaCurrentContextSlot [2]
0x63e081d5b7d @ 3 : 17 02 LdaImmutableCurrentContextSlot [2]
0x63e081d5b7f @ 5 : 45 01 00 AddSmi [1], [0]
0x63e081d5b82 @ 8 : 65 66 01 fa 00 CallRuntime [ThrowConstAssignError], r0-r0
0x63e081d5b87 @ 13 : c4 Star0
0x63e081d5b88 @ 14 : a9 Return
Copy the code
When we write a variable declared by var at the top level, it will be converted to a global variable:
var a13 = 0;
Copy the code
This still has runtime DeclareGlobals involved and is saved as a global variable via StaGlobal:
0x63e081d5cda @ 0 : 13 00 LdaConstant [0]
0x63e081d5cdc @ 2 : c3 Star1
0x63e081d5cdd @ 3 : 19 fe f8 Mov <closure>, r2
0x63e081d5ce0 @ 6 : 65 54 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
0x63e081d5ce5 @ 11 : 0c LdaZero
0x63e081d5ce6 @ 12 : 23 01 00 StaGlobal [1], [0]
0x63e081d5ce9 @ 15 : 0e LdaUndefined
0x63e081d5cea @ 16 : a9 Return
Copy the code
literal
The Ignition directive provides three literal-created directives:
- CreateArrayLiteral
- CreateObjectLateral
- CreateRegExpLiteral
This is easy to understand, for [], {}, // three values.
Let’s look at a couple of examples.
- An empty array:
let a1 = [];
Copy the code
As a common operation, Ignition provides a special instruction for it: CreateEmptyArrayLiteral
0x24b2081d3386 @ 0 : 7b 00 CreateEmptyArrayLiteral [0]
0x24b2081d3388 @ 2 : 25 02 StaCurrentContextSlot [2]
0x24b2081d338a @ 4 : 0e LdaUndefined
0x24b2081d338b @ 5 : a9 Return
Copy the code
- An empty object
let a2={};
Copy the code
Ignition provides a specialized instruction for this: CreateEmptyObjectLiteral
0x24b2081d411e @ 0 : 7d CreateEmptyObjectLiteral
0x24b2081d411f @ 1 : 25 02 StaCurrentContextSlot [2]
0x24b2081d4121 @ 3 : 0e LdaUndefined
0x24b2081d4122 @ 4 : a9 Return
Copy the code
- Regular expression
let a3 = /a*/;
Copy the code
It’s not interesting to have a regular with an empty one, let’s have a valuable one. As you can see, the literal ends up converting the string on the heap.
Bytecode Age: 0
0x24b2081d42e2 @ 0 : 78 00 00 00 CreateRegExpLiteral [0], [0], #0
0x24b2081d42e6 @ 4 : 25 02 StaCurrentContextSlot [2]
0x24b2081d42e8 @ 6 : 0e LdaUndefined
0x24b2081d42e9 @ 7 : a9 Return
Constant pool (size = 1)
0x24b2081d42b5: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 1
0: 0x24b2081d424d <String[2]: #a*>
Copy the code
Property access
Igintion provides LdaNamedProperty and StaNamedProperty directives to access properties by name.
Let’s look at an example:
let a4 = {}; a4.a = 1;
Copy the code
The generated instructions are as follows:
0x24b2081d4412 @ 0 : 7d CreateEmptyObjectLiteral
0x24b2081d4413 @ 1 : 25 02 StaCurrentContextSlot [2]
0x24b2081d4415 @ 3 : 16 02 LdaCurrentContextSlot [2]
0x24b2081d4417 @ 5 : c3 Star1
0x24b2081d4418 @ 6 : 0d 01 LdaSmi [1]
0x24b2081d441a @ 8 : c2 Star2
0x24b2081d441b @ 9 : 32 f9 00 00 StaNamedProperty r1, [0], [0]
0x24b2081d441f @ 13 : 19 f8 fa Mov r2, r0
0x24b2081d4422 @ 16 : 0b fa Ldar r0
0x24b2081d4424 @ 18 : a9 Return
Constant pool (size = 1)
0x24b2081d43e5: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 1
0: 0x24b208008265 <String[1]: #a>
Copy the code
Note that A4 is not present because we are using Current Context Slot.
Read it again:
a4['a'];
Copy the code
In this case, A4 should be passed to LdaNamedProperty as a parameter.
Bytecode Age: 0
0x24b2081d466a @ 0 : 21 00 00 LdaGlobal [0], [0]
0x24b2081d466d @ 3 : c3 Star1
0x24b2081d466e @ 4 : 2d f9 01 02 LdaNamedProperty r1, [1], [2]
0x24b2081d4672 @ 8 : c4 Star0
0x24b2081d4673 @ 9 : a9 Return
Constant pool (size = 2)
0x24b2081d4639: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 2
0: 0x24b2081d437d <String[2]: #a4>
1: 0x24b208008265 <String[1]: #a>
Copy the code
Closure creation and invocation
The function is created by the CreateClosure directive.
Let’s take an example of an arrow function:
let f1 = () = > 0;
Copy the code
The generated instructions are as follows:
Bytecode Age: 0
0x24b2081d481a @ 0 : 80 00 00 00 CreateClosure [0], [0], #0
0x24b2081d481e @ 4 : 25 02 StaCurrentContextSlot [2]
0x24b2081d4820 @ 6 : 0e LdaUndefined
0x24b2081d4821 @ 7 : a9 Return
Constant pool (size = 1)
0x24b2081d47ed: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 1
0: 0x24b2081d47b9 <SharedFunctionInfo f1>
Copy the code
The normal function is also CreateClosure:
let f2 = function(x) { returnx * x; }Copy the code
The bytecode is exactly the same as above:
Bytecode Age: 0
0x24b2081d4996 @ 0 : 80 00 00 00 CreateClosure [0], [0], #0
0x24b2081d499a @ 4 : 25 02 StaCurrentContextSlot [2]
0x24b2081d499c @ 6 : 0e LdaUndefined
0x24b2081d499d @ 7 : a9 Return
Constant pool (size = 1)
0x24b2081d4969: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 1
0: 0x24b2081d4935 <SharedFunctionInfo f2>
Copy the code
What about the code for the function? Only seen when called. The directive called is CallUndefinedReceiver*, CallUndefinedReceiver0 without arguments.
f1();
Copy the code
The bytecode for F1 is printed separately after the call.
[generated bytecode for function: (0x24b2081d4a51 <SharedFunctionInfo>)]
...
Bytecode Age: 0
0x24b2081d4ac2 @ 0 : 21 00 00 LdaGlobal [0], [0]
0x24b2081d4ac5 @ 3 : c3 Star1
0x24b2081d4ac6 @ 4 : 61 f9 02 CallUndefinedReceiver0 r1, [2]
0x24b2081d4ac9 @ 7 : c4 Star0
0x24b2081d4aca @ 8 : a9 Return
...
[generated bytecode for function: f1 (0x24b2081d47b9 <SharedFunctionInfo f1>)]
...
Bytecode Age: 0
0x24b2081d4b52 @ 0 : 0c LdaZero
0x24b2081d4b53 @ 1 : a9 Return
Copy the code
Let’s look at f2:
f2(1.1);
Copy the code
Since f2 has one argument, the directive called is CallUndefinedReceiver1:
[generated bytecode for function: (0x24b2081d4c49 <SharedFunctionInfo>)]
...
Bytecode Age: 0
0x24b2081d4cca @ 0 : 21 00 00 LdaGlobal [0], [0]
0x24b2081d4ccd @ 3 : c3 Star1
0x24b2081d4cce @ 4 : 13 01 LdaConstant [1]
0x24b2081d4cd0 @ 6 : c2 Star2
0x24b2081d4cd1 @ 7 : 62 f9 f8 02 CallUndefinedReceiver1 r1, r2, [2]
0x24b2081d4cd5 @ 11 : c4 Star0
0x24b2081d4cd6 @ 12 : a9 Return
...
[generated bytecode for function: f2 (0x24b2081d4935 <SharedFunctionInfo f2>)]
...
Bytecode Age: 0
0x24b2081d4d5e @ 0 : 0b 03 Ldar a0
0x24b2081d4d60 @ 2 : 3b 03 00 Mul a0, [0]
0x24b2081d4d63 @ 5 : a9 Return
Copy the code
Branch jump instruction
The branch instruction is divided into two parts, one is to set the flag bit of Test class, the other is to jump according to the flag bit.
Let’s look at an example:
let f3 = (x) = > {if (x>0) return 0; else return -1; }; f3(0);
Copy the code
We’ll just extract the function body, which consists of TestGreaterThan and JumpIfFalse. JumpIfFalse yes If it is true, the execution continues; if it is false, the execution jumps.
0x24b2081d561a @ 0 : 0c LdaZero
0x24b2081d561b @ 1 : 6e 03 00 TestGreaterThan a0, [0]
0x24b2081d561e @ 4 : 99 04 JumpIfFalse [4] (0x24b2081d5622 @ 8)
0x24b2081d5620 @ 6 : 0c LdaZero
0x24b2081d5621 @ 7 : a9 Return
0x24b2081d5622 @ 8 : 0d ff LdaSmi [-1]
0x24b2081d5624 @ 10 : a9 Return
Copy the code
Other than the if statement, like “? Operators such as.” also produce branch judgments. Let’s look at examples:
let f5 = (x) = > {returnx? .length; }; f5(null);
Copy the code
The airlift operator corresponds to the JumpIfUndefinedOrNull directive.
Bytecode Age: 0
0x24b2081d6766 @ 0 : 0b 03 Ldar a0
0x24b2081d6768 @ 2 : 19 03 fa Mov a0, r0
0x24b2081d676b @ 5 : 9e 08 JumpIfUndefinedOrNull [8] (0x24b2081d6773 @ 13)
0x24b2081d676d @ 7 : 2d fa 00 00 LdaNamedProperty r0, [0], [0]
0x24b2081d6771 @ 11 : 8a 03 Jump [3] (0x24b2081d6774 @ 14)
0x24b2081d6773 @ 13 : 0e LdaUndefined
0x24b2081d6774 @ 14 : a9 Return
Constant pool (size = 1)
0x24b2081d6739: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 1
0: 0x24b208004cb1 <String[6]: #length>
Copy the code
Our own nullation does not generate this command:
let f6 = (x) = > {return x===null || x===undefined}; f6(0);
Copy the code
TestNull and TestUndefined directives are generated directly:
0x24b2081d695a @ 0 : 0b 03 Ldar a0
0x24b2081d695c @ 2 : 1e TestNull
0x24b2081d695d @ 3 : 98 05 JumpIfTrue [5] (0x24b2081d6962 @ 8)
0x24b2081d695f @ 5 : 0b 03 Ldar a0
0x24b2081d6961 @ 7 : 1f TestUndefined
0x24b2081d6962 @ 8 : a9 Return
Copy the code
Similarly, the Nullish operator is a branch statement:
let a2 = a1 ?? 100;
Copy the code
Also an implementation of JumpIfUndefinedOrNull, which looks like the same author:
0x2c11081d409e @ 0 : 21 00 00 LdaGlobal [0], [0]
0x2c11081d40a1 @ 3 : 9e 04 JumpIfUndefinedOrNull [4] (0x2c11081d40a5 @ 7)
0x2c11081d40a3 @ 5 : 8a 04 Jump [4] (0x2c11081d40a7 @ 9)
0x2c11081d40a5 @ 7 : 0d 64 LdaSmi [100]
0x2c11081d40a7 @ 9 : 25 02 StaCurrentContextSlot [2]
Copy the code
Handle exceptions
Exceptions are branch instructions in another sense, but involve exception context.
try{ a4.a = 1; } catch(e) {};Copy the code
CreateCatchContext provides the ability to create an exception context and then switch the context by PushContext and PopContext.
Bytecode Age: 0
0x24b2081d60c2 @ 0 : 0e LdaUndefined
0x24b2081d60c3 @ 1 : c4 Star0
0x24b2081d60c4 @ 2 : 19 ff f9 Mov <context>, r1
0x24b2081d60c7 @ 5 : 21 00 00 LdaGlobal [0], [0]
0x24b2081d60ca @ 8 : c2 Star2
0x24b2081d60cb @ 9 : 0d 01 LdaSmi [1]
0x24b2081d60cd @ 11 : c1 Star3
0x24b2081d60ce @ 12 : 32 f8 01 02 StaNamedProperty r2, [1], [2]
0x24b2081d60d2 @ 16 : 19 f7 fa Mov r3, r0
0x24b2081d60d5 @ 19 : 0b f7 Ldar r3
0x24b2081d60d7 @ 21 : 8a 0f Jump [15] (0x24b2081d60e6 @ 36)
0x24b2081d60d9 @ 23 : c2 Star2
0x24b2081d60da @ 24 : 82 f8 02 CreateCatchContext r2, [2]
0x24b2081d60dd @ 27 : c3 Star1
0x24b2081d60de @ 28 : 10 LdaTheHole
0x24b2081d60df @ 29 : a6 SetPendingMessage
0x24b2081d60e0 @ 30 : 0b f9 Ldar r1
0x24b2081d60e2 @ 32 : 1a f8 PushContext r2
0x24b2081d60e4 @ 34 : 1b f8 PopContext r2
0x24b2081d60e6 @ 36 : 0b fa Ldar r0
0x24b2081d60e8 @ 38 : a9 Return
Constant pool (size = 3)
0x24b2081d608d: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 3
0: 0x24b2081d437d <String[2]: #a4>
1: 0x24b208008265 <String[1]: #a>
2: 0x24b2081d603d <ScopeInfo CATCH_SCOPE>
Handler Table (size = 16)
from to hdlr (prediction, data)
( 5, 19) -> 23 (prediction=1, data=1)
Copy the code
It’s also the first time we’ve seen an order like LdaTheHole that involves an internal mechanism. According to a student on the V8 team, this hole is a sentinel for the system to do some checking work, there are several possibilities, to distinguish the specific case. The following article on the source code when carefully analyzed.
If we use the new optional catch feature in ES2019, the CreateCatchContext will not be generated. The generated code looks like this:
Bytecode Age: 0
0x2c11081d5b3a @ 0 : 0e LdaUndefined
0x2c11081d5b3b @ 1 : c4 Star0
0x2c11081d5b3c @ 2 : 19 ff f9 Mov <context>, r1
0x2c11081d5b3f @ 5 : 21 00 00 LdaGlobal [0], [0]
0x2c11081d5b42 @ 8 : c2 Star2
0x2c11081d5b43 @ 9 : 0d 01 LdaSmi [1]
0x2c11081d5b45 @ 11 : c1 Star3
0x2c11081d5b46 @ 12 : 32 f8 01 02 StaNamedProperty r2, [1], [2]
0x2c11081d5b4a @ 16 : 19 f7 fa Mov r3, r0
0x2c11081d5b4d @ 19 : 0b f7 Ldar r3
0x2c11081d5b4f @ 21 : 8a 06 Jump [6] (0x2c11081d5b55 @ 27)
0x2c11081d5b51 @ 23 : 10 LdaTheHole
0x2c11081d5b52 @ 24 : a6 SetPendingMessage
0x2c11081d5b53 @ 25 : 0b f9 Ldar r1
0x2c11081d5b55 @ 27 : 0b fa Ldar r0
0x2c11081d5b57 @ 29 : a9 Return
Constant pool (size = 2)
0x2c11081d5b09: [FixedArray] in OldSpace
- map: 0x2c1108002209 <Map>
- length: 2
0: 0x2c11081d413d <String[2]: #a4>
1: 0x2c1108008265 <String[1]: #a>
Handler Table (size = 16)
from to hdlr (prediction, data)
( 5, 19) -> 23 (prediction=1, data=1)
Copy the code
Cycle instruction
Let’s start with a traditional C-like for loop:
let f4 = (x) = > {for(let i=0; i<x; i++) {console.log(i); }}; f4(10);
Copy the code
It is implemented in much the same way as branch statements, except for a JumpLoop directive.
0x24b2081d582a @ 0 : 0c LdaZero
0x24b2081d582b @ 1 : c4 Star0
0x24b2081d582c @ 2 : 0b 03 Ldar a0
0x24b2081d582e @ 4 : 6d fa 00 TestLessThan r0, [0]
0x24b2081d5831 @ 7 : 99 18 JumpIfFalse [24] (0x24b2081d5849 @ 31)
0x24b2081d5833 @ 9 : 21 00 01 LdaGlobal [0], [1]
0x24b2081d5836 @ 12 : c2 Star2
0x24b2081d5837 @ 13 : 2d f8 01 03 LdaNamedProperty r2, [1], [3]
0x24b2081d583b @ 17 : c3 Star1
0x24b2081d583c @ 18 : 5e f9 f8 fa 05 CallProperty1 r1, r2, r0, [5]
0x24b2081d5841 @ 23 : 0b fa Ldar r0
0x24b2081d5843 @ 25 : 51 07 Inc [7]
0x24b2081d5845 @ 27 : c4 Star0
0x24b2081d5846 @ 28 : 89 1a 00 JumpLoop [26], [0] (0x24b2081d582c @ 2)
Copy the code
Let’s look at what’s behind an iteration over an array:
let a21 = [1.2.3.4.5];
let sum=0;
for(let i ofa21) {sum+=i; }Copy the code
For iterator mode, Ignition provides the GetIterator instruction, followed by handling of various exceptions:
0x24b2081d5dc2 @ 0 : 0e LdaUndefined
0x24b2081d5dc3 @ 1 : c3 Star1
0x24b2081d5dc4 @ 2 : 21 00 00 LdaGlobal [0], [0]
0x24b2081d5dc7 @ 5 : be Star6
0x24b2081d5dc8 @ 6 : b1 f4 02 04 GetIterator r6, [2], [4]
0x24b2081d5dcc @ 10 : 9f 07 JumpIfJSReceiver [7] (0x24b2081d5dd3 @ 17)
0x24b2081d5dce @ 12 : 65 c3 00 fa 00 CallRuntime [ThrowSymbolIteratorInvalid], r0-r0
0x24b2081d5dd3 @ 17 : bf Star5
0x24b2081d5dd4 @ 18 : 2d f5 01 06 LdaNamedProperty r5, [1], [6]
0x24b2081d5dd8 @ 22 : c0 Star4
0x24b2081d5dd9 @ 23 : 12 LdaFalse
0x24b2081d5dda @ 24 : be Star6
0x24b2081d5ddb @ 25 : 19 ff f1 Mov <context>, r9
0x24b2081d5dde @ 28 : 11 LdaTrue
0x24b2081d5ddf @ 29 : be Star6
0x24b2081d5de0 @ 30 : 5d f6 f5 08 CallProperty0 r4, r5, [8]
0x24b2081d5de4 @ 34 : ba Star10
0x24b2081d5de5 @ 35 : 9f 07 JumpIfJSReceiver [7] (0x24b2081d5dec @ 42)
0x24b2081d5de7 @ 37 : 65 bb 00 f0 01 CallRuntime [ThrowIteratorResultNotAnObject], r10-r10
0x24b2081d5dec @ 42 : 2d f0 02 0a LdaNamedProperty r10, [2], [10]
0x24b2081d5df0 @ 46 : 96 27 JumpIfToBooleanTrue [39] (0x24b2081d5e17 @ 85)
0x24b2081d5df2 @ 48 : 2d f0 03 0c LdaNamedProperty r10, [3], [12]
0x24b2081d5df6 @ 52 : ba Star10
0x24b2081d5df7 @ 53 : 12 LdaFalse
0x24b2081d5df8 @ 54 : be Star6
0x24b2081d5df9 @ 55 : 19 f0 fa Mov r10, r0
0x24b2081d5dfc @ 58 : 19 fa f7 Mov r0, r3
0x24b2081d5dff @ 61 : 21 04 0e LdaGlobal [4], [14]
0x24b2081d5e02 @ 64 : b9 Star11
0x24b2081d5e03 @ 65 : 0b fa Ldar r0
0x24b2081d5e05 @ 67 : 39 ef 10 Add r11, [16]
0x24b2081d5e08 @ 70 : b8 Star12
0x24b2081d5e09 @ 71 : 23 04 11 StaGlobal [4], [17]
0x24b2081d5e0c @ 74 : 19 ee f9 Mov r12, r1
0x24b2081d5e0f @ 77 : 19 f7 f0 Mov r3, r10
0x24b2081d5e12 @ 80 : 0b f9 Ldar r1
0x24b2081d5e14 @ 82 : 89 36 00 JumpLoop [54], [0] (0x24b2081d5dde @ 28)
0x24b2081d5e17 @ 85 : 0d ff LdaSmi [-1]
0x24b2081d5e19 @ 87 : bc Star8
0x24b2081d5e1a @ 88 : bd Star7
0x24b2081d5e1b @ 89 : 8a 05 Jump [5] (0x24b2081d5e20 @ 94)
0x24b2081d5e1d @ 91 : bc Star8
0x24b2081d5e1e @ 92 : 0c LdaZero
0x24b2081d5e1f @ 93 : bd Star7
0x24b2081d5e20 @ 94 : 10 LdaTheHole
0x24b2081d5e21 @ 95 : a6 SetPendingMessage
0x24b2081d5e22 @ 96 : bb Star9
0x24b2081d5e23 @ 97 : 0b f4 Ldar r6
0x24b2081d5e25 @ 99 : 96 23 JumpIfToBooleanTrue [35] (0x24b2081d5e48 @ 134)
0x24b2081d5e27 @ 101 : 19 ff ef Mov <context>, r11
0x24b2081d5e2a @ 104 : 2d f5 05 13 LdaNamedProperty r5, [5], [19]
0x24b2081d5e2e @ 108 : 9e 1a JumpIfUndefinedOrNull [26] (0x24b2081d5e48 @ 134)
0x24b2081d5e30 @ 110 : b8 Star12
0x24b2081d5e31 @ 111 : 5d ee f5 15 CallProperty0 r12, r5, [21]
0x24b2081d5e35 @ 115 : 9f 13 JumpIfJSReceiver [19] (0x24b2081d5e48 @ 134)
0x24b2081d5e37 @ 117 : b7 Star13
0x24b2081d5e38 @ 118 : 65 bb 00 ed 01 CallRuntime [ThrowIteratorResultNotAnObject], r13-r13
0x24b2081d5e3d @ 123 : 8a 0b Jump [11] (0x24b2081d5e48 @ 134)
0x24b2081d5e3f @ 125 : b9 Star11
0x24b2081d5e40 @ 126 : 0c LdaZero
0x24b2081d5e41 @ 127 : 1c f3 TestReferenceEqual r7
0x24b2081d5e43 @ 129 : 98 05 JumpIfTrue [5] (0x24b2081d5e48 @ 134)
0x24b2081d5e45 @ 131 : 0b ef Ldar r11
0x24b2081d5e47 @ 133 : a8 ReThrow
0x24b2081d5e48 @ 134 : 0b f1 Ldar r9
0x24b2081d5e4a @ 136 : a6 SetPendingMessage
0x24b2081d5e4b @ 137 : 0c LdaZero
0x24b2081d5e4c @ 138 : 1c f3 TestReferenceEqual r7
0x24b2081d5e4e @ 140 : 99 05 JumpIfFalse [5] (0x24b2081d5e53 @ 145)
0x24b2081d5e50 @ 142 : 0b f2 Ldar r8
0x24b2081d5e52 @ 144 : a8 ReThrow
0x24b2081d5e53 @ 145 : 0b f9 Ldar r1
0x24b2081d5e55 @ 147 : a9 Return
Copy the code
We’ll talk about the details of this later, just to give you a sense of it.
class
Let’s define an empty class and see what happens:
class A {};
Copy the code
As you can see from generating the CreateClosure directive, the system generates a constructor for us by default.
Bytecode Age: 0
0x24b2081d6b32 @ 0 : 81 00 CreateBlockContext [0]
0x24b2081d6b34 @ 2 : 1a f9 PushContext r1
0x24b2081d6b36 @ 4 : 10 LdaTheHole
0x24b2081d6b37 @ 5 : bf Star5
0x24b2081d6b38 @ 6 : 80 02 00 00 CreateClosure [2], [0], #0
0x24b2081d6b3c @ 10 : c2 Star2
0x24b2081d6b3d @ 11 : 13 01 LdaConstant [1]
0x24b2081d6b3f @ 13 : c1 Star3
0x24b2081d6b40 @ 14 : 19 f8 f6 Mov r2, r4
0x24b2081d6b43 @ 17 : 65 25 00 f7 03 CallRuntime [DefineClass], r3-r5
0x24b2081d6b48 @ 22 : c1 Star3
0x24b2081d6b49 @ 23 : 1b f9 PopContext r1
0x24b2081d6b4b @ 25 : 0b f6 Ldar r4
0x24b2081d6b4d @ 27 : 25 02 StaCurrentContextSlot [2]
0x24b2081d6b4f @ 29 : 0e LdaUndefined
0x24b2081d6b50 @ 30 : a9 Return
Constant pool (size = 3)
0x24b2081d6afd: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 3
0: 0x24b2081d6a11 <ScopeInfo CLASS_SCOPE>
1: 0x24b2081d6ad9 <FixedArray[7]>
2: 0x24b2081d6a25 <SharedFunctionInfo A>
Copy the code
Let’s create a new object:
let a100 = new A();
Copy the code
As you can see, the new operator is translated into the Construct instruction:
Bytecode Age: 0
0x24b2081d6ee2 @ 0 : 21 00 00 LdaGlobal [0], [0]
0x24b2081d6ee5 @ 3 : c3 Star1
0x24b2081d6ee6 @ 4 : 69 f9 fa 00 02 Construct r1, r0-r0, [2]
0x24b2081d6eeb @ 9 : 25 02 StaCurrentContextSlot [2]
0x24b2081d6eed @ 11 : 0e LdaUndefined
0x24b2081d6eee @ 12 : a9 Return
Constant pool (size = 1)
0x24b2081d6eb5: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 1
0: 0x24b2081d69a5 <String[1]: #A>
Copy the code
Implementation of some new features
BigInt
As mentioned earlier, LdaSmi accepts up to 4 bytes of immediate numbers, any more of which become floating point numbers. ES2020 gives us BigInt.
Let’s see how the interpreter can help us with this by adding an “n” to the end:
let la = 123n;
Copy the code
The system directly handles it for us as FixedArray:
Bytecode Age: 0
0x24b2081d7ada @ 0 : 13 00 LdaConstant [0]
0x24b2081d7adc @ 2 : 25 02 StaCurrentContextSlot [2]
0x24b2081d7ade @ 4 : 0e LdaUndefined
0x24b2081d7adf @ 5 : a9 Return
Constant pool (size = 1)
0x24b2081d7a9d: [FixedArray] in OldSpace
- map: 0x24b208002209 <Map>
- length: 1
0: 0x24b2081d7aa9 <BigInt 123>
Copy the code
Class private variable
As we know, starting with ES2020, we can define private variables with # :
class A3{
#priv_data = 100
get_data(){
return this.#priv_data; }}let a3 = new A3();
a3.get_data();
Copy the code
The v8 solution is to pass this to Runtime and call Runtime CreatePrivateNameSymbol: CreatePrivateNameSymbol
Bytecode Age: 0
0x27b1081d352e @ 0 : 81 00 CreateBlockContext [0]
0x27b1081d3530 @ 2 : 1a f9 PushContext r1
0x27b1081d3532 @ 4 : 13 02 LdaConstant [2]
0x27b1081d3534 @ 6 : c1 Star3
0x27b1081d3535 @ 7 : 13 02 LdaConstant [2]
0x27b1081d3537 @ 9 : c1 Star3
0x27b1081d3538 @ 10 : 65 78 01 f7 01 CallRuntime [CreatePrivateNameSymbol], r3-r3
0x27b1081d353d @ 15 : 25 02 StaCurrentContextSlot [2]
0x27b1081d353f @ 17 : 10 LdaTheHole
0x27b1081d3540 @ 18 : bf Star5
0x27b1081d3541 @ 19 : 80 03 00 00 CreateClosure [3], [0], #0
0x27b1081d3545 @ 23 : c2 Star2
0x27b1081d3546 @ 24 : 13 01 LdaConstant [1]
0x27b1081d3548 @ 26 : c1 Star3
0x27b1081d3549 @ 27 : 80 04 01 00 CreateClosure [4], [1], #0
0x27b1081d354d @ 31 : be Star6
0x27b1081d354e @ 32 : 19 f8 f6 Mov r2, r4
0x27b1081d3551 @ 35 : 65 25 00 f7 04 CallRuntime [DefineClass], r3-r6
0x27b1081d3556 @ 40 : c1 Star3
0x27b1081d3557 @ 41 : 80 05 02 00 CreateClosure [5], [2], #0
0x27b1081d355b @ 45 : c0 Star4
0x27b1081d355c @ 46 : 32 f8 06 00 StaNamedProperty r2, [6], [0]
0x27b1081d3560 @ 50 : 1b f9 PopContext r1
0x27b1081d3562 @ 52 : 0b f8 Ldar r2
0x27b1081d3564 @ 54 : 25 02 StaCurrentContextSlot [2]
0x27b1081d3566 @ 56 : 16 02 LdaCurrentContextSlot [2]
0x27b1081d3568 @ 58 : c3 Star1
0x27b1081d3569 @ 59 : 69 f9 fa 00 02 Construct r1, r0-r0, [2]
0x27b1081d356e @ 64 : 25 03 StaCurrentContextSlot [3]
0x27b1081d3570 @ 66 : 16 03 LdaCurrentContextSlot [3]
0x27b1081d3572 @ 68 : c2 Star2
0x27b1081d3573 @ 69 : 2d f8 07 04 LdaNamedProperty r2, [7], [4]
0x27b1081d3577 @ 73 : c3 Star1
0x27b1081d3578 @ 74 : 5d f9 f8 06 CallProperty0 r1, r2, [6]
0x27b1081d357c @ 78 : c4 Star0
0x27b1081d357d @ 79 : a9 Return
Constant pool (size = 8)
0x27b1081d34e5: [FixedArray] in OldSpace
- map: 0x27b108002209 <Map>
- length: 8
0: 0x27b1081d3365 <ScopeInfo CLASS_SCOPE>
1: 0x27b1081d34c1 <FixedArray[7]>
2: 0x27b1081d3291 <String[10]: ##priv_data>
3: 0x27b1081d33a9 <SharedFunctionInfo A3>
4: 0x27b1081d33dd <SharedFunctionInfo get_data>
5: 0x27b1081d3411 <SharedFunctionInfo <instance_members_initializer>>
6: 0x27b108005895 <Symbol: (class_fields_symbol)>
7: 0x27b1081d32a9 <String[8]: #get_data>
Copy the code
TurboFan IR
Ignition’s bytecode isn’t the only intermediate code in V8; there’s Also TurboFan’s IR.
I’m running out of space to post a picture of TurboFan IR:
Using the Arithmetic IR JSAdd as an example, let’s take a look at the TurboFan IR architecture:
summary
In this article, we take a quick look at Ignition blindness.
JVM Bytecode:
-
So Ignition is a very rich set of instructions, you know, nullify instructions, GetIterator instructions, things like that
-
It relies heavily on CallRuntime and has more functions than CallRuntime. With the upgrade of ES, run-time functions have been expanded. For example, while ES2019 added DynamicImport, V8 added a DynamicImportCall Runtime function without modifying the instruction set
-
There are directives such as LdaTheHole that serve internal state
-
If there is an error at run time, instructions such as throwing an error are generated, and the error information is retrieved at run time
Tao department front – F-X-team opened a weibo! (Visible after microblog recording)In addition to the article there is more team content to unlock 🔓