Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

feat(rome_control_flow): add the control flow crate #2781

Merged
merged 3 commits into from
Jul 1, 2022

Conversation

leops
Copy link
Contributor

@leops leops commented Jun 24, 2022

Summary

This PR adds a new rome_control_flow containing the data structures needed to represent a control flow graph, as well as associated helpers for building such a graph.

The control flow graph is an auxiliary data structure to the syntax tree representing the execution order of statements and expressions in the function as a graph of "basic blocks" comprised of a flattened list of instruction. There are currently 3 kind of instructions: Statement denotes the execution of a certain syntax node that may have some side effects but does not explicitly impact the control flow, Jump signals an edge from the current block to another block in the function (the edge may be conditional in which case it will generally be associated with an expression), and finally Return represents an exit point for the function (these includes return statements but also throw statements).

As an example, considering the following JavaScript function:

function exampleLoop(count) {
    for(let i = 0; i < count; i++) {
        if(count - i === 2) {
            continue;
        }

        if(i % 3 === 0) {
            callExpression(i + 1);
        } else {
            callExpression(i);
        }
    }

    callExpression();
}

The following control flow graph would be generated:

flowchart TB
    block_0["<b>block_0</b><br/>Statement(JS_VARIABLE_DECLARATION 38..47)<br/>Jump { block: 1 }"]
    block_1["<b>block_1</b><br/>Jump { condition: JS_BINARY_EXPRESSION 49..58, block: 4 }<br/>Jump { block: 3 }"]
    block_2["<b>block_2</b><br/>Statement(JS_POST_UPDATE_EXPRESSION 60..63)<br/>Jump { block: 1 }"]
    block_3["<b>block_3</b><br/>Statement(JS_EXPRESSION_STATEMENT 260..277)"]
    block_4["<b>block_4</b><br/>Jump { condition: JS_BINARY_EXPRESSION 78..93, block: 5 }<br/>Jump { block: 6 }"]
    block_5["<b>block_5</b><br/>Jump { block: 2 }<br/>Jump { block: 6 }"]
    block_6["<b>block_6</b><br/>Jump { condition: JS_BINARY_EXPRESSION 141..152, block: 7 }<br/>Jump { block: 8 }"]
    block_7["<b>block_7</b><br/>Statement(JS_EXPRESSION_STATEMENT 168..190)<br/>Jump { block: 9 }"]
    block_8["<b>block_8</b><br/>Statement(JS_EXPRESSION_STATEMENT 220..238)<br/>Jump { block: 9 }"]
    block_9["<b>block_9</b><br/>Jump { block: 2 }"]

    block_4 --> block_6
    block_1 --> block_3
    block_1 -- "JS_BINARY_EXPRESSION 49..58" --> block_4
    block_6 -- "JS_BINARY_EXPRESSION 141..152" --> block_7
    block_5 --> block_2
    block_2 --> block_1
    block_8 --> block_9
    block_0 --> block_1
    block_6 --> block_8
    block_7 --> block_9
    block_9 --> block_2
    block_4 -- "JS_BINARY_EXPRESSION 78..93" --> block_5
    block_5 --> block_6

(Note that the generated graph contains extraneous jumps and edges, these could be avoided in the generation pass but ultimately it does not influence the end result as everything following an unconditional jump is treated as unreachable)

Test Plan

The crate doesn't really have any tests at the moment since it's mostly comprised of definitions for plain data structures, while the code generating these is intended to live in the language-specific analyzer implementations. I could however try to add a few tests for the FunctionBuilder helper using the RawLanguage from rome_rowan

@cloudflare-pages
Copy link

cloudflare-pages bot commented Jun 24, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 581fe65
Status: ✅  Deploy successful!
Preview URL: https://aa994602.tools-8rn.pages.dev
Branch Preview URL: https://feature-control-flow-graph.tools-8rn.pages.dev

View logs

Comment on lines +66 to +69
pub struct Instruction<L: Language> {
pub kind: InstructionKind,
pub node: Option<SyntaxNode<L>>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should create some utility functions to quickly create instructions.

I was looking at how they are created inside the visitors, and there's a lot of code we could save:

impl Instruction {
	pub fn new_statement() -> Self;
	pub fn new_return() -> Self;
	pub fn new_jump(conditional: boo, block: BlockId) -> Self;
	pub fn with_node(self, node: SyntaxNode<L>) -> Self;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up implementing these methods on the FunctionBuilder itself, this avoids having to call append_instruction once the instruction has been built and instead directly constructs it in place

@leops leops force-pushed the refactor/analyze-visitor branch 2 times, most recently from 5f66f71 to fc4f882 Compare June 29, 2022 09:24
@leops leops force-pushed the feature/control-flow-graph branch from e69284b to 9e9196f Compare June 29, 2022 09:36
Base automatically changed from refactor/analyze-visitor to main June 29, 2022 13:08
leops and others added 2 commits June 29, 2022 15:16
@leops leops force-pushed the feature/control-flow-graph branch from 9e9196f to 9c81afb Compare June 29, 2022 13:16
@leops leops marked this pull request as ready for review June 29, 2022 13:17
@leops leops temporarily deployed to aws June 29, 2022 13:17 Inactive
@github-actions
Copy link

github-actions bot commented Jun 29, 2022

@leops leops temporarily deployed to aws June 29, 2022 14:01 Inactive
@leops leops merged commit d5a5535 into main Jul 1, 2022
@leops leops deleted the feature/control-flow-graph branch July 1, 2022 09:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants