Skip to content

feat(ir): clone statements and CFGs, and inline calls in linear time - #851

Open
alexcgatea wants to merge 12 commits into
alex/insertion-apifrom
alex/cloning-inlining
Open

alexcgatea wants to merge 12 commits into
alex/insertion-apifrom
alex/cloning-inlining

Conversation

@alexcgatea

@alexcgatea alexcgatea commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds detached deep copies of statements and CFGs, and a one-level call inliner in kirin-function that runs in linear time.

New operations

Operation What it does
clone_statement, clone_cfg + IRMap Detached deep copies with fresh ids, including graph bodies. Results and block arguments are always copied, overwriting their IRMap entries, so a map can be reused, even after an earlier copy is gone
kirin_function::inline_calls Inline one level of same-stage CallSpecialized calls in a CFG

API changes

  • Clones are the first detached statements: insert places a cloned statement; splice_cfg_after or create places a cloned CFG. Detached CFGs never placed, including one a splice left empty, are tombstoned when the Rewriter drops, like detached statements.
  • create adopts the unowned bodies its definition names (e.g. a cloned CFG), with the same checks as replace_statement: each body must be live, owned by nothing (BodyOwnedElsewhere), named once (BodyListedTwice), and must not enclose the insertion point (BodyEnclosesOwner).
  • New CFGInfo::parent() accessor.
  • Regression tests for a detached producer placed after its consumers (the fix is in the insertion-API PR, but it needs clones to test).

Inlining

kirin-python's Walk(Inline(...)) scales as O(n²) (#554): each multi-block inline splits the call's block, and the split walks every statement after the call. In heavily inlined kernels most of the program ends up in one block, so every split costs close to the whole program. Calls are also visited first to last, so the same tail is moved again for every later call. inline_calls is built to stay linear:

  • Single-block callee: its statements are copied in front of the call, with no split.
  • Multi-block callee: the block is split after the call and the copied CFG is spliced in between. It is entered with a branch passing the call's arguments to the copied entry block's arguments, so a callee whose entry block is a loop header keeps its loop-carried values. Each copied ret becomes a branch to the second half.
  • Last-to-first order: each block is walked from its last statement to its first, so a split only moves statements after the call, which no earlier split has moved. Each statement moves at most once, and a run is linear in the body's size plus the size of the copied callees. Only the whole-body inline_calls is public, so no single run can inline in a quadratic order.
  • Moving the copied blocks is O(blocks): splice_cfg_after relinks them, with no list removal from the front.
  • One level: calls brought in by inlined code are left for another run. Running it on callees before their callers inlines everything with one run per function.
  • No recursion: it is the caller's responsibility to ensure there is no recursion, self or mutual, among the calls inlined.
  • A language implements FunctionOps<T> (as_call / as_return) to tell the inliner which of its statements are calls and returns; a closure builds its branch op.

Not included

  • Recursion detection in the inliner (it's one level, so it always terminates).

alexcgatea and others added 11 commits November 12, 2026 10:14
clone_statement and clone_cfg make detached deep copies in two passes,
mapping ids through an IRMap. create adopts the unowned bodies its
definition names; detached CFGs never adopted are tombstoned on drop.
Graph bodies are rejected until they can be cloned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A copied graph gets fresh ports, copied members in their roles and
yields; its topology is built from the copied operands once their uses
are recorded. Removes CopyOfGraphBody.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
inline_calls inlines one level of same-stage CallSpecialized calls in a
CFG's blocks and returns how many. Single-block callees are copied in
place; others are copied between the halves of a split block. Walking
each block from last to first keeps a run linear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
List splicing and cloning in the Rewriter module docs, count fifteen
MutationEvent kinds, and remove the remaining-IR-operations plan now
that it is done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Renames body_encloses_point to body_encloses_insertion_pt, and the
InsertionPoint parameter of check_can_adopt_bodies_at to insertion_pt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds regression tests placing a cloned DiGraph member and UnGraph edge
statement after the statements that read them, and documents that a
copy never placed leaves its readers dangling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mapping a block argument no longer drops it from the copy, which left
incoming edges passing values to a block with fewer arguments. The
liveness check skips values the copy defines, so a map can be reused
after its earlier copy is gone. The inliner enters a multi-block copy
with a branch passing the call's arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alexcgatea
alexcgatea force-pushed the alex/cloning-inlining branch from 5cd1b07 to ce1d44a Compare November 12, 2026 15:30
Link copied blocks with the shared list helper in the fill pass, so
reserving only allocates; clone each definition once when filling; make
lookup_or_original public and use it in the inliner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant