🧬 Dart VM Kernel

加载与类型更新机制

深入理解 Dart 运行时核心

基于 Dart SDK 源码分析

📦 什么是 Kernel?

Kernel 是 Dart 的中间表示 (IR),用于编译器和运行时之间传递程序信息

格式扩展名用途
Kernel Binary.dill编译产物,VM 加载
Kernel AST-内存中的抽象语法树
Kernel Text.txt调试用的文本格式

🎯 Kernel 设计目标

  • 语言无关 - 支持多种前端语言
  • 平台无关 - 可用于 AOT/JIT
  • 紧凑高效 - 二进制格式体积小
  • 完整类型 - 保留所有类型信息
  • 可增量 - 支持热重载

📄 Kernel 文件结构

.dill 文件 (Kernel Binary)
│
├── Magic Number (0x90ABCDEF)
├── Version Information
│
├── String Table
│   └── 所有字符串常量
│
├── Canonical Name Table
│   └── 符号引用
│
├── Library List
│   ├── Library 1
│   │   ├── Class List
│   │   ├── Procedure List
│   │   └── Field List
│   └── Library 2 ...
│
├── Constant Pool
│   └── 所有常量值
│
└── Type Table
    └── 所有类型定义

🔢 Kernel 二进制格式

区域内容大小
Header魔数、版本固定
String Table字符串池可变
Canonical Names符号表可变
Libraries库定义可变
Constants常量池可变

使用变长整数编码减少文件体积

🌳 Kernel AST 节点

TreeNode (基类)
│
├── Library (库)
│   ├── Class (类)
│   │   ├── Constructor (构造函数)
│   │   ├── Procedure (方法)
│   │   └── Field (字段)
│   │
│   ├── Procedure (顶级函数)
│   └── Field (顶级变量)
│
├── Expression (表达式)
│   ├── VariableGet
│   ├── PropertyGet
│   ├── MethodInvocation
│   └── ...
│
└── Statement (语句)
    ├── Block
    ├── IfStatement
    ├── ForStatement
    └── ...

📚 Library 节点

class Library extends NamedNode {
  final StringReference importUri;
  final StringReference name;
  
  final List<Class> classes;
  final List<Field> fields;
  final List<Procedure> procedures;
  
  final List<LibraryDependency> dependencies;
  // 导入的其他库
}

📦 Class 节点

class Class extends NamedNode {
  final Library enclosingLibrary;
  final StringReference name;
  
  final List<TypeParameter> typeParameters;  // 泛型参数
  final Supertype? superType;                // 父类
  final List<Supertype> implementedTypes;    // 接口
  
  final List<Field> fields;
  final List<Constructor> constructors;
  final List<Procedure> procedures;
}

⚡ Procedure 节点

class Procedure extends NamedNode {
  final Name name;
  final ProcedureKind kind;
  // Method, Getter, Setter, Operator, Factory
  
  final FunctionNode function;
  // 函数体和参数
  
  final List<TypeParameter> typeParameters;
  // 泛型方法参数
}

🔤 类型表示

类型Kernel 类示例
基础类型InterfaceTypeint, String
泛型类型InterfaceTypeList<int>
类型参数TypeParameterTypeT, S
函数类型FunctionTypevoid Function(int)
可空类型NullableTypeString?
NeverNeverTypeNever

📊 常量池

常量池存储所有编译时常量,减少重复

  • 数值常量:int, double
  • 字符串常量:String
  • 布尔常量:true, false
  • 列表常量:const []
  • Map 常量:const {}
  • 对象常量:const MyClass()
  • 类型常量:int, List<String>

📥 KernelLoader 概述

源码:runtime/vm/kernel_loader.cc

KernelLoader 负责解析 Kernel 二进制文件并创建 VM 运行时对象

class KernelLoader {
  static ErrorPtr LoadProgram(
    const uint8_t* buffer,
    intptr_t buffer_size,
  );
  
  static ErrorPtr LoadLibrary(
    const Library& library,
  );
}

🔄 加载流程

.dill 文件
     │
     ▼
┌─────────────────┐
│  Read Header    │ 读取魔数和版本
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Read Strings   │ 加载字符串表
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Read Names     │ 加载符号表
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Read Libraries │ 加载所有库
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Link Types     │ 解析类型引用
└─────────────────┘

📖 ReadProgram

ErrorPtr KernelLoader::ReadProgram(
  const uint8_t* buffer,
  intptr_t size,
) {
  // 1. 解析 Header
  Reader reader(buffer, size);
  auto magic = reader.ReadUInt32();
  if (magic != kMagicNumber) {
    return ILLEGAL_ARGUMENT;
  }
  
  // 2. 读取字符串表
  reader.ReadStringTable();
  
  // 3. 读取库列表
  for (auto lib : libraries) {
    ReadLibrary(lib);
  }
}

📚 ReadLibrary

void ReadLibrary(const Library& lib) {
  // 1. 创建 VM Library 对象
  auto vm_lib = Library::Handle();
  vm_lib = Library::New(url, name);
  
  // 2. 加载类
  for (auto cls : lib.classes) {
    ReadClass(cls, vm_lib);
  }
  
  // 3. 加载顶级成员
  for (auto proc : lib.procedures) {
    ReadProcedure(proc, vm_lib);
  }
}

📦 ReadClass

void ReadClass(const Class& cls, Library& lib) {
  // 1. 创建 VM Class 对象
  auto vm_cls = Class::New(lib, name);
  
  // 2. 设置父类和接口
  vm_cls.set_super_type(ReadType(cls.superType));
  
  // 3. 加载字段
  for (auto field : cls.fields) {
    ReadField(field, vm_cls);
  }
  
  // 4. 加载方法
  for (auto proc : cls.procedures) {
    ReadProcedure(proc, vm_cls);
  }
}

⚡ ReadProcedure

void ReadProcedure(
  const Procedure& proc,
  Class& owner,
) {
  // 1. 创建 Function 对象
  auto func = Function::New(
    name,
    kind,  // Method, Getter, Setter
    owner,
  );
  
  // 2. 设置签名
  func.set_signature(ReadSignature(proc.function));
  
  // 3. 延迟加载函数体
  // 函数体在首次调用时编译
}

🔤 类型解析

类型解析需要延迟处理,因为类型可能引用尚未加载的类

TypePtr ReadType(const DartType& type) {
  switch (type.kind) {
    case Interface:
      return InterfaceType::New(
        klass,
        type_args,
      );
    case TypeParameter:
      return TypeParameterType::New(param);
    case Function:
      return FunctionType::New(signature);
  }
}

📤 增量加载

Hot Reload 使用增量 Kernel只加载修改的部分

ErrorPtr ReloadSources(
  IsolateGroup* group,
  const uint8_t* kernel_bytes,
  intptr_t kernel_size,
) {
  // 1. 加载增量 Kernel
  auto delta = ReadDelta(kernel_bytes);
  
  // 2. 更新修改的类
  for (auto cls : delta.modified_classes) {
    UpdateClass(cls);
  }
  
  // 3. 迁移现有实例
  MorphInstances();
}

🔄 热重载类型更新

增量 Kernel (.dill)
       │
       ▼
┌──────────────────┐
│  Parse Delta     │ 解析增量部分
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Compare Classes │ 对比新旧类定义
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Create Mapping  │ 创建字段映射
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Morph Instances │ 变换现有实例
└──────────────────┘

🎭 InstanceMorpher 概述

源码:runtime/vm/instance_morpher.cc

InstanceMorpher 负责将旧类实例变形为新类实例

  • 遍历堆中所有旧类实例
  • 调整内存布局
  • 迁移字段值
  • 更新类型指针

🔄 Become 机制

Become 是 Dart VM 的指针置换机制

// Become 核心操作
void Become::ElementsForward(
  const Array& old_arr,
  const Array& new_arr,
) {
  // 将所有指向 old_arr[i] 的指针
  // 替换为指向 new_arr[i]
  
  // 这包括:
  // - 堆中的引用
  // - 栈中的引用
  // - 全局变量
}

📋 字段迁移

修改类型迁移策略
添加字段新字段初始化为 null/默认值
删除字段字段值被丢弃
重命名字段值迁移到新位置
修改类型尝试转换,失败则为 null
调整顺序按映射重新排列

✅ 类型兼容性

✅ 兼容修改

  • 添加新字段
  • 添加新方法
  • 修改方法实现
  • 添加默认参数

❌ 不兼容修改

  • 修改构造函数
  • 修改枚举值
  • 修改泛型约束
  • 修改 mixin

⚠️ 迁移限制

以下修改无法通过 Hot Reload完成,需要 Hot Restart

  • ❌ 修改类的构造函数签名
  • ❌ 添加/删除枚举值
  • ❌ 修改泛型类型参数约束
  • ❌ 修改 mixin 应用
  • ❌ 修改 extension 定义

🔄 Isolate 与 Kernel

每个 Isolate 都有独立的Kernel 加载状态

  • Isolate 拥有独立的类表
  • Isolate 拥有独立的常量池
  • Hot Reload 需要更新所有 Isolate

👥 IsolateGroups

IsolateGroup 共享只读 Kernel 数据

┌─────────────────────────────────────┐
│          IsolateGroup               │
│  ┌─────────────────────────────┐   │
│  │  Shared Kernel Data         │   │
│  │  (classes, constants, ...)  │   │
│  └───────────┬─────────────────┘   │
│              │                      │
│   ┌──────────┴──────────┐          │
│   │                     │          │
│  ▼                     ▼          │
│ Isolate 1           Isolate 2     │
│ (独立堆)           (独立堆)        │
└─────────────────────────────────────┘

📦 共享 Kernel

// IsolateGroup 共享机制
class IsolateGroup {
  // 共享的 Kernel 程序
  Program* shared_program_;
  
  // 共享的类对象
  ClassTable* shared_class_table_;
  
  // 每个 Isolate 的独立数据
  void ForEachIsolate([&](Isolate* isolate) {
    // 处理独立堆
  });
}

💾 内存管理

数据类型存储位置共享
Kernel 二进制只读内存
Class 对象只读堆
常量对象只读堆
实例对象普通堆
局部变量

🗃️ Kernel 缓存

VM 缓存已加载的 Kernel 减少重复解析

class KernelCache {
  // 缓存已加载的程序
  HashMap<String, Program*> programs_;
  
  // 缓存已解析的类
  HashMap<String, Class*> classes_;
  
  Program* GetOrLoad(String url) {
    if (programs_.contains(url)) {
      return programs_[url];
    }
    return LoadAndCache(url);
  }
}

🖥️ 前端编译器

CFE (Common Front End) 是 Dart 的统一前端编译器

  • 解析 Dart 源代码
  • 执行类型推断
  • 生成 Kernel AST
  • 序列化为 .dill 文件

🏗️ CFE 架构

Dart Source (.dart)
       │
       ▼
┌─────────────────┐
│  Parser         │ 词法/语法分析
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Body Builder   │ 构建 AST
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Type Inference │ 类型推断
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Kernel Writer  │ 序列化
└────────┬────────┘
         │
         ▼
    Kernel (.dill)

📤 增量编译

class IncrementalCompiler {
  // 之前编译的状态
  Program* previous_program_;
  
  // 增量编译
  Future<Program> compile(
    List<Uri> modified_files,
  ) async {
    // 1. 复用之前的 AST
    // 2. 只重新编译修改的文件
    // 3. 生成增量 Kernel
  }
}

🔀 混合编译

混合编译允许同时使用 AOT 和 JIT 编译的代码

  • 核心库:AOT 预编译
  • 应用代码:JIT 动态编译
  • 热重载:增量 JIT 编译

⚙️ AOT vs JIT

特性AOTJIT
编译时机构建时运行时
启动速度
热重载
代码优化全局优化运行时优化
包体积较大较小

📦 AOT 编译流程

Kernel (.dill)
       │
       ▼
┌─────────────────┐
│  IL Generator   │ 生成中间语言
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  IL Optimizer   │ 优化
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Code Generator │ 生成机器码
└────────┬────────┘
         │
         ▼
   Snapshot (.snapshot)

⚡ JIT 执行流程

Kernel (.dill)
       │
       ▼
┌─────────────────┐
│  KernelLoader   │ 加载到内存
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Interpreter    │ 解释执行
│  (首次调用)      │
└────────┬────────┘
         │ 热点检测
         ▼
┌─────────────────┐
│  JIT Compiler   │ 编译为机器码
└─────────────────┘

📸 快照机制

快照将VM 状态序列化到文件,加速启动

快照类型内容用途
Core Snapshot核心库所有应用共享
App Snapshot应用代码AOT 产物
JIT SnapshotJIT 状态加速 JIT 启动

🔵 Core Snapshot

// Core Snapshot 包含
class CoreSnapshot {
  // 核心库对象
  Library dart_core;
  Library dart_async;
  Library dart_collection;
  ...
  
  // 预创建的对象
  Object* true_value;
  Object* false_value;
  Object* null_value;
}

🟢 App Snapshot

// App Snapshot 包含
class AppSnapshot {
  // 所有应用代码
  List<Library> libraries;
  
  // 编译后的代码
  List<Code> compiled_code;
  
  // 常量对象
  List<Object> constants;
  
  // 对象堆快照
  HeapSnapshot heap;
}

🐛 调试支持

Kernel 保留源码位置信息用于调试

  • 文件名和行号
  • 列号信息
  • 变量名映射
  • 类型信息

📍 源码映射

class SourceLocation {
  final Uri fileUri;       // 文件路径
  final int line;           // 行号
  final int column;         // 列号
  final int tokenPosition;   // Token 位置
}

// 每个 AST 节点都有位置信息
node.location.fileUri
node.location.line

⚡ 性能优化

加载优化

  • 延迟加载函数体
  • 共享只读数据
  • 缓存解析结果

运行优化

  • 内联缓存
  • 类型反馈
  • 热点编译

📁 源码结构

dart-sdk/runtime/vm/
├── kernel_loader.cc         # Kernel 加载
├── kernel_loader.h
├── kernel_binary_flowgraph.cc
├── instance_morpher.cc      # 实例变形
├── become.cc                # 指针置换
├── object.cc                # VM 对象
├── isolate.cc               # Isolate
└── snapshot.cc              # 快照

dart-sdk/pkg/kernel/
├── lib/                     # Kernel AST 定义
└── binary/                  # 二进制格式

📄 关键类

类名职责
KernelLoader加载 Kernel 文件
InstanceMorpher实例变形
Become指针置换
ProgramKernel 程序
Library/ClassAST 节点

🔧 调试技巧

# 查看 .dill 文件内容
dart kernel-service.dart temp.dill

# 打印 Kernel AST
dart pkg/kernel/tool/dump.dart app.dill

# VM 调试输出
dart --print-flow-graph script.dart

❓ 常见问题

Hot Reload 失败

  • 检查是否有不兼容的修改
  • 查看 VM 日志错误信息
  • 尝试 Hot Restart

内存泄漏

  • 检查是否有循环引用
  • 使用 Observatory 分析

📝 总结

Kernel 加载流程

.dill → KernelLoader → VM Objects → InstanceMorpher (Hot Reload)

组件职责
CFE编译 Dart 到 Kernel
KernelLoader加载 Kernel 到 VM
InstanceMorpher热重载时变形实例
Become指针置换

🔄 核心流程

1. 前端编译
   └─▶ CFE 编译 Dart → Kernel (.dill)

2. 加载解析
   └─▶ KernelLoader 解析二进制 → VM 对象

3. 热重载更新
   └─▶ 加载增量 Kernel → InstanceMorpher 变形

4. 指针置换
   └─▶ Become 替换旧对象指针

🎯 关键要点

  • 📦 Kernel 是 Dart 的中间表示
  • 📥 KernelLoader 解析二进制创建 VM 对象
  • 🎭 InstanceMorpher 实现热重载实例变形
  • 🔄 Become 机制实现指针原子替换
  • 👥 IsolateGroup 共享只读 Kernel 数据
  • 📸 快照机制加速启动

📚 参考资料

  • 🔗 Dart Kernel: github.com/dart-lang/kernel
  • 🔗 Dart VM: github.com/dart-lang/sdk/tree/main/runtime/vm
  • 🔗 CFE: github.com/dart-lang/sdk/tree/main/pkg/front_end
  • 🔗 Kernel Binary Format: github.com/dart-lang/kernel/blob/master/docs/Kernel-Binary-Format.md

🧬 Happy Coding!