×
Community Blog JavaScript Bytecode – v8 Ignition Instructions

JavaScript Bytecode – v8 Ignition Instructions

This article discusses JavaScript bytecode, specifically basic information about Ignition bytecode.

By Xulun, from F(x) Team

Various operation methods at the AST level of JS were introduced in the previous articles. After mastering AST, we are one step away from execution, which is the conversion into the intermediate code, the interpreted bytecode, or IR prepared for a compiler.

Let's take v8 as an example. First is the operating architecture:

1

There are three unfamiliar proper nouns in this diagram: Ignition, Crankshaft, and TurboFan.

Ignition is the interpreter of v8, Crankshaft is the compiler of the older generation, and Turbofan is the newer one. The bytecode in discussion corresponds to the Ignition bytecode, and the compiled intermediate code is TurboFan IR. Ignition bytecode is the focus of the article.

We can see the sequence of Ignition bytecode by using the d8 tool of v8. Just add the --print-bytecode parameter:

./d8 --print-bytecode

If there are no d8 tools on hand, a node is acceptable:

node --print-bytecode

Now, we are ready for Ignition bytecode.

Accumulator Load Instruction

Let's start with the smallest statement.

undefined;

This is small enough. Let's look at its corresponding three bytecode instructions:

         0x63e081d407a @    0 : 0e                LdaUndefined 
         0x63e081d407b @    1 : c4                Star0 
         0x63e081d407c @    2 : a9                Return 

As a statement, it has its return value. This statement is returning undefined.

Ld* belongs to the instruction set loaded into the accumulator. Star is a register operation instruction. Return is the return instruction.

When we enter undefined; again, as the bytecode has been converted, the conversion will not be done again. The previously generated bytecode will be run directly.

Similarly, there is LdaTrue bytecode for true.

         0x63e081d42d6 @    0 : 11                LdaTrue 
         0x63e081d42d7 @    1 : c4                Star0 
         0x63e081d42d8 @    2 : a9                Return 

null corresponds to LdaNull:

         0x63e081d43e6 @    0 : 0f                LdaNull 
         0x63e081d43e7 @    1 : c4                Star0 
         0x63e081d43e8 @    2 : a9                Return 

There is a special LdaZero instruction for 0:

         0x63e081d4772 @    0 : 0c                LdaZero 
         0x63e081d4773 @    1 : c4                Star0 
         0x63e081d4774 @    2 : a9                Return 

If it is 1, there will be a LoadSmi instruction:

         0x63e081d4856 @    0 : 0d 01             LdaSmi [1]
         0x63e081d4858 @    2 : c4                Star0 
         0x63e081d4859 @    3 : a9                Return

LoadSmi is a two-byte instruction. The instruction code 0d is followed by an immediate number of bytes. Let's look at what will happen when it is -1:

         0x63e081d493a @    0 : 0d ff             LdaSmi [-1]
         0x63e081d493c @    2 : c4                Star0 
         0x63e081d493d @    3 : a9                Return 

If you want to load an immediate number of two bytes, LdaSmi will become the LdaSmi.Wide instruction:

         0x2c11081d556a @    0 : 00 0d 10 27       LdaSmi.Wide [10000]

There is also the Lda.ExtraWide instruction for the case of 4 bytes. For example,

let n1 = 100_000_000;

The corresponding instruction is:

         0x2c11081d5456 @    0 : 01 0d 00 e1 f5 05 LdaSmi.ExtraWide [100000000]

What if it's 1.1? At this time, the instruction does not fit. The number 1.1 is stored on the heap, and the LdaConstant instruction reads this value from the heap according to 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: 0x063e081d4a19 <HeapNumber 1.1>

What if it's 1+1?:

         0x63e081d4c1a @    0 : 0d 02             LdaSmi [2]
         0x63e081d4c1c @    2 : c4                Star0
         0x63e081d4c1d @    3 : a9                Return

When generating bytecode, the interpreter has calculated the immediate number and will not waste any more instructions.

One interesting thing that is well-known is the comparison between 0.0 and -0.0. For 0.0, the interpreter treats it as 0:

         0x63e081d4e0a @    0 : 0c                LdaZero 
         0x63e081d4e0b @    1 : c4                Star0 
         0x63e081d4e0c @    2 : a9                Return 

-0.0 is treated as a floating point constant, but 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: 0x063e081d4cf9 <HeapNumber 0.0>

Arithmetic Instruction

Increment and Decrement Instructions

Let's start introducing variables now. If it is a local scope, it is similar to only using immediate numbers. For example:

{let a2=1};

Convert it to bytecode:

         0x63e081d500e @    0 : 0d 01             LdaSmi [1]
         0x63e081d5010 @    2 : c3                Star1 
         0x63e081d5011 @    3 : 0e                LdaUndefined 
         0x63e081d5012 @    4 : a9                Return 

If variables are defined globally:

let a1 = 0;

A new instruction StaCurrentContextSlot will be introduced:

         0x63e081d4f06 @    0 : 0c                LdaZero 
         0x63e081d4f07 @    1 : 25 02             StaCurrentContextSlot [2]
         0x63e081d4f09 @    3 : 0e                LdaUndefined 
         0x63e081d4f0a @    4 : a9                Return

Let's start doing some arithmetic. First, focus on the increment operator.

{let a3 = 1; a3 ++;}

The corresponding instructions are 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 

It is worth noting that before performing arithmetic operations, the ToNumberic instruction is used to convert the current value to numeric.

For the decrement operator:

{let a4=100; a4--};

It was only replaced by Dec instructions:

         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 

Binary Arithmetic Operators

Let's look at an additive example first:

{let a5 = 2; a5 = a5 + 2;}

Translate it into AddSmi instruction:

         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 

If it is not for the immediate value but for two variable operations:

{let a6 = 1; let a7=2; let a8 = a6 + a7;}

It will be replaced by an Add instruction:

         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 

Constants and Variables

What kind of instructions will be generated if we operate on constants?:

const a11 = 0; a11 +=1;

When saving constants, we still use StaCurrentContextSlot instructions, but when loading, we use LdaImmutableCurrentContextSlot. CallRuntime will be called to call ThrowConstAssignError exceptions at runtime for AddSmi instructions.

         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 

When a var declared variable is written at the top level, it will be converted into a global variable:

var a13 = 0;

DeclareGlobals participates at runtime. The var declared variable is finally saved as a global variable through 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 

Literals

The Ignition instruction set provides instructions created by three literals:

  • CreateArrayLiteral
  • CreateObjectLateral
  • CreateRegExpLiteral

This is easy to understand. It is aimed at [], {}, //.

Let's look at several examples:

1.  Empty Array

let a1 = [];

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 

2.  Empty Object

let a2={};

Ignition provides a special instruction for it: CreateEmptyObjectLiteral.

         0x24b2081d411e @    0 : 7d                CreateEmptyObjectLiteral 
         0x24b2081d411f @    1 : 25 02             StaCurrentContextSlot [2]
         0x24b2081d4121 @    3 : 0e                LdaUndefined 
         0x24b2081d4122 @    4 : a9                Return 

3.  Regular Expression

let a3 = /a*/;

We prefer a regular expression with values. The literals are finally converted into strings 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*>

Access to Properties

Ignition provides LdaNamedProperty and StaNamedProperty instructions to access properties by name.

Let's look at an example:

let a4 = {}; a4.a = 1;

The following are the generated instructions:

         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>

Note: a4 does not appear because of the Current Context Slot.

Read it again:

a4['a'];

At this time, a4 will 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>

Creation and Invocation of Closures

The function is created using the CreateClosure instruction.

Let's look at an example of an arrow function:

let f1 = () => 0;

The following are the generated instructions:

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>

The common function is also CreateClosure:

let f2 = function(x) { return x * x;}

The bytecode is the same as the preceding one:

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>

Where is the code of the function? It can only be seen when calling. The instruction called is CallUndefinedReceiver, and the one without parameters is CallUndefinedReceiver0.

f1();

The bytecode of f1 will be output 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 

Look at f2:

f2(1.1);

Since f2 has a parameter, the instruction 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 

Branch Jump Instruction

The branch jump instruction is divided into two parts. One is to set the flag bit for the Test class, and the other is to jump according to the flag bit.

Look at an example:

let f3 = (x) => {if (x>0) return 0; else return -1;}; f3(0);

Extract this part of the function body only, which consists of TestGreaterThan and JumpIfFalse. JumpIfFalse executes if it is true. If not, it 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 

In addition to the if statement, operators like "?." also generate branch judgments. Let's look at the following example:

let f5 = (x) => {return x?.length;}; f5(null);

The null judgment operator corresponds to the JumpIfUndefinedOrNull instruction.

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>

The judgment we wrote cannot generate this instruction:

let f6 = (x) => {return x===null || x===undefined}; f6(0);

The generated instructions are literally translated into TestNull and TestUndefined:

         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 

Similarly, the nullish operator is also a branch statement:

let a2 = a1 ?? 100;

It is also an implementation of JumpIfUndefinedOrNull, whose author seems to be the same:

         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]

Handle Exceptions

An exception is a branch instruction in another sense, but it involves exception context.

try{ a4.a = 1;} catch(e) {};

CreateCatchContext provider creates an exception context and then switches the context through 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)

We also see instructions like LdaTheHole first that involve internal mechanisms. According to a member of the v8 team, this hole is doing some inspection here. There are several possibilities. It is necessary to distinguish the specific situation. The following articles will carefully analyze the source code.

If we use the optional catch feature added in ES2019, no CreateCatchContext will be generated. The following is the generated code:

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)

Loop Instruction

First, let's look at the traditional C-like for loop:

let f4 = (x) => {for(let i=0;i<x;i++) {console.log(i);}}; f4(10);

Its implementation is similar to the branch statement. An additional JumpLoop instruction is added.

         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)

Next, watch what is done behind an iteration of an array:

let a21 = [1,2,3,4,5];
let sum=0;
for(let i of a21) {sum+=i;}

Ignition provides the GetIterator instruction for the iterator pattern, followed by the 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 

The details will be discussed later. The important thing now is perceptual understanding.

Class

First, define an empty class and see what happens:

class A {};

As seen in the CreateClosure instruction, the system still generates a constructor 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>

Let's look at another new object:

let a100 = new A();

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>

Implementation of Some New Features

BigInt

As mentioned earlier, LdaSmi accepts an immediate number of up to 4 bytes. If there are more, it becomes a floating point number. ES2020 provides us with BigInt.

Let's look at how the interpreter helps us implement it by adding "n" to the end:

let la = 123n;

The system handles it as a 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>

Class Private Variable

Since ES2020, we can use # to define private variables:

class A3{
        #priv_data = 100
        get_data(){
                return this.#priv_data;
        }
}
let a3 = new A3();
a3.get_data();

The solution to v8 is to hand it over to the Runtime and call the Runtime 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>

TurboFan IR

The bytecode of Ignition is not the only intermediate code in v8, but also the IR of TurboFan.

Due to space constraints, we will post a picture of TurboFan IR:

2

Let's take arithmetic IR JSAdd as an example. Look at the structure of TurboFan IR:

3

Summary

This article introduces basic knowledge about Ignition. It can be seen that compared with JVM bytecode:

  • The Ignition instruction set is rich, such as empty judgment instructions. Even GetIterator has instructions.
  • There is a strong dependence on runtime. Many functions depend on CallRuntime. With the upgrade of ES, runtime functions continue to expand. For example, ES2019 adds Dynamic Import, while v8 adds a DynamicImportCall Runtime function without modifying the instruction set.
  • There are instructions (like LdaTheHole) to serve the internal state.
  • If the runtime makes an error, an instruction will be generated (such as throwing an error). The error information is obtained during the runtime.
0 0 0
Share on

Alibaba F(x) Team

66 posts | 3 followers

You may also like

Comments