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.