V8 的 TurboFan 的效果和控制边缘有什么区别?
What's the difference between effect and control edges of V8's TurboFan?
我已经阅读了很多博文、文章、演示文稿和视频,甚至检查了 V8 的源代码,包括字节码生成器、节点海图生成器和优化阶段,但仍然找不到合适的回答。
V8 的优化编译器 TurboFan 使用“节点海”类型的 IR。我找到的所有关于它的学术文章都说它基本上是一个 CFG 与数据流图的结合,因此有两种类型的边来连接节点:数据边和控制边。基本上,如果你只取数据边,你会形成一个数据流图,而如果你选择控制边,你会得到一个控制流图。
但是,TurboFan 还有一种边缘类型:“效应边缘”(和效应 phis)。我想这就是 this slide 表示这不是节点的“海”而是节点的“汤”时的意思,因为我在其他任何地方都找不到这个术语。据我了解,效果边缘帮助编译器保持 statements/expressions 的结构,如果重新排序将产生可见的副作用。每个人使用的示例是 o.f = o.f + 1
:加载必须在存储之前(否则我们将读取新值),加法也必须在存储之前(否则我们将存储旧值值并无用地增加结果)。
但是我无法理解:这不就是控制边的目的吗?通过搜索代码,我可以看到几乎每个节点都有一个效果边 和 一个控制边。但它们的用途并不明显。据我了解,在节点海洋中,您使用控制边来限制评估顺序。那么为什么我们需要效果边和控制边呢?可能我遗漏了一些基本的东西,但我找不到它。
TL;DR:效果边和 EffectPhi 节点有什么用,它们与控制边有何不同。
非常感谢。
sea-of-nodes 编译器的想法是 IR 节点具有最大的移动自由度。这就是为什么在像 o.f = o.f + 1
这样的片段中,序列加载-添加-存储不被视为“控制流”。只有条件和分支是。因此,如果我们稍微扩展示例:
if (condition) {
o.f = o.f + 1;
} else {
// something else
}
然后,正如您在问题中描述的那样,效果依赖性确保加载-添加-存储按此顺序安排,控制依赖性确保所有三个操作仅在 condition
为真时执行(技术上,如果if
-条件的true
-分支被采用)。请注意,这对于负载也很重要;例如,如果 condition
为假,o
可能是一个无效对象,并且在这种情况下尝试加载它的 f
属性 可能会导致段错误。
我已经阅读了很多博文、文章、演示文稿和视频,甚至检查了 V8 的源代码,包括字节码生成器、节点海图生成器和优化阶段,但仍然找不到合适的回答。
V8 的优化编译器 TurboFan 使用“节点海”类型的 IR。我找到的所有关于它的学术文章都说它基本上是一个 CFG 与数据流图的结合,因此有两种类型的边来连接节点:数据边和控制边。基本上,如果你只取数据边,你会形成一个数据流图,而如果你选择控制边,你会得到一个控制流图。
但是,TurboFan 还有一种边缘类型:“效应边缘”(和效应 phis)。我想这就是 this slide 表示这不是节点的“海”而是节点的“汤”时的意思,因为我在其他任何地方都找不到这个术语。据我了解,效果边缘帮助编译器保持 statements/expressions 的结构,如果重新排序将产生可见的副作用。每个人使用的示例是 o.f = o.f + 1
:加载必须在存储之前(否则我们将读取新值),加法也必须在存储之前(否则我们将存储旧值值并无用地增加结果)。
但是我无法理解:这不就是控制边的目的吗?通过搜索代码,我可以看到几乎每个节点都有一个效果边 和 一个控制边。但它们的用途并不明显。据我了解,在节点海洋中,您使用控制边来限制评估顺序。那么为什么我们需要效果边和控制边呢?可能我遗漏了一些基本的东西,但我找不到它。
TL;DR:效果边和 EffectPhi 节点有什么用,它们与控制边有何不同。
非常感谢。
sea-of-nodes 编译器的想法是 IR 节点具有最大的移动自由度。这就是为什么在像 o.f = o.f + 1
这样的片段中,序列加载-添加-存储不被视为“控制流”。只有条件和分支是。因此,如果我们稍微扩展示例:
if (condition) {
o.f = o.f + 1;
} else {
// something else
}
然后,正如您在问题中描述的那样,效果依赖性确保加载-添加-存储按此顺序安排,控制依赖性确保所有三个操作仅在 condition
为真时执行(技术上,如果if
-条件的true
-分支被采用)。请注意,这对于负载也很重要;例如,如果 condition
为假,o
可能是一个无效对象,并且在这种情况下尝试加载它的 f
属性 可能会导致段错误。