Sitelet https://github.com/starkware-libs/cairo/pull/10127
Skip to content

performance(lowering): Avoid cloning blocks and analysis info in inlining and dataflow. - #10127

Merged
orizi merged 1 commit into
mainfrom
orizi/06-18-performance_lowering_avoid_cloning_blocks_and_analysis_info_in_inlining_and_dataflow
Jun 23, 2026
Merged

orizi merged 1 commit into
mainfrom
orizi/06-18-performance_lowering_avoid_cloning_blocks_and_analysis_info_in_inlining_and_dataflow

Conversation

@orizi

@orizi orizi commented Jun 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Replaces the UnorderedHashMap<BlockId, TAnalyzer::Info> in BackAnalysis with a Vec<Option<TAnalyzer::Info>> indexed directly by BlockId.0, using is_none()/take()/index-assignment instead of contains_key/remove/insert. Adds a Blocks::into_builder method that moves blocks out of a Blocks<'db> into a BlocksBuilder via std::mem::take, and uses it in inner_apply_inlining to avoid deep-cloning every block before discarding the original. Also moves from .clone().unwrap() to .take().unwrap() in the forward analysis where lexicographic ordering guarantees each entry is consumed exactly once.


Type of change

Please check one:

  • Bug fix (fixes incorrect behavior)
  • New feature
  • Performance improvement
  • Documentation change with concrete technical impact
  • Style, wording, formatting, or typo-only change

Why is this change needed?

BackAnalysis was using a hash map for block info storage despite BlockId being a dense integer index, making lookups and membership checks more expensive than necessary. In inner_apply_inlining, every block was being cloned into the new BlocksBuilder even though the original lowered.blocks was immediately overwritten and the clones discarded, wasting allocations proportional to the size of the function being inlined.


What was the behavior or documentation before?

BackAnalysis stored per-block analysis results in an UnorderedHashMap<BlockId, TAnalyzer::Info>, using hash-based lookups for cache checks and insertions. inner_apply_inlining called .clone() on every block to seed the BlocksBuilder, then discarded the originals.


What is the behavior or documentation after?

BackAnalysis stores per-block analysis results in a Vec<Option<TAnalyzer::Info>> indexed by BlockId.0, with O(1) direct-index access and no hashing overhead. inner_apply_inlining moves blocks out of lowered.blocks via Blocks::into_builder, eliminating the per-block clone.


Related issue or discussion (if any)


Additional context

The Vec-based approach is valid because BlockId values are dense indices into lowered.blocks, so the Vec is pre-sized to exactly lowered.blocks.len() with no wasted capacity.

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

orizi commented Jun 18, 2026 •

Copy link
Copy Markdown
Collaborator Author

@orizi
orizi requested a review from eytan-starkware June 18, 2026 12:23
@orizi
orizi marked this pull request as ready for review June 18, 2026 12:24
@cursor

cursor Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches core CFG dataflow and inlining paths where incorrect merge/take ordering could change analysis results or block layout, though behavior is intended to be equivalent with less allocation.

Overview
Backward dataflow (BackAnalysis) now keeps per-block state in a Vec<Option<Info>> keyed by BlockId.0 instead of an UnorderedHashMap, using is_none / take / direct assignment for the DFS cache instead of hash lookups and remove.

Forward dataflow (ForwardDataflowAnalysis) avoids an extra clone on the work queue: ready blocks are (BlockId, Info) pairs, and merged predecessor state stays in incoming only until the last predecessor arrives—then it moves into ready via take.

Inlining (inner_apply_inlining) drops the temporary BlocksBuilder that cloned every block up front; it mutates Lowered.blocks in place (push, direct blocks[block_id] access) while walking block ids 0..len. Tests drop redundant .clone() on run() results.

Reviewed by Cursor Bugbot for commit 37d12f5. Bugbot is set up for automated code reviews on this repo. Configure here.

@orizi
orizi force-pushed the orizi/06-18-performance_lowering_avoid_cloning_blocks_and_analysis_info_in_inlining_and_dataflow branch from 125cfd6 to a8944a3 Compare June 18, 2026 12:24

@eytan-starkware eytan-starkware left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@eytan-starkware reviewed 5 files and all commit messages, and made 3 comments.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on orizi).


crates/cairo-lang-lowering/src/analysis/forward.rs line 57 at r1 (raw file):

            // Get entry info from incoming edges. Can move out, since we are working in
            // lexicographic order.

topological


crates/cairo-lang-lowering/src/inline/mod.rs line 232 at r1 (raw file):

    // Seed the builder by moving the existing blocks in (they are discarded at the end of this
    // function when `lowered.blocks` is overwritten), rather than deep-cloning each block.
    let mut blocks: BlocksBuilder<'db> = lowered.blocks.into_builder();

Did you consider something like
let Lowered { blocks, variables, .. } = lowered;
instead of blocks builder? Removes the mem::take and the unsafety that comes with it


crates/cairo-lang-lowering/src/objects/blocks.rs line 130 at r1 (raw file):

    /// place. Used by passes that rebuild the block list from scratch, to avoid cloning every
    /// block when the original is discarded anyway.
    pub fn into_builder(&mut self) -> BlocksBuilder<'db> {

Into is a bad name for taking &mut

@orizi
orizi force-pushed the orizi/06-18-performance_lowering_avoid_cloning_blocks_and_analysis_info_in_inlining_and_dataflow branch from a8944a3 to ea673ed Compare June 23, 2026 09:17

@orizi orizi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@orizi made 3 comments.
Reviewable status: 2 of 5 files reviewed, 3 unresolved discussions (waiting on eytan-starkware).


crates/cairo-lang-lowering/src/analysis/forward.rs line 57 at r1 (raw file):

Previously, eytan-starkware wrote…

topological

Done.


crates/cairo-lang-lowering/src/inline/mod.rs line 232 at r1 (raw file):

Previously, eytan-starkware wrote…

Did you consider something like
let Lowered { blocks, variables, .. } = lowered;
instead of blocks builder? Removes the mem::take and the unsafety that comes with it

Done.


crates/cairo-lang-lowering/src/objects/blocks.rs line 130 at r1 (raw file):

Previously, eytan-starkware wrote…

Into is a bad name for taking &mut

Done.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea673ed. Configure here.

Comment thread crates/cairo-lang-lowering/src/analysis/forward.rs Outdated
…ning and dataflow.

  Reduce heap allocations in the lowering pipeline by removing redundant copies:

  - inline: seed the BlocksBuilder by moving the existing blocks in via the new
    `Blocks::into_builder` (mem::take) instead of deep-cloning every block, since
    `lowered.blocks` is overwritten at the end of the pass anyway.
  - analysis/backward: back the per-block info cache with a `Vec<Option<Info>>`
    indexed by `BlockId` instead of an `UnorderedHashMap`, dropping the hashing
    and per-entry allocation.
  - analysis/forward: move the incoming info out with `take()` rather than
    `clone()`, since each block's slot is dead once the block is processed.

  Measured on corelib -> Sierra (dhat): -33.7 MB allocated (-3.7%) and -140,744
  allocations (-2.2%); peak heap unchanged.
@orizi
orizi force-pushed the orizi/06-18-performance_lowering_avoid_cloning_blocks_and_analysis_info_in_inlining_and_dataflow branch from ea673ed to 37d12f5 Compare June 23, 2026 11:13

@eytan-starkware eytan-starkware left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:lgtm:

@eytan-starkware reviewed 3 files and all commit messages, made 1 comment, and resolved 3 discussions.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on orizi).

@orizi
orizi added this pull request to the merge queue Jun 23, 2026
Merged via the queue into main with commit 3051885 Jun 23, 2026
55 checks passed
@orizi
orizi deleted the orizi/06-18-performance_lowering_avoid_cloning_blocks_and_analysis_info_in_inlining_and_dataflow branch June 23, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants