Structured vs Unstructured Control-Flow
Charon can represent a function body in two ways. Both describe the same computation; they differ in how they connect one operation to the next.
Structured control-flow is the default. In this mode, control-flow is represented by having
Statements
be able to contain Blocks
(statement lists),
e.g. a match
will contain one block
for each possible branch, and a function call
will contain a block for its unwind path. The only
“indirect” control-flow affordances are break/continue within a loop.
Unstructured control-flow instead represents the function body as a set of blocks that can jump
to one another. Each Block contains a series of control-flow-free
Statements,
followed by a single
Terminator,
which describes which other blocks computation may jump to.
For example,
a match
will contain one block id
for each possible branch.
Loops are not explicitly represented: they are simply cases where the graph of blocks is cyclic.
Pass --ullbc to get unstructured bodies in the output.
The unstructured bodies are actually what rustc gives us;
we reconstruct the structured bodies ourselves.
Code Duplication
In structured mode, by default we aim for the output code to be legible, and for that we may duplicate input code. For example:
#![allow(unused)]
fn main() {
if foo() && bar() {
do_something();
} else {
do_something_else();
}
}
is today reconstructed as:
#![allow(unused)]
fn main() {
if foo() {
if bar() {
do_something();
} else {
do_something_else();
}
} else {
do_something_else();
}
}
This specific case may be improved (see charon#803), but in the general case we will still have duplication.
If this is undesirable for your use case, use --no-code-duplication. Instead of duplicating code
this will introduce loops and break from them. Our example becomes more or less:
#![allow(unused)]
fn main() {
'a: loop {
if foo() {
if bar() {
do_something();
break 'a
}
}
do_something_else();
}
}