how to run javascript script, how to run javascript code

Hello everyone, the editor is here to answer the following questions for you, how to run the chkdsk tool to repair damaged files, how to run javascript scripts, let us take a look today!

Fill in title here
  • 1. How does Js run?
    • 1.1. Introduction
    • 1.2. V8 engine
    • 1.3. How does the CPU execute machine instructions?
    • 1.4. The process of CPU executing machine instructions
    • 1.5. Compilation pipeline of V8 engine
    • 1.6. Complete analysis of how a piece of JavaScript code is executed
      • 1.6.1. Initialize the basic environment
      • 1.6.2. Parse source code to generate AST and scope
      • 1.6.3. Generate bytecode based on AST and scope
      • 1.6.4. Interpret and execute bytecode
      • 1.6.5. Just-in-time compilation
    • 1.7. Optimization strategy of V8
      • 1.7.1. Reintroducing bytecode
      • 1.7.2. Delayed parsing
      • 1.7.3. Hidden classes
      • 1.7.4. Fast attributes and slow attributes
      • 1.7.5. Inline caching
    • 1.8. Reference links

1. How does Js run?

1.1. Introduction

How is the JavaScript code we write recognized and executed by the computer? What is the specific process in between?

Some students may already know that Js is run through the Js engine, so

What is Js engine?

  • How does the Js engine compile, execute and optimize Js code?
  • There are many Js engines, such as the V8 engine used by Chrome, JavaScriptCore used by Webkit, and Hermes used by React Native. Today we will mainly analyze how the more mainstream V8 engine runs the GPT rewriting of Js.

1.2. V8 Engine

V8_Preview

Before introducing the concept of the V8 engine, let’s first review the programming language. Programming languages can be divided into machine language, assembly language, and high-level language.

  • Machine language: Binary code composed of 0s and 1s, which is difficult for humans to remember. The compatibility of different CPU platforms must also be considered.
  • Assembly language: Replaces binary instructions with English abbreviated identifiers that are easier to remember, but still require developers to have sufficient hardware knowledge.
  • High-level languages: are simpler and more abstract and do not require hardware consideration, but require a more complex and time-consuming translation process to be executed.

Machine_Language

At this point we know that high-level language must be converted into machine language before it can be executed by the computer, and the more advanced the language, the longer it takes to convert. High-level languages can be divided into interpreted languages and compiled languages.

  • Compiled language: requires a compiler to compile once, and the compiled file can be executed multiple times. Such as C++, C language.
  • Interpreted language: No prior compilation is required, and the interpreter interprets and executes it at the same time. Starts quickly, but execution is slow.

We know that JavaScript is a high-level language and a dynamically typed language. When we define a variable, we do not need to care about its type, and we can modify the variable type at will. In a statically typed language like C++, we must declare the type of the variable in advance and assign the correct value. It is precisely because JavaScript does not provide enough information in advance for the compiler to compile lower-level machine code like C++. It can only collect type information during the running phase, and then compile and execute based on this information, so JavaScript is also Interpreted language.

This means that in order for JavaScript to be executed by a computer, it needs a program that can quickly parse and execute JavaScript scripts. This program is what we usually call a JavaScript engine. Here we give the concept of the V8 engine: V8 is Google’s open source, high-performance Java and WebAssembly engine written in C++. For Google Chrome (Google’s open source browser) and Node.js, etc.

1.3. How does the CPU execute machine instructions?

After converting high-level language into machine language, how does the CPU execute it? Let’s take a piece of C code as an example:

int main()
{
    int x = 1;
    int y = 2;
    int z = x + y;
    return z;
}}

Let’s first take a look at what the above code looks like when converted into machine language. The left side of the figure below is the binary machine code expressed in hexadecimal, the middle part is the assembly code, and the right side is the meaning of the instruction.

Main_ASM

1.4. The process of CPU executing machine instructions

  • First, the program is loaded into memory before execution.
  • The system writes the address of the first instruction in the binary code into the PC register.

CPU_Register

  • The CPU fetches instructions from memory based on the address in the PC register.
  • Update the address of the next instruction into the PC register.
  • Analyze the current fetched instruction and identify different types of instructions and various methods of obtaining operands.
  • Load instruction: Copies the specified length of content from memory to a general register, and overwrites the original content in the register.
  • Store instruction: Copies the contents of the register to a certain location in memory, and overwrites the original contents at this location in memory.

CPU_Mem

In the picture above, ?x after the movl instruction is the register address, and -8(%rbp) is the address in the memory. This instruction Its function is to copy the value in the register to memory.

  • Update instruction: Copy the contents of two registers to the ALU, or the contents of a register and a memory to the ALU. The ALU adds the two words, stores the result in one of the registers, and overwrites the contents of this register. …
  • After the instruction execution is completed, the next CPU clock cycle is entered.

1.5. Compilation pipeline of V8 engine

Next, let’s take a look at how V8 executes JavaScript code from a macro perspective, and then analyze each step.

  • Initialize the basic environment;
  • Parse source code to generate AST and scope;
  • Generate bytecode based on AST and scope;
  • Interpret and execute bytecode; monitor hot code;

V8_Runtime

1.6. Complete analysis of how a piece of JavaScript code is executed

1.6.1. Initializing the basic environment

V8 cannot execute JS code without the host environment. The host of V8 can be a browser or Node.js. The figure below shows the structure of a browser, in which the rendering engine is what is usually called the browser kernel, which includes the network module, Js interpreter, etc. When a rendering process is opened, a runtime environment is initialized for V8.

V8_Flow

The runtime environment provides V8 with heap space, stack space, global execution context, message loop system, host objects, host API, etc. The core of V8 is to implement the ECMAScript standard, and also provides garbage collector and other content.

V8_Stack

1.6.2. Parse source code to generate AST and scope

After the basic environment is prepared, the JavaScript code to be executed can be submitted to V8. First, V8 will receive the JavaScript source code to be executed, but to V8 this is just a bunch of strings. V8 cannot directly understand the meaning of this string, it needs to structure this string.

function add(x, y) {
  var z = x + y
  return z
}

console.log(add(1, 2))

For example, for the above source code, V8 first parses it into the following abstract syntax tree AST through the parser:

[generating bytecode for function: add]

---AST ---

FUNC at 12

.KIND 0

. LITERAL ID 1

.SUSPEND COUNT 0

. NAME "add"

.PARAMS

. . VAR (0x7fa7bf8048e8) (mode = VAR, assigned = false) "x"

. . VAR (0x7fa7bf804990) (mode = VAR, assigned = false) "y"

.DECLS

. . VARIABLE (0x7fa7bf8048e8) (mode = VAR, assigned = false) "x"

. . VARIABLE (0x7fa7bf804990) (mode = VAR, assigned = false) "y"

. . VARIABLE (0x7fa7bf804a38) (mode = VAR, assigned = false) "z"

. BLOCK NOCOMPLETIONS at -1

. . EXPRESSION STATEMENT at 31

. . . INIT at 31

. . . . VAR PROXY local[0] (0x7fa7bf804a38) (mode = VAR, assigned = false) "z"

. . . . ADD at 32

. . . . . VAR PROXY parameter[0] (0x7fa7bf8048e8) (mode = VAR, assigned = false) "x"

. . . . . VAR PROXY parameter[1] (0x7fa7bf804990) (mode = VAR, assigned = false) "y"

. RETURN at 37

. . VAR PROXY local[0] (0x7fa7bf804a38) (mode = VAR, assigned = false) "z"

While V8 generates the AST, it also generates the scope of the add function:

Global scope:

function add (x, y) { // (0x7f9ed7849468) (12, 47)

  // will be compiled

  // 1 stack slots

  // local vars:

  VAR y; // (0x7f9ed7849790) parameter[1], never assigned

  VAR z; // (0x7f9ed7849838) local[0], never assigned

  VAR x; // (0x7f9ed78496e8) parameter[0], never assigned

}

During parsing, all variables and function parameters declared in the function body are put into the scope. If it is an ordinary variable, the default value is undefined. If it is a function declaration, it will point to the actual function object. During the execution phase, variables in the scope will point to the corresponding data in the heap and stack.

1.6.3. Generate bytecode based on AST and scope

After generating the scope and AST, V8 can generate bytecode based on them. The AST will then be passed as input to the BytecodeGenerator, which is part of the Ignition interpreter and is used to generate bytecode in function units.

[generated bytecode for function: add (0x079e0824fdc1 <SharedFunctionInfo add>)]

Parameter count 3

Register count 2

Frame size 16

         0x79e0824ff7a @ 0 : a7 StackCheck

         0x79e0824ff7b @ 1 : 25 02 Ldar a1

         0x79e0824ff7d @ 3 : 34 03 00 Add a0, [0]

         0x79e0824ff80 @ 6 : 26 fb Star r0

         0x79e0824ff82 @ 8 : 0c 02 LdaSmi [2]

         0x79e0824ff84 @ 10 : 26 fa Star r1

         0x79e0824ff86 @ 12 : 25 fb Ldar r0

         0x79e0824ff88 @ 14 : ab Return

Constant pool (size = 0)

Handler Table (size = 0)

Source Position Table (size = 0)
1.6.4. Interpret and execute bytecode

It is similar to the CPU executing binary machine code: use an area in the memory to store the bytecode; use the general register to store some intermediate data; the PC register is used to point to the next bytecode to be executed; the top register of the stack is used to point to The current top position of the stack.

V8_CPU_Register

  • The StackCheck bytecode instruction checks whether the stack has reached the upper limit of overflow.
  • Ldar means loading the value from the register into the accumulator.
  • Add means loading the register with a value and adding it to the value in the accumulator, then putting the result into the accumulator again.
  • Star means saving the value in the accumulator to a register.
  • Return ends the execution of the current function and passes control back to the caller. The value returned is the value in the accumulator.
1.6.5. Just-in-time compilation

During the execution of bytecode by the interpreter Ignition, if there is hot spot code (HotSpot), such as a piece of code that is repeatedly executed multiple times, this is called hot code, then the background compiler TurboFan will convert the hot spot code into a hot spot code. The bytecode is compiled into efficient machine code, and then when this optimized code is executed again, only the compiled machine code needs to be executed, which greatly improves the execution efficiency of the code. This technology of bytecode cooperating with interpreters and compilers is called just-in-time compilation (JIT).

JS_Bytecode

1.7. Optimization strategy of V8

Let’s take a look at what optimizations V8 has done to improve the speed of parsing and executing JS. Due to space constraints, only 5 optimization points are introduced here.

1.7.1. Reintroducing bytecode

The early V8 team believed that generating bytecode first and then executing the bytecode would reduce code execution efficiency, so they directly compiled JavaScript code into machine code. There are two problems caused by this. One is that it requires a long compilation time, and the other is that the generated binary machine code requires a large memory space.

JS_Bytecode_In

Although using bytecode sacrifices a little execution efficiency, it saves memory space and reduces compilation time. In addition, bytecode also reduces the complexity of V8 code, making it easier to port V8 to different CPU architecture platforms. This is because it is easier to uniformly convert bytecode into binary code for different platforms than for compilers to write binary code for different CPU systems.

1.7.2. Delayed parsing

Through the compilation process of V8, we can see that V8 needs to go through two stages of compilation and execution to execute JavaScript code.

  • Compilation process: refers to the stage where V8 converts JavaScript code into bytecode, or binary machine code.
  • Execution stage: refers to the stage where the interpreter interprets and executes bytecode, or the CPU directly executes binary machine code.

V8 will not parse all JavaScript into intermediate code at once. This is mainly based on the following two points:

  • If all JavaScript code is parsed and compiled at once, too much code will increase the compilation time, which will seriously affect the speed of first execution of JavaScript code and make users feel stuck.
  • Secondly, the parsed bytecode and compiled machine code will be stored in memory. If all JavaScript codes are parsed and compiled at once, these intermediate codes and machine codes will always occupy memory.

Delayed parsing means that if the parser encounters a function declaration during the parsing process, it will skip the code inside the function and will not generate AST and bytecode for it.

1.7.3. Hidden classes

We can combine a piece of code to analyze how hidden classes work:

let point = {x:100,y:200}

When V8 executes this code, it will first create a hidden class for the point object. In V8, the hidden class is also called map. Each object has a map attribute, whose value points to the hidden class in memory. The hidden class describes the attribute layout of the object. It mainly includes the attribute name and the offset corresponding to each attribute. For example, the hidden class of the point object includes the x and y attributes. The offset of x is 4, and the offset of y is 4. The offset is 8.

V8_Hide

With the hidden class, when V8 accesses an attribute in an object, it will first find the offset of the attribute relative to its object in the hidden class. With the offset and attribute type, V8 You can directly retrieve the corresponding attribute value from the memory without going through a series of search processes, which greatly improves the efficiency of V8’s object search.

1.7.4. Fast attributes and slow attributes

When we enter the following code in the console:

function Foo() {

    this[100] = 'test-100'

    this[1] = 'test-1'

    this["B"] = 'bar-B'

    this[50] = 'test-50'

    this[9] = 'test-9'

    this[8] = 'test-8'

    this[3] = 'test-3'

    this[5] = 'test-5'

    this["A"] = 'bar-A'

    this["C"] = 'bar-C'
}

var bar = new Foo()

for(key in bar){
    console.log(`index:${key} value:${bar[key]}`)
}

The printed results are as follows:

index:1 value:test-1

index:3 value:test-3

index:5 value:test-5

index:8 value:test-8

index:9 value:test-9

index:50 value:test-50

index:100 value:test-100

index:B value:bar-B

index:A value:bar-A

index:C value:bar-C

The reason for this result is that the ECMAScript specification defines that numeric properties should be arranged in ascending order according to the size of the index value, and string properties should be arranged in ascending order according to the order in which they were created.

  • Numeric properties are called sorting properties, or elements in V8.
  • String properties are called regular properties, or properties in V8.

Let’s execute this piece of code to see how its structure in memory changes when the number of attributes in the object changes.

function Foo(property_num,element_num) {
    //Add sorting attribute
    for (let i = 0; i < element_num; i + + ) {
        this[i] = `element${i}`
    }

    //Add regular properties
    for (let i = 0; i < property_num; i + + ) {
        let ppt = `property${i}`
        this[ppt] = ppt
    }
}

var bar = new Foo(10,10)

Switch the Chrome developer tools to the Memory tab, and then click the small circle on the left to capture a memory snapshot of the above code. The final screenshot is as follows:

Chrome_Debug_Memory

Adjust the number of created object properties to 20:

var bar2 = new Foo(20,10)

Chrome_Elements

Summary: When there are too many attributes in an object, or there are operations of repeatedly adding or deleting attributes, V8 will downgrade the linear storage mode (fast attributes) to the non-linear dictionary storage mode (slow attributes). Although this reduces The search speed is improved, but the speed of modifying the properties of the object is increased.

1.7.5. Inline cache

Let’s look at a piece of code like this again.

function loadX(o) {
    o.y = 4
    return o.x
}

var o = {x: 1,y:3}
var o1 = { x: 3 ,y:6}

for (var i = 0; i < 90000; i + + ) {
    loadX(o)
    loadX(o1)
}

Usually the process of V8 getting o. The process of obtaining o.x also needs to be executed repeatedly. In order to improve the efficiency of object search. The strategy implemented by V8 is to use inline cache (Inline Cache), referred to as IC. IC will maintain a feedback vector (FeedBack Vector) for each function. The feedback vector records some key intermediate data during the execution of the function. These data are then cached, and when the function is executed again next time, V8 can directly use these intermediate data, saving the process of obtaining these data again. V8 will allocate a slot (Slot) for each call site in the feedback vector. For example, o.y = 4 and return o.x are the call sites (CallSite) because they use objects and properties. Each slot includes the slot index (slot index), the slot type (type), the slot state (state), the address of the hidden class (map), and the offset of the attribute, such as the above Both call points in this function use object o, so the map attributes in the two slots of the feedback vector also point to the same hidden class, so the map addresses of the two slots are the same.

JS_Slot_Map

Through the inline caching strategy, the efficiency of the next execution of the function can be improved, but there is a premise for this, that is, the shape of the object is fixed when executed multiple times. If the shape of the object is not fixed, this means that V8 is The hidden classes they create are also different. Faced with this situation, V8 will choose to record the new hidden class in the feedback vector and record the offset of the attribute value. At this time, a slot in the feedback vector will appear containing multiple hidden classes and In the case of offsets, if there are more than 4, V8 will store them in a hash table structure. My sharing ends here. If there are any shortcomings, you are welcome to criticize and correct me.

1.8. Reference links

  • https://www.cnblogs.com/nickchen121/p/10722720.html
  • Documentation · V8
  • Detailed explanation of V8 engine (4) – How bytecode is executed – Nuggets
  • 01 | How does V8 execute a piece of JavaScript code? -geek time
  • Components and operating principles of browsers – Brief Book