JavaScript Internals

Interpreters & Compilers

Compiler

Interpreter

Interpreters & Compilers

Compiler

Interpreter

  • AST based (recursive) interpreters
  • Bytecode interpreters (VM)
  • Ahead of time (AOT)
  • Just in time (JIT)
  • AST transformers (transpilers)

JIT Compiler

Lexer

AST

Parsed tokens

Tokenizer

let code = '100 - 5 * (2 - 2 / 3)'

let arithmeticToknizer = (eqn) =>
  eqn
    .split(/(\+|\-|\*|\/|\(|\))/)
    .map((token) => {
      let type

      if (['-', '*', '/', '+'].includes(token)) {
        type = 'BinaryExpression'
      } else if (['(', ')'].includes(token)) {
        type = 'GroupExpression'
      } else {
        type = 'NumberLiteral'
      }

      return {
        type,
        value: token,
      }
    })
    .filter((s) => s.value !== '')

let tokens = arithmeticToknizer(code)

Tokenizer

const esprima = require('esprima')

let code = 'const meaningOfLife = 42'

console.log(esprima.tokenize(code))

/*
  output => [
    { type: 'Keyword', value: 'const' },
    { type: 'Identifier', value: 'meaningOfLife' },
    { type: 'Punctuator', value: '=' },
    { type: 'Numeric', value: '42' },
  ]
*/

Parser

VariableDeclaration
VariableDeclarator
id: Identifier
init: Literal
name: meaningOfLife
value: 42
const util = require('util')
const esprima = require('esprima')

console.log(
  util.inspect(
    esprima.parse('const meaningOfLife = 42'), false, null, true,
  ),
)

Abstract Syntax Tree

/*
  output => [
    { type: 'Keyword', value: 'const' },
    { type: 'Identifier', value: 'meaningOfLife' },
    { type: 'Punctuator', value: '=' },
    { type: 'Numeric', value: '42' },
  ]
*/

Parsed tokens

[generated bytecode for function: define (0x1c8e78250519 <SharedFunctionInfo define>)]
Parameter count 1
Register count 1
Frame size 8
   44 S> 0x1c8e78250f36 @    0 : 0c 2a             LdaSmi [42]
         0x1c8e78250f38 @    2 : 26 fb             Star r0
         0x1c8e78250f3a @    4 : 0d                LdaUndefined
   47 S> 0x1c8e78250f3b @    5 : aa                Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 6)
0x1c8e78250f41 <ByteArray[6]>

AST + scopes

JIT Compiler

Profiler

Interpreter

Compiler

Lexer

AST

Output

Optimized code

Parsed tokens

Generated bytecode

Compiler optimizations

  1. On stack replacement
  2. Constant folding
  3. Removing recursion
  4. Inline caching
  5. Dead code elimination
  6. Trampolines

On stack replacement

OSR is simply a technique the optimizing compilers makes to optimize the code by replacing some frames from the stack by new ones that are optimized meanwhile the execution and without losing any data from the removed stack frame 

On stack replacement

function getX(point) {
  return point.x
}

un-optimized stack frame in byte code

Stack  trace

[generated bytecode for function: getX (0x2665be510511 <SharedFunctionInfo getX>)]
Parameter count 2
Register count 0
Frame size 0
   38 S> 0x2665be5111c6 @    0 : 28 02 00 00       LdaNamedProperty a0, [0], [0]
   39 S> 0x2665be5111ca @    4 : aa                Return
Constant pool (size = 1)
0x2665be511179: [FixedArray] in OldSpace
 - map: 0x34c1936c0729 <Map>
 - length: 1
           0: 0x16097aa0c561 <String[#1]: x>
Handler Table (size = 0)
Source Position Table (size = 6)
0x2665be5111d1 <ByteArray[6]>

AST + Scopes

On stack replacement

a new optimized stack frame using opt code

Stack  trace

[generated bytecode for function: getX (0x2665be510511 <SharedFunctionInfo getX>)]
Parameter count 2
Register count 0
Frame size 0
   38 S> 0x2665be5111c6 @    0 : 28 02 00 00       LdaNamedProperty a0, [0], [0]
   39 S> 0x2665be5111ca @    4 : aa                Return
Constant pool (size = 1)
0x2665be511179: [FixedArray] in OldSpace
 - map: 0x34c1936c0729 <Map>
 - length: 1
           0: 0x16097aa0c561 <String[#1]: x>
Handler Table (size = 0)
Source Position Table (size = 6)
0x2665be5111d1 <ByteArray[6]>
Instructions (size = 1008)
0xbd6ab243000     0  488b59c0       REX.W movq rbx,[rcx-0x40]
0xbd6ab243004     4  f6430f01       testb [rbx+0xf],0x1
0xbd6ab243008     8  740d           jz 0xbd6ab243017  <+0x17>
0xbd6ab24300a     a  49ba80d93d0100000000 REX.W movq r10,0x13dd980  (CompileLazyDeoptimizedCode)
0xbd6ab243014    14  41ffe2         jmp r10
0xbd6ab243017    17  55             push rbp
0xbd6ab243018    18  4889e5         REX.W movq rbp,rsp
0xbd6ab24301b    1b  56             push rsi
0xbd6ab24301c    1c  57             push rdi
0xbd6ab24301d    1d  48ba0000000021000000 REX.W movq rdx,0x2100000000
0xbd6ab243027    27  49bac010400100000000 REX.W movq r10,0x14010c0  (Abort)
0xbd6ab243031    31  41ffd2         call r10
0xbd6ab243034    34  cc             int3l
0xbd6ab243035    35  4883ec08       REX.W subq rsp,0x8
0xbd6ab243039    39  4889b568ffffff REX.W movq [rbp-0x98],rsi
0xbd6ab243040    40  488b4db8       REX.W movq rcx,[rbp-0x48]
0xbd6ab243044    44  48bf89675172ed200000 REX.W movq rdi,0x20ed72516789    ;; object: 0x20ed72516789 <JSFunction next (sfi = 0x25c7b99d7971)>
0xbd6ab24304e    4e  483bcf         REX.W cmpq rcx,rdi
0xbd6ab243051    51  0f8508030000   jnz 0xbd6ab24335f  <+0x35f>
0xbd6ab243057    57  488b771f       REX.W mo

Optimizing compiler

Constant folding

Replaces the value that an expression yields to, with the actual value of expression at the run-time instead of re-evaluating the expression

import axios from 'axios'

const { data } = await axios.get(
  'http://jsonplaceholder.typicode.com/todos',
)

const todos = data
  .slice(0, 5)
  .filter((todo) => todo.completed)
  .map((todo) => todo.title)

console.log(todos)
const data = [
  {
    id: 1,
    title: "abc",
    completed: false,
    user_id: 2
  },
   ...
]

Constant folding

Replaces the value that an expression yields to, with the actual value of expression at the run-time instead of re-evaluating the expression

import axios from 'axios'

const { data } = await axios.get(
  'http://jsonplaceholder.typicode.com/todos',
)
const data = [
  {
    id: 1,
    title: "abc",
    completed: false,
    user_id: 2
  },
   ...
]

Dead code elimination

Not compiling/interpreting the un-used parts of code in order to save time

function add(x, y) {
  return x + y
}

add(5, 2)
[generated bytecode for function: add (0x0d2bff519889 <SharedFunctionInfo add>)]
Parameter count 3
Register count 0
Frame size 0
   23 S> 0xd2bff519c86 @    0 : 25 02             Ldar a1
   32 E> 0xd2bff519c88 @    2 : 34 03 00          Add a0, [0]
   35 S> 0xd2bff519c8b @    5 : aa                Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 8)
0x0d2bff519c91 <ByteArray[8]>

Dead code elimination

Not compiling/interpreting the un-used parts of code in order to save time

function add(x, y) {
  return x + y
}

// add(5, 2)
// empty file

JavaScript Engine

Byte code

Optimized code

output

Ignition

TurboFan

Feedback

JavaScript runtime

Callback queue

Runtime API

Event  loop

Memory
Heap

Call
Stack

Byte code

Ignition

TurboFan

Output

Opt code

Callstack

 A call stack is an implementation of the stack data structure to keep track of function calls in your program

Me 👉👈

function one() {
  console.log('I am first')
}

function two() {
  console.log('I am second')
  one()
}

function third() {
  two()
  return 'I am third'
}

function run() {
  return third()
}

console.log(run())

main()

run()

third()

two()

one()

Stack  trace

Memory heap

 A memory heap is an implementation of the graph data structure for allocating memory used by your program 

Me 👉👈

Memory  heap

GC Algorithms

Memory  allocation

Memory heap

let meaningOfLife = 42

const data = {
  users: [
    {
      username: 'ahmedosama-st',
      email: 'ahmedosama@sectheater.io',
      articles: [
        {
          title: 'Optimizing recursive functions',
          url: 'https://dev.to/ahmedosama_st/optimizing-recursive-functions-1lhh',
        },
      ],
    },
  ],
}
meaningOfLife
42
data

JSObject

reference

Stack allocation

Heap allocation

GC Algorithms

Reference count

Mark and sweep

GC Algorithms

Reference count

Mark and sweep

class Point {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
}

let p1 = new Point(1, 2) // Point Ref count : 1, p1 Ref count: 1

let p2 = p1 // p1 Ref count: 2

GC Algorithms

Reference count

Mark and sweep

Free

GC Algorithms

Reference count

Mark and sweep

Free

A

Root
let A = { value: 1 }

GC Algorithms

Reference count

Mark and sweep

Free

A

Root

B

C

D

let A = { value: 1 }

let B = { value: 2 }

A.B = B
A.C = { value: 3 } // C object

B.D = { value: 4 } // D object

GC Algorithms

Mark and sweep

Free

A

Root

B

C

D

E

let A = { value: 1 }

let B = { value: 2 }

A.B = B
A.C = { value: 3 } // C object

B.D = { value: 4 } // D object

A.C.E = { value: 5 }

GC Algorithms

Mark and sweep

Free

A

Root

B

C

D

E

let A = { value: 1 }

let B = { value: 2 }

A.B = B
A.C = { value: 3 } // C object

B.D = { value: 4 } // D object

A.C.E = { value: 5 }

delete A.B

GC Algorithms

Mark and sweep

Free

A

Root

Free

C

Free

E

let A = { value: 1 }

let B = { value: 2 }

A.B = B
A.C = { value: 3 } // C object

B.D = { value: 4 } // D object

A.C.E = { value: 5 }

delete A.B

Memory allocation

let arr = [1, 2, 3]

let user = { first_name: 'ahmed', last_name: 'osama' }

const double = (x) => x * 2

function sayWelcome(name) {
  return `Hello ${name}`
}

Memory allocation

let arr = [1, 2, 3]

let user = { first_name: 'ahmed', last_name: 'osama' }

const double = (x) => x * 2

function sayWelcome(name) {
  return `Hello ${name}`
}

Stack

Heap

arr: x1ud

x1ud: [1, 2, 3]

Memory allocation

let object =  {
  x: 5,
  y: 6,
}

Property attributes

[[Value]]: 5

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

[[Value]]: 6

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

'x'

'y'

JSObject

Memory allocation

let a = { x: 5, y: 6 }

let b = { x: 5, y: 6 }

Property attributes

[[Value]]: 5

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

[[Value]]: 6

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

'x'

'y'

JSObject (a)

'x'

'y'

JSObject (b)

Property attributes

[[Value]]: 5

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

[[Value]]: 6

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Memory allocation

let a = {
  x: 5,
  y: 6,
}

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

Offset: 1

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

5

6

JSObject (a)

'x'

'y'

Shape

Memory allocation

let a = { x: 5, y: 6 }

let b = { x: 7, y: 8 }

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

Offset: 1

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

5

6

JSObject (a)

'x'

'y'

Shape

7

8

JSObject (b)

Memory allocation

let o = {}

JSObject (o)

Shape

(empty)

Memory allocation

let o = {}
o.x = 6

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

6

JSObject (o)

'x'

Shape

(empty)

Shape

(x)

Memory allocation

let o = {}
o.x = 6
o.y = 7

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

Offset: 1

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

6

7

JSObject (o)

'x'

'x'

'y'

Shape

(empty)

Shape

(x)

Shape

(x, y)

Memory allocation

let o = {}
o.x = 6
o.y = 7

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

Offset: 1

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

6

7

JSObject (o)

'x'

'y'

Shape

(empty)

Shape

(x)

Shape

(x, y)

Memory allocation

let a = {}
a.x = 6

Property attributes

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

6

JSObject (a)

'x'

Shape

(empty)

Shape

(x)

Memory allocation

let a = {}
a.x = 6

let b = {}
b.y = 7

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Property attributes

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

6

JSObject (a)

'x'

Shape

(empty)

Shape

(x)

7

JSObject (b)

'y'

Shape

(y)

Memory allocation

let o = {}
o.x = 6

let w = { x: 6 }

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

6

JSObject (o)

'x'

Shape

(empty)

Shape

(x)

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

6

JSObject (w)

'x'

Shape

(x)

Memory allocation

let point = {}

point.x = 3
point.y = 4
point.z = 5

3

JSObject (p)

4

5

'x'

Shape

(x)

'y'

Shape

(x, y)

'z'

Shape

(x, y, z)

'x'

Shape table

'y'

'z'

Shape  ICS (inline  caching)

function getX(o) {
  return o.x
}
JSFunction 'getX'
get_by_id loc0, arg1, x
N/A
N/A
return loc0

Shape  ICS (inline  caching)

function getX(o) {
  return o.x
}






getX({ x: 'a' })
JSFunction 'getX'
get_by_id loc0, arg1, x
0
return loc0

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

'x'

Shape

Shape  ICS (inline  caching)

function getX(o) {
  return o.x
}






getX({ x: 'a' })



getX({ x: 'b' })

JSFunction 'getX'
get_by_id loc0, arg1, x
0
return loc0

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

'x'

Shape

Memory allocation

let array =  [
  'a',
]

Property attributes

[[Value]]: 1

[[Writable]]: true

[[Enumerable]]: false

[[Configurable]]: false

Property attributes

[[Value]]: 'a'

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

'length'

'0'

Array

Memory allocation

let array =  [
  'a',
  'b,'
]

Property attributes

[[Value]]: 'a'

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

'length'

'0'

Array

'1'

Property attributes

[[Value]]: 2

[[Writable]]: true

[[Enumerable]]: false

[[Configurable]]: false

Property attributes

[[Value]]: 'b'

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Memory allocation

let usernames = [
  'ahmedosama-st',
  'ahmedosama_st'
]

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

1

JSArray

'length'

Shape

Element information

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

'ahmedosama-st'

Elements

'ahmedosama_st'

Memory allocation

const array = Object.defineProperty([], '0', {
  value: 'ahmed',
  writable: false,
  enumerable: false,
  configurable: false,
})

const anotherArray = []

Object.defineProperties(anotherArray, [
  {
    value: 'one',
    writable: true,
    enumerable: true,
    configurable: true,
  },
])

console.log(anotherArray)

Memory allocation

let arr = Object.defineProperty([], '0', {...})
let anotherArr = Object.defineProperties([], [...])

Property information

Offset: 0

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

1

JSArray

'length'

Shape

Element information

[[Value]]: 'abc'

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

0

Dictionary Elements

1

Event Loop

GIF from Lydia Hallie's article

Wrapping Up

Wrapping Up

  • The lifecycle of executing JS code using Ignition and TurboFan
  • Compiler optimizations and profiler assumptions
  • Reasons to use TypeScript over JavaScript for performance
  • JS Runtime
    • Call stack
    • Memory heap
      • GC Algorithms
      • Memory allocation 
      • Object shapes and inline caching
    • Event Loop

What's next?

What's next?

SecTheater

SecTheater

Raise up your techinal skills

SecTheater

The internals of JavaScript

By Security Theater

The internals of JavaScript

Let's explore how JavaScript works under the hood.

  • 154