这里需要注意,该 Flutter 工具不处理 Dart 本身的解析, 相反它会生成另一个持久进程 frontend_server
,它本质上是围绕 CFE 和一些 Flutter 特定的 Kernel-to-Kernel 转换的封装。
frontend_server
将 Dart 源代码编译为内核文件, 然后 flutter 将其发送到设备, 当开发人员请求热重载时 frontend_server
开始发挥作用:在这种情况下 frontend_server
可以重用先前编译中的 CFE 状态,并重新编译实际更改的库。
一旦内核二进制文件加载到 VM 中,它就会被解析以创建代表各种程序实体的对象,然而这个过程是惰性完成的:首先只加载关于库和类的基本信息,源自内核二进制文件的每个实体都保留一个指向二进制文件的指针,以便以后可以根据需要加载更多信息。
每当我们引用 VM 内部分配的对象时,我们都会使用 Untagged 前缀,因为这遵循了 VM 自己的命名约定:内部 VM 对象的布局由 C++ 类定义,名称以 Untagged头文件
runtime/vm/raw_object.h
开头。例如dart::UntaggedClass
是描述一个 Dart 类 VM 对象,dart::UntaggedField
是一个 VM 对象
只有在运行时需要它时(例如查找类成员、分配实例等),有关类的信息才会完全反序列化,在这个阶段,类成员会从内核二进制文件中读取,然而在此阶段不会反序列化完整的函数体,只会反序列化它们的签名。
此时 methods 在运行时可以被成功解析和调用,因为已经从内核二进制文件加载了足够的信息,例如它可以解析和调用 main
库中的函数。
package:kernel/ast.dart
定义了描述内核 AST 的类;package:front_end
处理解析 Dart 源代码并从中构建内核 AST。dart::kernel::KernelLoader::LoadEntireProgram是
将内核 AST 反序列化为相应 VM 对象的入口点;pkg/vm/bin/kernel_service.dart
实现了内核服务隔离,runtime/vm/kernel_isolate.cc
将 Dart 实现粘合到 VM 的其余部分;package:vm
承载大多数基于内核的 VM 特定功能,例如各种内核到内核的转换;由于历史原因一些特定于 VM 的转换仍然存在于package:kernel
中。
最初所有的函数都会有一个占位符,而不是它们的主体的实际可执行代码:它们指向 LazyCompileStub
,它只是要求运行时系统为当前函数生成可执行代码,然后 tail-calls
这个新生成的代码。
第一次编译函数时,是通过未优化编译器完成的。
未优化编译器分两遍生成机器代码:
- 1、遍历函数体的序列化 AST 以生成函数体的控制流图( CFG ),CFG 由填充有中间语言( IL ) 指令的基本块组成。在此阶段使用的 IL 指令类似于基于堆栈的虚拟机的指令:它们从堆栈中获取操作数,执行操作,然后将结果推送到同一堆栈。
实际上并非所有函数都具有实际的 Dart / Kernel AST 主体ÿ