Dart 代码的组件集合Dart VM4

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 这是我参与8月更文挑战的第 9 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战,未优化编译器分两遍生成机器代码:1、「遍历函数体的序列化 AST 以生成函数体的控制流图( CFG ),CFG 由填充有中间语言( IL ) 指令的基本块组成」。在此阶段使用的 IL 指令类似于基于堆栈的虚拟机的指令:它们从堆栈中获取操作数,执行操作,然后将结果推送到同一堆栈。

这是我参与8月更文挑战的第 9 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战

未优化编译器分两遍生成机器代码:

  • 1、「遍历函数体的序列化 AST 以生成函数体的控制流图( CFG ),CFG 由填充有中间语言( IL ) 指令的基本块组成」。在此阶段使用的 IL 指令类似于基于堆栈的虚拟机的指令:它们从堆栈中获取操作数,执行操作,然后将结果推送到同一堆栈。

实际上并非所有函数都具有实际的 Dart / Kernel AST 主体,例如在 C++ 中定义的本地函数或由 Dart VM 生成的人工 tear-off 函数,在这些情况下,IL 只是凭空创建,而不是从内核 AST 生成。

  • 2、生成的 CFG 使用一对多的底层 IL 指令直接编译为机器代码:每个 IL 指令扩展为多个机器语言指令。

在此阶段没有执行任何优化,未优化编译器的主要目标是快速生成可执行代码。

这也意味着:「未优化的编译器不会尝试静态解析内核二进制文件中未解析的任何调用」,VM 当前不使用基于虚拟表或接口表的调度,而是使用**「内联缓存」**实现动态调用。

内联缓存的原始实现,实际上是修补函数的 native 代码,因此得名**「内联缓存」**,内联缓存的想法可以追溯到 Smalltalk-80,请参阅 Smalltalk-80 系统的高效实现。

「内联缓存背后的核心思想,是在特定的调用点中缓存方法解析的结果」,VM 使用的内联缓存机制包括:

  • 一个调用特定的缓存( dart::UntaggedICData),它将接收者的类映射到一个方法,如果接收者是匹配的类,则应该调用该方法,缓存还存储一些辅助信息,例如调用频率计数器,用于跟踪给定类在此调用点上出现的频率;
  • 一个共享查找 stub ,它实现了方法调用的快速路径。这个 stub 搜索给定的缓存,以查看它是否包含与接收者的类匹配的条目。如果找到该条目,则 stub 将增加频率计数器和 tail-calls 用缓存方法。否则 stub 将调用一个运行时系统助手来实现方法解析逻辑。如果方法解析成功,则缓存将被更新,后续调用将不需要进入运行时系统。

如下图所示,展示了与 animal.toFace() 调用关联的内联缓存的结构和状态,该缓存使用 Dog 的实例执行了两次,使用 Cat 的实例执行了一次C。

网络异常,图片无法展示
|

未优化的编译器本身足以执行任何 Dart 代码,然而它产生的代码相当慢,这就是为什么 VM 还实现了自适应优化编译管道的原因,自适应优化背后的想法是:「使用运行程序的执行配置文件来驱动优化决策」

当未优化的代码运行时,它会收集以下信息:

  • 如上所述,内联缓存收集有关在调用点观察到的接收器类型的信息;
  • 函数和函数内的基本块相关联的执行计数器跟踪代码的热点区域;

当与函数关联的执行计数器达到一定阈值时,该函数被提交给后台优化编译器进行优化。

优化编译的启动方式与非优化编译的启动方式相同:「通过遍历序列化内核 AST ,为正在优化的函数构建未优化的 IL」

然而不是直接将 IL 处理为机器代码,而是基于表单的优化 IL, 优化编译器继续将未优化的 IL 转换为静态单赋值(SSA) ,然后基于 SSA 的 IL 根据收集的类型反馈进行专业化的推测,并通过一系列Dart 的特定优化,例如:

  • 内联(inlining);
  • 范围分析(range analysis);
  • 类型传播( type propagation);
  • 代理选择(representation selection);
  • 存储加载和加载转发(store-to-load and load-to-load forwarding);
  • 全局值编号(global value numbering);
  • 分配下沉(,allocation sinking)等,;

最后使用线性扫描寄存器和简单的一对多降低 IL 指令,将优化的 IL 转化为机器代码。

编译完成后,后台编译器会请求 mutator 线程进入安全点并将优化的代码附加到函数中。

广义上讲,当与线程相关联的状态(例如堆栈帧、堆等)一致,并且可以在不受线程本身中断的情况下访问或修改时,托管环境(虚拟机)中的线程被认为处于安全点。通常这意味着线程要么暂停,要么正在执行托管环境之外一些代码,例如运行非托管 native 代码。

下次调用此函数时, 它将使用优化的代码。某些函数包含非常长的运行循环,对于那些函数,在函数仍在运行时,将执行从未优化代码切换到优化代码是有意义的。

「这个过程被称为堆栈替换( OSR )」,它的名字是因为:一个函数版本的堆栈帧被透明地替换为同一函数的另一个版本的堆栈帧。

网络异常,图片无法展示
|

编译器源代码位于 runtime/vm/compiler 目录中;编译管道入口点是 dart::CompileParsedFunctionHelper::Compile;IL 在 runtime/vm/compiler/backend/il.h 中定义;内核到 IL 的转换从 dart::kernel::StreamingFlowGraphBuilder::BuildGraph 开始,该函数还处理各种人工函数的 IL 构建;当 InlineCacheMissHandler 处理 IC 的未命中,dart::compiler::StubCodeCompiler::GenerateNArgsCheckInlineCacheStub 为内联缓存存根生成机器代码; runtime/vm/compiler/compiler_pass.cc 定义了优化编译器传递及其顺序; dart::JitCallSpecializer 大多数基于类型反馈的专业化。

需要强调的是,优化编译器生成的代码,是在基于应用程序执行配置文件的专业推测下假设的。

例如,一个动态调用点只观察到一个 C 类的实例作为一个接收方,它将被转换成一个可以直接调用的对象,并通过检查来验证接收方是否有一个预期的 C 类。然而这些假设可能会在程序执行期间被违反:

void printAnimal(obj) {
  print('Animal {');
  print('  ${obj.toString()}');
  print('}');
}
// Call printAnimal(...) a lot of times with an intance of Cat.
// As a result printAnimal(...) will be optimized under the
// assumption that obj is always a Cat.
for (var i = 0; i < 50000; i++)
  printAnimal(Cat());
// Now call printAnimal(...) with a Dog - optimized version
// can not handle such an object, because it was
// compiled under assumption that obj is always a Cat.
// This leads to deoptimization.
printAnimal(Dog());
复制代码

每当代码正在做一些假设性优化时,它可能会在执行过程中被违反,所以它需要保证当出现违反假设的情况下,可以恢复原本的执行。

这个恢复过程又被称为去优化:当优化版本遇到它无法处理的情况时,它只是将执行转移到未优化函数的匹配点,并在那里继续执行,函数的未优化版本不做任何假设,可以处理所有可能的输入。

VM 通常在去优化后丢弃函数的优化版本,而之后再次重新优化它时,会 使用更新的类型反馈。

VM 有两种方式保护编译器做出的推测性假设:

  • 内联检查(例如CheckSmi,CheckClassIL 指令)验证假设在编译器做出此假设的使用站点是否成立。例如将动态调用转换为直接调用时,编译器会在直接调用之前添加这些检查。
  • Global guards 会运行时丢弃优化代码,当依赖的内容变化时。例如优化编译器可能会观察到某个 C 类从未被扩展,并在类型传播过程中使用此信息。然而随后的动态代码加载或类终结可能会引入一个子类 C。此时运行时需要查找并丢弃在 C 没有子类的假设下编译的所有优化代码。运行时可能会在执行堆栈上找到一些现在无效的优化代码,在这种情况下受影响的帧将被标记为“去优化”,并在执行返回时取消优化。「这种去优化被称为惰性去优化: 因为它被延迟执行,直到控制返回到优化的代码」

去优化器机制在 runtime/vm/deopt_instructions.cc 中,它本质上是一个解优化指令的微型解释器,它描述了如何从优化代码的状态,重建未优化代码的所需状态。去优化指令由 dart::CompilerDeoptInfo::CreateDeoptInfo 在编译期间针对优化代码中的每个潜在"去优化"位置生成。



相关文章
|
3月前
|
Dart API 开发工具
Dart ffi 使用问题之Dart API要在C++中使用,该如何初始化
Dart ffi 使用问题之Dart API要在C++中使用,该如何初始化
|
3月前
|
Dart API C语言
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
|
3月前
|
Dragonfly Dart NoSQL
Dart ffi 使用问题之在Dart中调用String的toNativeUtf8方法时有什么是需要注意的
Dart ffi 使用问题之在Dart中调用String的toNativeUtf8方法时有什么是需要注意的
|
3月前
|
Dart 编译器 API
Dart ffi 使用问题之在C++线程中无法直接调用Dart函数的问题如何解决
Dart ffi 使用问题之在C++线程中无法直接调用Dart函数的问题如何解决
|
前端开发 JavaScript Android开发
React Native 和 Flutter对比,包含代码实例
@[TOC](目录) React Native 和 Flutter 都是流行的跨平台移动应用开发框架。虽然它们在很多方面都有相似之处,但它们也有一些不同之处。在本文中,我们将详细比较这两个框架,讨论它们的优缺点、应用场景、性能以及包含的代码。 # 1. 框架概述 React Native 是由 Facebook 开发的一个开源框架,它允许开发者使用 JavaScript 和 React 来创建原生移动应用。React Native 可以用于开发 iOS、Android 和 Web 应用。它使用 JavaScript 的生态系统和工具,如 Babel、ESLint 和 React 工具链,使得开
187 0
|
Dart 程序员 C++
Flutter 基础 | Dart 语法 mixin
Flutter 基础 | Dart 语法 mixin
126 0
|
Dart 编译器 索引
Flutter 基础 | Dart 语法
Flutter 基础 | Dart 语法
173 0
|
Dart
dart 类型提升
dart 类型提升
119 0
|
Dart
Dart之 方法定义
Dart之 方法定义
107 0
Dart之 方法定义
|
缓存 Dart 编译器
Dart 代码的组件集合Dart VM2
这是我参与8月更文挑战的第 7 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战, Dart 代码的组件集合Dart VM 2
189 1
下一篇
无影云桌面