10 years of Dart

by Vyacheslav Egorov

[email protected]

Evolution of the Dart platform


                                     dynamic                static
                        ──────────────────────────── ──────────
                        2011     2013     2015         2018     2020
                        ──────────────────────────────────────►
                        0.x       1.0                  2.0      2.12
                        ───────JIT────── ──────JIT or AOT───────
                                                  ╱        ╲    NNBD
                                      development          deployment
                    

Dart 1


                        Dog d = new Cat();  // ignored type annotations
                                            //   access to a field
                        d.f                 // ─ invocation of getter
                                            //  method tear-off
                                            //   noSuchMethod invocation

                        int x = null;       // no primitive types
                        (1 << 100) >> 100;  // == 1, int is bignum
                    

Dart 1 was designed to be JITed


                                /* How to make a JIT */

                        ╔══════════╗               ┌──────────┐
                        ║ Compiler ║ ▶ generates ▶ │   Code   │
                        ╚══════════╝               └──────────┘
                             ▲                          │
                             ╰──────────────────────────╯
                                 execution feedback
                    

2012 Dart's artisanal JIT

— generate unoptimized code;

— collect type feedback through inline caches;

— generate optimized code using type feedback;

— rinse and repeat.


                            // call-site specific cache
                      ─── ICData { ... }
                     ╱
                 dog.bark();
                     ╲      // generic dispatch stub
                      ─── dispatch(icData, receiver, ...) { ... }
                

d.bark() (unoptimized)


                mov RBX, [PP + 0x7f] ; ICData
                call [PP + 0x87]     ; dispatch stub
                ;       ╲
                ;         instead of embedding constants
                ;          into the code we access them
                ;          through object pool
                

                      ─── ICData { }
                     ╱
                 dog.bark();
                

                      ─── ICData { Dog: Dog.bark (1) }
                     ╱
                 dog.bark();
                

                      ─── ICData { Dog: Dog.bark (2) }
                     ╱
                 dog.bark();
                

                      ─── ICData { Dog: Dog.bark (3) }
                     ╱
                 dog.bark();
                

                      ─── ICData { Dog: Dog.bark (555) }
                     ╱
                 dog.bark();
                

d.bark() (optimized, monomorphic)


                ;; Check that RAX contains a Dog
                test al, 0x1
                jz ->deopt  ; deopt if RAX is a Smi
                movzx rcx, word ptr [rax + 0x1]
                cmp rcx, 0x53a
                jnz ->deopt
                ;; Call Dog.bark directly.
                mov r10, qword ptr [PP + 0x5f] ; argdesc
                call qword ptr [PP + 0x67]
                

d.bark() (optimized, monomorphic)


                ;; Check that RAX contains a Dog
                test al, 0x1
                jz ->deopt  ; deopt if RAX is a Smi
                movzx rcx, word ptr [rax + 0x1]
                cmp rcx, 0x53a
                jnz ->deopt
                ;; Call Dog.bark directly.
                mov r10, qword ptr [PP + 0x5f] ; argdesc
                call qword ptr [PP + 0x67]
                

d.bark() (optimized, monomorphic)


                ;; Check that RAX contains a Dog
                test al, 0x1
                jz ->deopt  ; deopt if RAX is a Smi
                movzx rcx, word ptr [rax + 0x1]
                cmp rcx, 0x53a
                jnz ->deopt
                ;; Call Dog.bark directly.
                mov r10, qword ptr [PP + 0x5f] ; argdesc
                call qword ptr [PP + 0x67]
                

                                 dynamic
                    ──────────────────────────────────────────
                    2011     2013
                    ─────────────────────────────────────────►
                    0.x       1.0
                    ───────JIT─────────────────────────────────

                     
                

                                 dynamic
                    ──────────────────────────────────────────
                    2011     2013
                    ────────────────────────────────────────►
                    0.x       1.0  │
                    ───────JIT────────────────────────────────
                                   │
                                Flutter
                
«Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.»
— https://flutter.dev

«Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.»
— https://flutter.dev

No JITing on iOS

(... unless you have special entitlements)

Need AOT!

2015 JIT ⇒ AOT

  1. disable speculations;
  2. compile all potentially reachable code;
  3. serialize heap

snapshot /ˈsnæpˌʃɒt/

(noun) serialized form for heap object graphs, used for faster startup or exchanging data between isolates. Similar to Smalltalk images. Only supported data, not machine code.

          instructions               pool
      ┌─────────────────────┐    ┌─┬─┬─┬─┬─┬─┬─
      │ object_pool_ ──────────┤ │ │ │ │ ││
      ├─────────────────────┤    └─┴─┴─┴─┴─┴┴─
entry │ mov PP, [RIP-0x25]  │               │
      │ ...                 │            ┌──┴───┐
      │ mov rax, [PP+0x2f]  │            │object│
      │ ...                 │            └──────┘
      └─────────────────────┘
                
          instructions (RX)          pool (RO)
      ┌─────────────────────┐    ┌─┬─┬─┬─┬─┬─┬─
      │ object_pool_ ──────────┤ │ │ │ │ ││
      ├─────────────────────┤    └─┴─┴─┴─┴─┴┴─
entry │ mov PP, [RIP-0x25]  │               │
      │ ...                 │            ┌──┴───┐
      │ mov rax, [PP+0x2f]  │            │object│
      │ ...                 │            └──────┘
      └─────────────────────┘
                

Dart Isolates


╔════════════════════════╗  ╔════════════════════════╗
║ Isolate    ┌─────────┐ ║  ║ Isolate    ┌─────────┐ ║
║           ┌─────────┐│ ║  ║           ┌─────────┐│ ║
║          ┌─────────┐│┘ ║  ║          ┌─────────┐│┘ ║
║          │         │┘  ║  ║          │         │┘  ║
║          └─────────┘   ║  ║          └─────────┘   ║
╚════════════════════════╝  ╚════════════════════════╝
                    

Dart Isolates


╔════════════════════════╗  ╔════════════════════════╗
║ Isolate    ┌─────────┐ ║  ║ Isolate    ┌─────────┐ ║
║           ┌─────────┐│ ║  ║           ┌─────────┐│ ║
║          ┌─────────┐│┘ ║  ║          ┌─────────┐│┘ ║
║          │ Pool    │┘  ║  ║          │ Pool    │┘  ║
║          └─────────┘   ║  ║          └─────────┘   ║
╚════════════════════════╝  ╚════════════════════════╝
                    
          instructions (RX)   ╏      pool (RW)
      ┌─────────────────────┐ ╏  ┌─┬─┬─┬─┬─┬─┬─
      │ object_pool_ ──────────┤ │ │ │ │ ││
      ├─────────────────────┤ ╏  └─┴─┴─┴─┴─┴┴─
entry │ mov PP, [RIP-0x25]  │ ╏             │
      │ ...                 │ ╏          ┌──┴───┐
      │ mov rax, [PP+0x2f]  │ ╏          │object│
      │ ...                 │ ╏ isolate  └──────┘
      └─────────────────────┘ ╏ heap
                
          instructions (RX)   ╏
                              ╏
                              ╏
      ┌─────────────────────┐ ╏
entry │ mov PP, [RIP-0x25]  │ ╏  ┌──────┐
      │ ...                 │ ╏  │ pool │
      │ mov rax, [PP+0x2f]  │ ╏  └──────┘
      │ ...                 │ ╏ isolate
      └─────────────────────┘ ╏ heap
                
          instructions (RX)   ╏           ┌──────┐
                              ╏           │ code │
                 ┌────────────────┐       └────┘
      ┌──────────┴──────────┐ ╏   └─────────┘  │
entry │ mov PP, [RIP-0x25]  │ ╏  ┌──────┐      │
      │ ...                 │ ╏  │ pool ├──────┘
      │ mov rax, [PP+0x2f]  │ ╏  └──────┘
      │ ...                 │ ╏ isolate
      └─────────────────────┘ ╏ heap
                
          instructions (RX)   ╏           ┌──────┐
                              ╏           │ code │
                 ┌────────────────┐       └────┘
      ┌──────────┴──────────┐ ╏   └─────────┘  │
entry │ mov PP, [CR+pp_ofs] │ ╏  ┌──────┐      │
      │ ...                 │ ╏  │ pool ├──────┘
      │ mov rax, [PP+0x2f]  │ ╏  └──────┘
      │ ...                 │ ╏ isolate
      └─────────────────────┘ ╏ heap
                

                ; STATIC CALLS
                ; ═ BEFORE ═══════════════════════════════════════
                call [PP + 0x87]          ; direct call
                  mov PP, [RIP - 0x25]   ; in callee load PP

                ; ═ AFTER ════════════════════════════════════════
                mov CR, [PP + 0x87]       ; load Code object
                call [CR + entry_ofs]     ; call through Code
                  mov PP, [CR + pp_ofs]  ; in callee load PP
                

2015 JIT ⇒ AOT

  1. disable speculations;
  2. compile all potentially reachable code;
  3. serialize heap ✓

                                 dynamic
                    ──────────────────────────────────────────
                    2011     2013     2015 2016
                    ───────────────────────────────────────►
                    0.x       1.0          └ we are here
                    ───────JIT────── ──────JIT or AOT────────
                                              ╱        ╲
                                  development          deployment
                

everything is a dynamic call

Optimizing Dart 1 AOT

  • inline caching;
  • inline fast paths;
  • local optimizations;
  • some global optimizations (CHA); remember: AOT was built JIT so it is not really well set for global optimizations

code (rx)         pool (rw)
                 ┌───┐
     ──────────▶│ ────▶ ICData
    ╱            ├───┤⬉
dog.bark();      │   │ can patch these
    ╲            ├───┤⬋
     ──────────▶│ ────▶ DispatchStub
                 ├───┤
                

                  code (rx)         pool (rw)
                                   ┌───┐
                       ──────────▶│ ────▶ class Dog
                      ╱            ├───┤
                  dog.woof();      │   │
                      ╲            ├───┤     Dog.woof
                       ──────────▶│ ────▶ ┌────────────────────────────────┐
                                   ├───┤     │ if (this.class != icData)      │
                                      normal │   return monomorphicMiss(...); │
                                       entry⬊├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
                

inline arithmetic fast paths

  • operators can be overridden
  • int is nullable
  • int is arbitrary precision

           ;; Y <- X + 1
           test X, 1
           jnz slow──┐
           mov Y, X   │
           add Y, 2   │
           jo slow───┤
     ┌─▶ done:        │
     │     ...        │
     │   slow: ◀──────┘ ;; "cold" code
     │     ;; ... X.operator+(1) call ...
     └────jmp done
                

scraping the bottom of the barrel

2015 state of the union

DevelopmentProduction
NativeJITJIT
WebDartium†            AOT (closed world)

† Fork of Chromium with Dart VM embedded

2014 DDC & strong mode

  • Dartium was a maintenance liability
  • dart2js too slow
  • An idea was born to build a modular AOT compiler operating on a stronger typed Dart.

2016 Dart Kernel & CFE

Pace of Dart language evolution was often hindered by duplication between different language implementations. A project is born to address that.


                              Dart VM (in C++)
                              ┌─────────────────────────┐
                           ┌─▶│ Parser ─▶ AST ─▶ ...  │
                           │  └─────────────────────────┘
                           │  dart2js (in Dart)
                 ╭────────╮  ┌─────────────────────────┐
                 │  Dart  ──▶│ Parser ─▶ AST ─▶ ...  │
                 │ Source │  └─────────────────────────┘
                 ╰────────╯│  dartanalyzer (in Dart)
                           │  ┌─────────────────────────┐
                           └─▶│ Parser ─▶ AST ─▶ ┌─────┐  some tools use
                              └────────────────────│┌─────┐ analyzer as a Dart
                                                   └│     │ parser
                                                    └─────┘
                                                

                                                     Dart VM
                                                     ┌──────┐
                                                  ┌─▶│ ...  │
                                                  │  └──────┘
                                CFE         Kernel│  dart2js
                 ╭────────╮   ┌────────┐   ╭─────╮  ┌──────┐
                 │  Dart  │──▶│ Parser │─▶│ AST ──▶│ ...  │
                 │ Source |   └────────┘   ╰─────╯  └──────┘
                 ╰────────╯   (in Dart)           ┊
                                                  ┊




                            

2018 Dart 2 release

  • CFE/Kernel form front-end foundation
  • Strong mode replaces Dart 1 optional typing
  • int becomes 64-bit signed integer

Dart 1


                    Dog d = new Cat();  // ignored type annotations
                                        //   access to a field
                    d.f                 // ─ invocation of getter
                                        //  method tear-off
                                        //   noSuchMethod invocation

                    int x = null;       // no primitive types
                    (1 << 100) >> 100;  // == 1, int is bignum
                

Dart 1 ⇒ Dart 2


                    Dog d = new Cat();  // compile time error
                                        //
                    d.f                 // calls an implementation
                                        // of Dog.f
                                        //

                    int x = null;       // still no primitive types
                    (1 << 100) >> 100;  //  == 0, int is 64-bit
                

What about VM's AOT?

Dart 2 impact on AOT

  • strong mode: types you can trust
  • Kernel: whole program AST

type flow analysis (TFA)

global context sensitive analysis over Kernel to annotate it with more accurate type information than what is available in static type annotations†.

† which specify nullable interface types


groom(Animal a) {
  a.method();
}

main() {
  groom(Dog());
  feed(Cat());
}
                

groom(Animal a) {
  a.method();  /* TFA: a is non-nullable Dog */
}    /* TFA: invokes Dog.method */

main() {
  groom(Dog());
  feed(Cat());
}
                

f(int anInt, double aDouble) {
//      ╲            ╱
// conservative *boxed* representation
}

// Dart 1
f(IntegerDog(), FloatingCat());  // ooook

// Dart 2
f(null, 2.0);  // ok
                

f(int anInt, double aDouble) {
//      ╲            ╱
// In real code TFA can often prove
// that such variables are non-nullable
// allowing to use *unboxed* representation.
// ... btw this was an intern project!
}

devirtualization highlights costs of calling conventions


                ; a lot of code for a static call
                mov CR, [PP + 0x87]       ; load Code object
                call [CR + entry_ofs]     ; call through Code
                  mov PP, [CR + pp_ofs]  ; in callee load PP
                

bare instructions

  • merge all object pools together
  • no longer need to call through Code object

                    ; ═ BEFORE ═══════════════════════════════════════
                    mov CR, [PP + 0x87]       ; load Code object
                    call [CR + entry_ofs]     ; call through Code
                      mov PP, [CR + pp_ofs]  ; in callee load PP

                    ; ═ AFTER ════════════════════════════════════════
                    call #relative-offset-to-target
                

ICs are not that good at handling polymorphism

a lot of monomorphic call-sites are devirtualized by TFA

global dispatch table (GDT)

Table indexed by a combination of receiver's class id and selector id. Classical idea described in literature as row displacement dispatch tables.


   movzx cid, word ptr [obj + 15] ; load class id
   call [GDT + cid * 8 + #(sid * 8)]
                

┌────────────────────────┐ ╱
│ Selectors are numbered ├
│ such that GDT[cid+sid] │
│ gives dispatch target  │
└────────────────────────┘
                    

   movzx cid, word ptr [obj + 15] ; load class id
   call [GDT + cid * 8 + #(sid * 8)]
                

┌────────────────────────┐ ╱
│ Naive numbering 0,     ├
│ NumCids, 2*NumCids, ...│
│ is too sparse          │
└────────────────────────┘
                    

   movzx cid, word ptr [obj + 15] ; load class id
   call [GDT + cid * 8 + #(sid * 8)]
                

┌────────────────────────┐ ╱
│ Selectors are numbered ├
│ in a way that tries to │
│ minimize GDT size.     │
└────────────────────────┘
                    

                                 dynamic                static
                    ──────────────────────────── ──────────
                    2011     2013     2015         2018     2020
                    ─────────────────────────────────────
                    0.x       1.0  │               2.0│     2.12
                 vm ───────JIT───── ──────────AOT──────────
                                   │                  │
                                Flutter              We are here
                

Dart 1 ⇒ Dart 2


                    Dog d = new Cat();  // compile time error
                                        //
                    d.f                 // calls an implementation
                                        // of Dog.f
                                        //

                    int x = null;       // still no primitive types
                    (1 << 100) >> 100;  //  == 0, int is 64-bit
                

Dart 2 ⇒ Dart 2.12


                     
                     
                     
                     
                     
                    // types are now non-nullable by default
                    int x = null;          // compile time error
                     
                

Dart 2 ⇒ Dart 2.12


                    // types are now non-nullable by default
                    int x = null;          // compile time error
                    int? x = null;         // ok
                    List<Dog>? dogs;
                    if (dogs != null) {
                        dogs.add(null);    // compile time error
                        dogs.add(Dog());   // ok
                    }
                

More† optimization opportunities!

† NNBD is incremental and provides an opt-out escape hatch. Compiler can only rely on static non-nullability if none of the libraries are opted out.

Demand for smaller downloads size and memory footprint.

Diverging AOT from JIT

  • compressed stack maps
  • DWARF stack traces
Unhandled exception:
Error occurred!
#0 causeError (test.dart:4)
#1 main (test.dart:9)
Unhandled exception:
Error occurred!
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 83265, tid: 4552355264, name Dart_Initialize
build_id: 'c0371757e9bbea5ce6a27b1f22e5a25a'
isolate_dso_base: 10f396000, vm_dso_base: 10f396000
isolate_instructions: 10f3a0000, vm_instructions: 10f398000
    #00 abs 000000010f3efd87 virt 0000000000059d87 _kDartIsolateSnapshotInstructions+0x4fd87
    #01 abs 000000010f3efd62 virt 0000000000059d62 _kDartIsolateSnapshotInstructions+0x4fd62

DWARF stack traces

Opens doors for dropping symbolic information and parts of program structure.

Demand for low-overhead concurrency.

Dart Isolates


╔════════════════════════╗  ╔════════════════════════╗
║ Isolate    ┌─────────┐ ║  ║ Isolate    ┌─────────┐ ║
║           ┌─────────┐│ ║  ║           ┌─────────┐│ ║
║ ┌───┐    ┌─────────┐│┘ ║  ║  ╌╌╌     ┌─────────┐│┘ ║
║ │MSG│    │ Program │┘  ║  ║ ╎   ╎    │ Program │┘  ║
║ └─┬─┘    └─────────┘   ║  ║  ╌▲╌     └─────────┘   ║
╚═══┆════════════════════╝  ╚═══┆════════════════════╝
    └╌╌╌╌╌╌╌╌╌╌╌ copying ╌╌╌╌╌╌╌┘
                    

Dart Isolate Groups (new!)


                    ╔═════════════════════════════════════╗
                    ║ Isolate    Isolate      ┌─────────┐ ║
                    ║                        ┌─────────┐│ ║
                    ║ ┌───┐       ╌╌╌       ┌─────────┐│┘ ║
                    ║ │MSG│      ╎   ╎      │ Program │┘  ║
                    ║ └─┬─┘       ╌▲╌       └─────────┘   ║
                    ╚═══┆══════════┆══════════════════════╝
                        └╌ copying ┘
                                        

Dart Isolate Groups (new!)


                    ╔═════════════════════════════════════╗
                    ║ Isolate    Isolate      ┌─────────┐ ║
                    ║                        ┌─────────┐│ ║
                    ║ ┌───┐                 ┌─────────┐│┘ ║
                    ║ │MSG│                 │ Program │┘  ║
                    ║ └───┘                 └─────────┘   ║
                    ╚═════════════════════════════════════╝

                                        

Dart Isolate Groups (new!)


                    ╔═════════════════════════════════════╗
                    ║            Isolate      ┌─────────┐ ║
                    ║                        ┌─────────┐│ ║
                    ║ ┌───┐                 ┌─────────┐│┘ ║
                    ║ │MSG│                 │ Program │┘  ║
                    ║ └───┘                 └─────────┘   ║
                    ╚═════════════════════════════════════╝

                                        

Dart Isolate Groups (new!)


                    ╔═════════════════════════════════════╗
                    ║            Isolate      ┌─────────┐ ║
                    ║                        ┌─────────┐│ ║
                    ║   ┌───┐               ┌─────────┐│┘ ║
                    ║   │MSG│               │ Program │┘  ║
                    ║   └───┘               └─────────┘   ║
                    ╚═════════════════════════════════════╝

                                        

Dart Isolate Groups (new!)


                    ╔═════════════════════════════════════╗
                    ║            Isolate      ┌─────────┐ ║
                    ║                        ┌─────────┐│ ║
                    ║     ┌───┐             ┌─────────┐│┘ ║
                    ║     │MSG│             │ Program │┘  ║
                    ║     └───┘             └─────────┘   ║
                    ╚═════════════════════════════════════╝

                                        

Dart Isolate Groups (new!)


                    ╔═════════════════════════════════════╗
                    ║            Isolate      ┌─────────┐ ║
                    ║                        ┌─────────┐│ ║
                    ║       ┌───┐           ┌─────────┐│┘ ║
                    ║       │MSG│           │ Program │┘  ║
                    ║       └───┘           └─────────┘   ║
                    ╚═════════════════════════════════════╝

                                        

Dart Isolate Groups (new!)


                    ╔═════════════════════════════════════╗
                    ║            Isolate      ┌─────────┐ ║
                    ║                        ┌─────────┐│ ║
                    ║           ┌───┐       ┌─────────┐│┘ ║
                    ║           │MSG│       │ Program │┘  ║
                    ║           └───┘       └─────────┘   ║
                    ╚═════════════════════════════════════╝

                                        

Learnings

Everything counts

at some point

It is all about the balance

specialized vs generic

specialized solutions

but generic foundations.