I am working on a compiler project and wondering about the meaning and implementation of "basic blocks" of a control-flow graph (CFG). They say that the basic block is for the linear sequence of steps which don't have any branching. But first, a few questions there:
- What if there is nested branching, how does that work?
- What about the logic inside the conditional branch statement, is that part of the previous block or the current block (or a third block)?
For example, say I have this:
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);
Here, what are the "basic blocks"?
const blocks = [
{
inputs: [],
statements: [
'let a = 10',
'let b = 0',
],
outputs: ['a', 'b']
},
{
// ...?
}
]
I am not asking how exactly to implement a data-flow analyzer, just roughly speaking what the blocks should be in an example like this? Some pseudocode would be helpful. The conditional expression (foo() && bar()) || baz()
even hides some branching logic. In my code, I do something like this:
if
or
and
test foo
test bar
baz
Or even:
andResult =
and
test foo
test bar
orResult = baz | andResult
if orResult, ...
That shows it itself is steps in computation. And where should the if
statement go, does it start the next block? Basically wondering how they should generally be structured. And then to account for the nested conditional branch.