Lan*_*ard 0 compiler-construction control-flow-graph
我正在开发一个编译器项目,想知道控制流图(CFG )的“基本块”的含义和实现。他们说基本块是用于没有任何分支的线性步骤序列。但首先,有几个问题:
例如,假设我有这个:
let a = 10;
let b = 0;
if ((foo() && bar()) || baz()) {
b = 20;
if (hello > world) {
b *= 3
}
} else {
b = 2 * a;
}
let c = a * b
log(a);
Run Code Online (Sandbox Code Playgroud)
这里,什么是“基本块”?
const blocks = [
{
inputs: [],
statements: [
'let a = 10',
'let b = 0',
],
outputs: ['a', 'b']
},
{
// ...?
}
]
Run Code Online (Sandbox Code Playgroud)
我不是问如何准确地实现数据流分析器,只是粗略地说在这样的示例中块应该是什么?一些伪代码会有所帮助。条件表达式(foo() && bar()) || baz()
甚至隐藏了一些分支逻辑。在我的代码中,我做了这样的事情:
if
or
and
test foo
test bar
baz
Run Code Online (Sandbox Code Playgroud)
甚至:
andResult =
and
test foo
test bar
orResult = baz | andResult
if orResult, ...
Run Code Online (Sandbox Code Playgroud)
这表明它本身就是计算的步骤。该语句应该if
放在哪里,它是否开始下一个块?基本上想知道它们通常应该如何构建。然后考虑嵌套条件分支。
如果您要根据标签和 goto(无条件形式:goto L
和条件形式:)重写程序,基本块实际上是您将获得的代码段if (t0) then goto L else L'
。
必须注意正确降低条件结构,以保留随之而来的短路语义。
此转换是构造控制流图之前的常见中间步骤。所谓的“领导者”算法(您识别第一个语句和跳转目标,将它们指定为领导者,然后通过从一个领导者到下一个领导者的指令来构造块)在“编译器:原理,技术”中描述和工具”。
那么,让我们看看你的例子:
let a = 10;
let b = 0;
if ((foo() && bar()) || baz()) {
b = 20;
if (hello > world) {
b *= 3
}
} else {
b = 2 * a;
}
let c = a * b
log(a);
Run Code Online (Sandbox Code Playgroud)
编译器首先希望将其规范化为如下所示的内容(请注意必须保留短路语义):
L0:
a = 100;
b = 0;
t0 = foo();
if (t0 != 0) goto L1; else goto L2;
L1:
t1 = bar();
if (t1 != 0) goto L3; else goto L2;
L2:
t2 = baz();
if (t2 != 0) goto L3; else goto L4;
L3:
b = 20;
if (hello > world) goto L5; else goto L6;
L5:
b = b * 3;
L6:
goto L7;
L4:
b = a * 2;
L7:
c = a * b;
log (a);
return
Run Code Online (Sandbox Code Playgroud)
从这里,我们可以应用前面提到的“前导”算法来识别基本块的前导指令。然后,我们可以将代码分割成块(从一个领导者到下一个领导者),然后找出控制流程;确保识别“fallthrough”边缘(当块的最后一条指令落在没有显式分支的领导者上时)。
您可以看到 是L6
一个无条件跳转到 的块L7
,这是归一化/线性化算法产生的产物。删除此类多余的块称为“跳转线程”,稍后完成。
如果您希望使用现有的编译器,在其 IR 之一中公开此控制流转换,您可以使用该标志调用 GCC -fdump-tree-all
,然后查看扩展名为.gimple
.