Javascript
the dark side of the engine

Kemal Erdem | github.com/burnpiro

JS is a strange language

Many implementations

Multiple platforms

Compiled or Interpreted?

JIT (Just In Time) Compiler

Main flow in JS Engine

AST (Abstract Syntax Tree)

          
            function addA(a,b) {
              const weirdConstant = 10
              return a + b + weirdConstant
            }
            const c = addA(4,5)
					
        

forEach polyfill in JS

forEach polyfill in JS

        
          Array.prototype.forEach = function (callback, thisArg) {
            var T, k
            if (this == null) {
              throw new TypeError(' this is null or not defined')
            }
            var O = Object(this)
            var len = O.length >>> 0
            if (typeof callback !== 'function') {
              throw new TypeError(callback + ' is not a function')
            }
            if (arguments.length > 1) {
              T = thisArg
            }
            k = 0
            while (k < len) {
              var kValue
              if (k in O) {
                kValue = O[k]
                callback.call(T, kValue, k, O)
              }
              k++
            }
          }
        
      

Interpreter

Fast and generate bytecode

Optimizing Compilers

Used only for "HOT" functions

Before dinosaurs => 2008

2010 - First optimizing compiler

2014 - Lets add more of them?

Era of optimizing compilers

Crankshaft and full codegen

Other engines follow using even more than 2 compilers per engine

2016 - Interpreter idea

Do you see, Harry? Do you see the flaw in my brilliant plan now?

The flaw?

2017 - Back to the future

Sea of nodes

          

Property access

const getXProp = obj => obj.x function getXProp (obj) { return obj.x }
9.1.8.1 OrdinaryGet ( O, P, Receiver )

When the abstract operation OrdinaryGet is called with Object O, property key P, and ECMAScript language value Receiver, the following steps are taken:

  1. Assert: IsPropertyKey(P) is true.
  2. Let desc be ? O.[[GetOwnProperty]](P).
  3. If desc is undefined, then
    1. Let parent be ? O.[[GetPrototypeOf]]().
    2. If parent is null, return undefined.
    3. Return ? parent.[[Get]](P, Receiver).
  4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
  5. Assert: IsAccessorDescriptor(desc) is true.
  6. Let getter be desc.[[Get]].
  7. If getter is undefined, return undefined.
  8. Return ? Call(getter, Receiver).
            
              const getXProp = obj => obj.x

              getXProp({ x: 1 })
            
            
              SET
              { x:int } => get from offset 1 in IC (Inline Cache)
            
            
              getXProp({ x: 2 })

              { x: 2 } type of { x:int }
              GET from offset 1
            
            
              const firstX = { x: 2 }
              const secondX = { x: 12 }

              console.log(%HaveSameMap(firstX, secondX))

              --allow-native-syntax
            
          

Let TurboFan do the work

            
              const getPropX = obj => obj.x

              for(var i=0; i<10000; i++) {
                getPropX({ x: i })
              }
            
          
          

0xc0abb749632 18 488bf0 REX.W movq rsi,rax ;;; <@18,#13> stack-check 0xc0abb749635 21 493ba5880a0000 REX.W cmpq rsp,[r13+0xa88] 0xc0abb74963c 28 7305 jnc 35 (0xc0abb749643) 0xc0abb74963e 30 e89d35efff call StackCheck (0xc0abb63cbe0) ;; code: BUILTIN ;;; <@20,#13> lazy-bailout ;;; <@21,#13> gap 0xc0abb749643 35 488b4510 REX.W movq rax,[rbp+0x10] ;;; <@22,#15> check-non-smi 0xc0abb749647 39 a801 test al,0x1 0xc0abb749649 41 0f8427000000 jz 86 (0xc0abb749676) ;;; <@24,#16> check-maps 0xc0abb74964f 47 49bad9bde1bd02370000 REX.W movq r10,0x3702bde1bdd9 ;; object: 0x3702bde1bdd9 Map(FAST_HOLEY_ELEMENTS) 0xc0abb749659 57 4c3950ff REX.W cmpq [rax-0x1],r10 0xc0abb74965d 61 0f8518000000 jnz 91 (0xc0abb74967b) ;;; <@26,#17> load-named-field 0xc0abb749663 67 8b401b movl rax,[rax+0x1b] ;;; <@28,#21> smi-tag 0xc0abb749666 70 8bd8 movl rbx,rax 0xc0abb749668 72 48c1e320 REX.W shlq rbx, 32 ;;; <@29,#21> gap 0xc0abb74966c 76 488bc3 REX.W movq rax,rbx ;;; <@30,#19> return 0xc0abb74966f 79 488be5 REX.W movq rsp,rbp 0xc0abb749672 82 5d pop rbp 0xc0abb749673 83 c21000 ret 0x10 ;;; -------------------- Jump table -------------------- 0xc0abb749676 86 e88fc9bbff call 0xc0abb30600a ;; deoptimization bailout 1 0xc0abb74967b 91 e894c9bbff call 0xc0abb306014 ;; deoptimization bailout 2

Let TurboFan do the work

            
              const getPropX = obj => obj.x

              for(var i=0; i<10000; i++) {
                getPropX({ x: i })
              }
            
          
          

0xc0abb749632 18 488bf0 REX.W movq rsi,rax ;;; <@18,#13> stack-check 0xc0abb749635 21 493ba5880a0000 REX.W cmpq rsp,[r13+0xa88] 0xc0abb74963c 28 7305 jnc 35 (0xc0abb749643) 0xc0abb74963e 30 e89d35efff call StackCheck (0xc0abb63cbe0) ;; code: BUILTIN ;;; <@20,#13> lazy-bailout ;;; <@21,#13> gap 0xc0abb749643 35 488b4510 REX.W movq rax,[rbp+0x10] ;;; <@22,#15> check-non-smi 0xc0abb749647 39 a801 test al,0x1 0xc0abb749649 41 0f8427000000 jz 86 (0xc0abb749676) ;;; <@24,#16> check-maps 0xc0abb74964f 47 49bad9bde1bd02370000 REX.W movq r10,0x3702bde1bdd9 ;; object: 0x3702bde1bdd9 Map(FAST_HOLEY_ELEMENTS) 0xc0abb749659 57 4c3950ff REX.W cmpq [rax-0x1],r10 0xc0abb74965d 61 0f8518000000 jnz 91 (0xc0abb74967b) ;;; <@26,#17> load-named-field 0xc0abb749663 67 8b401b movl rax,[rax+0x1b] ;;; <@28,#21> smi-tag 0xc0abb749666 70 8bd8 movl rbx,rax 0xc0abb749668 72 48c1e320 REX.W shlq rbx, 32 ;;; <@29,#21> gap 0xc0abb74966c 76 488bc3 REX.W movq rax,rbx ;;; <@30,#19> return 0xc0abb74966f 79 488be5 REX.W movq rsp,rbp 0xc0abb749672 82 5d pop rbp 0xc0abb749673 83 c21000 ret 0x10 ;;; -------------------- Jump table -------------------- 0xc0abb749676 86 e88fc9bbff call 0xc0abb30600a ;; deoptimization bailout 1 0xc0abb74967b 91 e894c9bbff call 0xc0abb306014 ;; deoptimization bailout 2

Let TurboFan do the work

            
              const getPropX = obj => obj.x

              for(var i=0; i<10000; i++) {
                getPropX({ x: i })
              }
            
          
          

0xc0abb749632 18 488bf0 REX.W movq rsi,rax ;;; <@18,#13> stack-check 0xc0abb749635 21 493ba5880a0000 REX.W cmpq rsp,[r13+0xa88] 0xc0abb74963c 28 7305 jnc 35 (0xc0abb749643) 0xc0abb74963e 30 e89d35efff call StackCheck (0xc0abb63cbe0) ;; code: BUILTIN ;;; <@20,#13> lazy-bailout ;;; <@21,#13> gap 0xc0abb749643 35 488b4510 REX.W movq rax,[rbp+0x10] ;;; <@22,#15> check-non-smi 0xc0abb749647 39 a801 test al,0x1 0xc0abb749649 41 0f8427000000 jz 86 (0xc0abb749676) ;;; <@24,#16> check-maps< 0xc0abb74964f 47 49bad9bde1bd02370000 REX.W movq r10,0x3702bde1bdd9 ;; object: 0x3702bde1bdd9 Map(FAST_HOLEY_ELEMENTS) 0xc0abb749659 57 4c3950ff REX.W cmpq [rax-0x1],r10 0xc0abb74965d 61 0f8518000000 jnz 91 (0xc0abb74967b) ;;; <@26,#17> load-named-field 0xc0abb749663 67 8b401b movl rax,[rax+0x1b] ;;; <@28,#21> smi-tag 0xc0abb749666 70 8bd8 movl rbx,rax 0xc0abb749668 72 48c1e320 REX.W shlq rbx, 32 ;;; <@29,#21> gap 0xc0abb74966c 76 488bc3 REX.W movq rax,rbx ;;; <@30,#19> return 0xc0abb74966f 79 488be5 REX.W movq rsp,rbp 0xc0abb749672 82 5d pop rbp 0xc0abb749673 83 c21000 ret 0x10 ;;; -------------------- Jump table -------------------- 0xc0abb749676 86 e88fc9bbff call 0xc0abb30600a ;; deoptimization bailout 1 0xc0abb74967b 91 e894c9bbff call 0xc0abb306014 ;; deoptimization bailout 2

Let TurboFan do the work

            
              const getPropX = obj => obj.x

              for(var i=0; i<10000; i++) {
                getPropX({ x: i })
              }
            
          
          

0xc0abb749632 18 488bf0 REX.W movq rsi,rax ;;; <@18,#13> stack-check 0xc0abb749635 21 493ba5880a0000 REX.W cmpq rsp,[r13+0xa88] 0xc0abb74963c 28 7305 jnc 35 (0xc0abb749643) 0xc0abb74963e 30 e89d35efff call StackCheck (0xc0abb63cbe0) ;; code: BUILTIN ;;; <@20,#13> lazy-bailout ;;; <@21,#13> gap 0xc0abb749643 35 488b4510 REX.W movq rax,[rbp+0x10] ;;; <@22,#15> check-non-smi 0xc0abb749647 39 a801 test al,0x1 0xc0abb749649 41 0f8427000000 jz 86 (0xc0abb749676) ;;; <@24,#16> check-maps 0xc0abb74964f 47 49bad9bde1bd02370000 REX.W movq r10,0x3702bde1bdd9 ;; object: 0x3702bde1bdd9 Map(FAST_HOLEY_ELEMENTS) 0xc0abb749659 57 4c3950ff REX.W cmpq [rax-0x1],r10 0xc0abb74965d 61 0f8518000000 jnz 91 (0xc0abb74967b) ;;; <@26,#17> load-named-field 0xc0abb749663 67 8b401b movl rax,[rax+0x1b] ;;; <@28,#21> smi-tag 0xc0abb749666 70 8bd8 movl rbx,rax 0xc0abb749668 72 48c1e320 REX.W shlq rbx, 32 ;;; <@29,#21> gap 0xc0abb74966c 76 488bc3 REX.W movq rax,rbx ;;; <@30,#19> return 0xc0abb74966f 79 488be5 REX.W movq rsp,rbp 0xc0abb749672 82 5d pop rbp 0xc0abb749673 83 c21000 ret 0x10 ;;; -------------------- Jump table -------------------- 0xc0abb749676 86 e88fc9bbff call 0xc0abb30600a ;; deoptimization bailout 1 0xc0abb74967b 91 e894c9bbff call 0xc0abb306014 ;; deoptimization bailout 2

Let TurboFan do the work

            
              const getPropX = obj => obj.x

              for(var i=0; i<10000; i++) {
                getPropX({ x: i })
              }
            
          
          

0xc0abb749632 18 488bf0 REX.W movq rsi,rax ;;; <@18,#13> stack-check 0xc0abb749635 21 493ba5880a0000 REX.W cmpq rsp,[r13+0xa88] 0xc0abb74963c 28 7305 jnc 35 (0xc0abb749643) 0xc0abb74963e 30 e89d35efff call StackCheck (0xc0abb63cbe0) ;; code: BUILTIN ;;; <@20,#13> lazy-bailout ;;; <@21,#13> gap 0xc0abb749643 35 488b4510 REX.W movq rax,[rbp+0x10] ;;; <@22,#15> check-non-smi 0xc0abb749647 39 a801 test al,0x1 0xc0abb749649 41 0f8427000000 jz 86 (0xc0abb749676) ;;; <@24,#16> check-maps 0xc0abb74964f 47 49bad9bde1bd02370000 REX.W movq r10,0x3702bde1bdd9 ;; object: 0x3702bde1bdd9 Map(FAST_HOLEY_ELEMENTS) 0xc0abb749659 57 4c3950ff REX.W cmpq [rax-0x1],r10 0xc0abb74965d 61 0f8518000000 jnz 91 (0xc0abb74967b) ;;; <@26,#17> load-named-field 0xc0abb749663 67 8b401b movl rax,[rax+0x1b] ;;; <@28,#21> smi-tag 0xc0abb749666 70 8bd8 movl rbx,rax 0xc0abb749668 72 48c1e320 REX.W shlq rbx, 32 ;;; <@29,#21> gap 0xc0abb74966c 76 488bc3 REX.W movq rax,rbx ;;; <@30,#19> return 0xc0abb74966f 79 488be5 REX.W movq rsp,rbp 0xc0abb749672 82 5d pop rbp 0xc0abb749673 83 c21000 ret 0x10 ;;; -------------------- Jump table -------------------- 0xc0abb749676 86 e88fc9bbff call 0xc0abb30600a ;; deoptimization bailout 1 0xc0abb74967b 91 e894c9bbff call 0xc0abb306014 ;; deoptimization bailout 2

Let TurboFan do the work

            
              const getPropX = obj => obj.x

              for(var i=0; i<10000; i++) {
                getPropX({ x: i })
              }
            
          
          

0xc0abb749632 18 488bf0 REX.W movq rsi,rax ;;; <@18,#13> stack-check 0xc0abb749635 21 493ba5880a0000 REX.W cmpq rsp,[r13+0xa88] 0xc0abb74963c 28 7305 jnc 35 (0xc0abb749643) 0xc0abb74963e 30 e89d35efff call StackCheck (0xc0abb63cbe0) ;; code: BUILTIN ;;; <@20,#13> lazy-bailout ;;; <@21,#13> gap 0xc0abb749643 35 488b4510 REX.W movq rax,[rbp+0x10] ;;; <@22,#15> check-non-smi 0xc0abb749647 39 a801 test al,0x1 0xc0abb749649 41 0f8427000000 jz 86 (0xc0abb749676) ;;; <@24,#16> check-maps 0xc0abb74964f 47 49bad9bde1bd02370000 REX.W movq r10,0x3702bde1bdd9 ;; object: 0x3702bde1bdd9 Map(FAST_HOLEY_ELEMENTS) 0xc0abb749659 57 4c3950ff REX.W cmpq [rax-0x1],r10 0xc0abb74965d 61 0f8518000000 jnz 91 (0xc0abb74967b) ;;; <@26,#17> load-named-field 0xc0abb749663 67 8b401b movl rax,[rax+0x1b] ;;; <@28,#21> smi-tag 0xc0abb749666 70 8bd8 movl rbx,rax 0xc0abb749668 72 48c1e320 REX.W shlq rbx, 32 ;;; <@29,#21> gap 0xc0abb74966c 76 488bc3 REX.W movq rax,rbx ;;; <@30,#19> return 0xc0abb74966f 79 488be5 REX.W movq rsp,rbp 0xc0abb749672 82 5d pop rbp 0xc0abb749673 83 c21000 ret 0x10 ;;; -------------------- Jump table -------------------- 0xc0abb749676 86 e88fc9bbff call 0xc0abb30600a ;; deoptimization bailout 1 0xc0abb74967b 91 e894c9bbff call 0xc0abb306014 ;; deoptimization bailout 2

          
            getPropX({ x: 13, y: 20 })
          
        

BAILOUT!!!

Try it again...

            
              const getPropX = obj => obj.x

              for(var i=0; i<10000; i++) {
                getPropX({ x: i })
                getPropX({ x: i, y: i })
                getPropX({ x: i, z: i })
              }
            
          
          

0x18eb24149772 18 488bf0 REX.W movq rsi,rax ;;; <@18,#13> stack-check 0x18eb24149775 21 493ba5880a0000 REX.W cmpq rsp,[r13+0xa88] 0x18eb2414977c 28 7305 jnc 35 (0x18eb24149783) 0x18eb2414977e 30 e85d34efff call StackCheck (0x18eb2403cbe0) ;; code: BUILTIN ;;; <@20,#13> lazy-bailout ;;; <@21,#13> gap 0x18eb24149783 35 488b4510 REX.W movq rax,[rbp+0x10] ;;; <@22,#15> check-non-smi 0x18eb24149787 39 a801 test al,0x1 0x18eb24149789 41 0f8447000000 jz 118 (0x18eb241497d6) ;;; <@24,#16> check-maps 0x18eb2414978f 47 49bad9bd9144b9130000 REX.W movq r10,0x13b94491bdd9 ;; object: 0x13b94491bdd9 Map(FAST_HOLEY_ELEMENTS)> 0x18eb24149799 57 4c3950ff REX.W cmpq [rax-0x1],r10 0x18eb2414979d 61 7424 jz 99 (0x18eb241497c3) 0x18eb2414979f 63 49ba89be9144b9130000 REX.W movq r10,0x13b94491be89 ;; object: 0x13b94491be89 Map(FAST_HOLEY_ELEMENTS)> 0x18eb241497a9 73 4c3950ff REX.W cmpq [rax-0x1],r10 0x18eb241497ad 77 7414 jz 99 (0x18eb241497c3) 0x18eb241497af 79 49bae1be9144b9130000 REX.W movq r10,0x13b94491bee1 ;; object: 0x13b94491bee1 Map(FAST_HOLEY_ELEMENTS)> 0x18eb241497b9 89 4c3950ff REX.W cmpq [rax-0x1],r10 0x18eb241497bd 93 0f8518000000 jnz 123 (0x18eb241497db) ;;; <@26,#17> load-named-field 0x18eb241497c3 99 8b401b movl rax,[rax+0x1b] ;;; <@28,#21> smi-tag 0x18eb241497c6 102 8bd8 movl rbx,rax 0x18eb241497c8 104 48c1e320 REX.W shlq rbx, 32 ;;; <@29,#21> gap 0x18eb241497cc 108 488bc3 REX.W movq rax,rbx ;;; <@30,#19> return 0x18eb241497cf 111 488be5 REX.W movq rsp,rbp 0x18eb241497d2 114 5d pop rbp 0x18eb241497d3 115 c21000 ret 0x10 ;;; -------------------- Jump table -------------------- 0x18eb241497d6 118 e82fc8bbff call 0x18eb23d0600a ;; deoptimization bailout 1 0x18eb241497db 123 e834c8bbff call 0x18eb23d06014 ;; deoptimization bailout 2

9.1.9.1 OrdinarySet ( O, P, V, Receiver )

When the abstract operation OrdinarySet is called with Object O, property key P, value V, and ECMAScript language value Receiver, the following steps are taken:

  1. Assert: IsPropertyKey(P) is true.
  2. Let ownDesc be ? O.[[GetOwnProperty]](P).
  3. If ownDesc is undefined, then
    1. Let parent be ? O.[[GetPrototypeOf]]().
    2. If parent is not null, then
      1. Return ? parent.[[Set]](P, V, Receiver).
    3. Else,
      1. Set ownDesc to the PropertyDescriptor{[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
  4. If IsDataDescriptor(ownDesc) is true, then
    1. If ownDesc.[[Writable]] is false, return false.
    2. If Type(Receiver) is not Object, return false.
    3. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
    4. If existingDescriptor is not undefined, then
      1. If IsAccessorDescriptor(existingDescriptor) is true, return false.
      2. If existingDescriptor.[[Writable]] is false, return false.
      3. Let valueDesc be the PropertyDescriptor{[[Value]]: V}.
      4. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
    5. Else Receiver does not currently have a property P,
      1. Return ? CreateDataProperty(Receiver, P, V).
  5. Assert: IsAccessorDescriptor(ownDesc) is true.
  6. Let setter be ownDesc.[[Set]].
  7. If setter is undefined, return false.
  8. Perform ? Call(setter, Receiver, « V »).
  9. Return true.
          

Property access

if (obj) { return obj.x } ... ........... 27 cmpq [r13-0x40],rax 31 jz 128 37 test al,0x1 39 setzl bl 42 movzxbl rbx,rbx 45 cmpl rbx,0x0 48 jnz 185 54 cmpq [r13-0x38],rax 58 jz 128 64 movq rdx,[rax-0x1] 68 testb [rdx+0xc],0x10 72 jnz 128 78 cmpq [r13+0x50],rdx 82 jz 160 160 vmovsd xmm0,[rax+0x7] 165 movq [rbp-0x18],rbx 169 vxorpd xmm1,xmm1,xmm1 173 vucomisd xmm1,xmm0 177 jz 128 179 movq rbx,[rbp-0x18] 183 jmp 88 185 movq [rbp-0x18],rbx 189 cmpq rax,0x0 193 jz 128 195 movq rbx,[rbp-0x18] 199 jmp 88 ... ...........
          

Property access

if (obj !== undefined) { return obj.x } ... ........... 23 cmpq [r13-0x60],rax 27 jz 72 ... ...........
          

Try it

https://github.com/v8/v8/wiki

node --v8-options node --print_opt_code test.js > nodejsOut.txt node --print_opt_code --turbo test.js > nodejsOut.txt d8 --print-opt-code text.js > d8Out.txt

Do not optimize code per engine!!!

Code readability gets worse

Each engine is different

V8 is changing on daily basis :D

Users use different engines either way

Thanks

Questions?

Kemal Erdem | github.com/burnpiro