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.

  1. 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
  1. 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
  1. 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 🔓