From f80060963d98efceee6a52524bbeba88939b3840 Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Thu, 9 Jul 2026 17:45:48 -0400 Subject: [PATCH 1/6] refactor: rename regions to cfgs and update related tests --- ...dialect_derive_struct_with_cfg_block.snap} | 32 +++++++++---------- ...ests__standalone__standalone_has_cfgs.snap | 26 +++++++++++++++ ...s__standalone__standalone_has_regions.snap | 26 --------------- .../src/builder/{region.rs => cfg.rs} | 0 .../kirin-ir/src/node/{region.rs => cfg.rs} | 0 ...n_prettyless__tests__print_cfg_empty.snap} | 0 ...ss__tests__print_cfg_multiple_blocks.snap} | 0 7 files changed, 42 insertions(+), 42 deletions(-) rename crates/kirin-derive-ir/src/tests/snapshots/{kirin_derive_ir__tests__dialect__dialect_derive_struct_with_region_block.snap => kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap} (94%) create mode 100644 crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_cfgs.snap delete mode 100644 crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_regions.snap rename crates/kirin-ir/src/builder/{region.rs => cfg.rs} (100%) rename crates/kirin-ir/src/node/{region.rs => cfg.rs} (100%) rename crates/kirin-prettyless/src/tests/snapshots/{kirin_prettyless__tests__print_region_empty.snap => kirin_prettyless__tests__print_cfg_empty.snap} (100%) rename crates/kirin-prettyless/src/tests/snapshots/{kirin_prettyless__tests__print_region_multiple_blocks.snap => kirin_prettyless__tests__print_cfg_multiple_blocks.snap} (100%) diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_region_block.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap similarity index 94% rename from crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_region_block.snap rename to crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap index 14e3b1f013..0d5f2cb573 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_region_block.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap @@ -225,55 +225,55 @@ impl<'a> Iterator for IfOpSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for IfOp { - type Iter = IfOpRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for IfOp { + type Iter = IfOpCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { condition, then_block, else_block, body, } = self; - IfOpRegionsIter { + IfOpCfgsIter { inner: std::iter::once(body), } } } #[automatically_derived] #[doc(hidden)] -pub struct IfOpRegionsIter<'a> { - inner: std::iter::Once<&'a ::kirin::ir::Region>, +pub struct IfOpCfgsIter<'a> { + inner: std::iter::Once<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for IfOpRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for IfOpCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for IfOp { - type IterMut = IfOpRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for IfOp { + type IterMut = IfOpCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { condition, then_block, else_block, body, } = self; - IfOpRegionsMutIter { + IfOpCfgsMutIter { inner: std::iter::once(body), } } } #[automatically_derived] #[doc(hidden)] -pub struct IfOpRegionsMutIter<'a> { - inner: std::iter::Once<&'a mut ::kirin::ir::Region>, +pub struct IfOpCfgsMutIter<'a> { + inner: std::iter::Once<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for IfOpRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for IfOpCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_cfgs.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_cfgs.snap new file mode 100644 index 0000000000..2997364253 --- /dev/null +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_cfgs.snap @@ -0,0 +1,26 @@ +--- +source: crates/kirin-derive-ir/src/tests/standalone.rs +expression: rustfmt(tokens.to_string()) +--- +#[automatically_derived] +impl<'a> ::kirin::ir::HasCfgs<'a> for Lambda { + type Iter = LambdaCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { + let Self { body } = self; + LambdaCfgsIter { + inner: std::iter::once(body), + } + } +} +#[automatically_derived] +#[doc(hidden)] +pub struct LambdaCfgsIter<'a> { + inner: std::iter::Once<&'a ::kirin::ir::Cfg>, +} +#[automatically_derived] +impl<'a> Iterator for LambdaCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; + fn next(&mut self) -> Option { + self.inner.next() + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_regions.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_regions.snap deleted file mode 100644 index abbb052240..0000000000 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__standalone__standalone_has_regions.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: crates/kirin-derive-ir/src/generate.rs -expression: rustfmt(tokens.to_string()) ---- -#[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for Lambda { - type Iter = LambdaRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { - let Self { body } = self; - LambdaRegionsIter { - inner: std::iter::once(body), - } - } -} -#[automatically_derived] -#[doc(hidden)] -pub struct LambdaRegionsIter<'a> { - inner: std::iter::Once<&'a ::kirin::ir::Region>, -} -#[automatically_derived] -impl<'a> Iterator for LambdaRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; - fn next(&mut self) -> Option { - self.inner.next() - } -} diff --git a/crates/kirin-ir/src/builder/region.rs b/crates/kirin-ir/src/builder/cfg.rs similarity index 100% rename from crates/kirin-ir/src/builder/region.rs rename to crates/kirin-ir/src/builder/cfg.rs diff --git a/crates/kirin-ir/src/node/region.rs b/crates/kirin-ir/src/node/cfg.rs similarity index 100% rename from crates/kirin-ir/src/node/region.rs rename to crates/kirin-ir/src/node/cfg.rs diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_region_empty.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_empty.snap similarity index 100% rename from crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_region_empty.snap rename to crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_empty.snap diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_region_multiple_blocks.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_multiple_blocks.snap similarity index 100% rename from crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_region_multiple_blocks.snap rename to crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_multiple_blocks.snap From aed4c2397c39ae484d1cabf22b6e7d9073f0741c Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Thu, 9 Jul 2026 17:46:08 -0400 Subject: [PATCH 2/6] Refactor terminology from Region to Cfg across the codebase --- AGENTS.md | 8 +- crates/kirin-bitwise/src/tests.rs | 8 +- crates/kirin-cf/src/tests.rs | 10 +- crates/kirin-chumsky/src/ast/blocks.rs | 28 ++-- .../src/function_text/dispatch.rs | 2 +- .../kirin-chumsky/src/function_text/syntax.rs | 2 +- .../kirin-chumsky/src/function_text/tests.rs | 12 +- crates/kirin-chumsky/src/parsers/blocks.rs | 22 +-- crates/kirin-chumsky/src/traits/emit_ir.rs | 2 +- crates/kirin-chumsky/src/traits/has_parser.rs | 4 +- crates/kirin-chumsky/src/traits/parse_emit.rs | 6 +- crates/kirin-cmp/src/tests.rs | 10 +- crates/kirin-constant/src/tests.rs | 10 +- .../src/codegen/emit_ir/field_emit.rs | 2 +- .../src/codegen/emit_ir/generate.rs | 4 +- .../src/codegen/parser/chain.rs | 6 +- crates/kirin-derive-chumsky/src/field_kind.rs | 26 ++- crates/kirin-derive-chumsky/src/format.rs | 2 +- crates/kirin-derive-chumsky/src/input.rs | 8 +- crates/kirin-derive-chumsky/src/validation.rs | 22 +-- crates/kirin-derive-ir/src/generate.rs | 24 +-- crates/kirin-derive-ir/src/lib.rs | 4 +- crates/kirin-derive-ir/src/tests/dialect.rs | 4 +- ...ect__dialect_derive_custom_crate_path.snap | 36 ++-- ..._mixed_does_not_generate_lift_project.snap | 44 ++--- ...ct_derive_enum_mixed_wraps_and_fields.snap | 44 ++--- ...m_pure_wrapper_generates_lift_project.snap | 62 +++---- ...alect__dialect_derive_enum_with_wraps.snap | 48 +++--- ...pper_with_side_fields_no_lift_project.snap | 48 +++--- ...num_wraps_with_extra_fields_from_impl.snap | 49 +++--- ...ect_derive_enum_wraps_with_terminator.snap | 48 +++--- ..._dialect_derive_struct_all_properties.snap | 36 ++-- ...__dialect__dialect_derive_struct_edge.snap | 36 ++-- ...lect__dialect_derive_struct_no_fields.snap | 36 ++-- ...t__dialect_derive_struct_option_block.snap | 36 ++-- ...dialect__dialect_derive_struct_symbol.snap | 36 ++-- ...ect__dialect_derive_struct_terminator.snap | 36 ++-- ...__dialect_derive_struct_vec_ssa_value.snap | 36 ++-- ...t__dialect_derive_struct_with_digraph.snap | 36 ++-- ...dialect_derive_struct_with_ssa_fields.snap | 36 ++-- ...dialect_derive_struct_with_successors.snap | 36 ++-- ...t__dialect_derive_struct_with_ungraph.snap | 36 ++-- ...wrapper_struct_generates_lift_project.snap | 36 ++-- ..._struct_generates_lift_project_bridge.snap | 36 ++-- ...t_derive_wrapper_struct_has_signature.snap | 36 ++-- .../kirin-derive-ir/src/tests/standalone.rs | 8 +- .../src/ir/fields/data.rs | 12 +- .../src/ir/fields/info.rs | 6 +- .../kirin-derive-toolkit/src/ir/fields/mod.rs | 2 +- .../src/ir/statement/accessors.rs | 6 +- .../src/ir/statement/definition.rs | 4 +- .../src/parse_dispatch.rs | 2 +- .../src/template/builder_template/helpers.rs | 6 +- .../method_pattern/field_collection.rs | 9 +- .../src/template/trait_impl.rs | 8 +- crates/kirin-function/src/body.rs | 6 +- crates/kirin-function/src/call/tests.rs | 6 +- crates/kirin-function/src/interpreter.rs | 6 +- crates/kirin-function/src/lambda.rs | 10 +- crates/kirin-function/src/ret.rs | 6 +- crates/kirin-interpreter/src/core/effect.rs | 18 +- crates/kirin-interpreter/src/core/error.rs | 4 +- crates/kirin-interpreter/src/core/frame.rs | 8 +- crates/kirin-interpreter/src/core/query.rs | 50 +++--- .../src/engines/concrete/frames.rs | 17 +- .../src/engines/concrete/interp.rs | 8 +- .../src/engines/dense_backward/interp.rs | 58 +++---- .../src/engines/sparse_backward/interp.rs | 92 +++++----- .../src/engines/sparse_backward/mod.rs | 2 +- .../src/engines/sparse_forward/interp.rs | 14 +- crates/kirin-interpreter/src/facts/anchor.rs | 6 +- crates/kirin-interpreter/src/facts/mod.rs | 4 +- crates/kirin-interpreter/src/facts/store.rs | 8 +- .../kirin-interpreter/src/facts/topology.rs | 40 +++-- crates/kirin-interpreter/src/lib.rs | 8 +- crates/kirin-ir/src/builder/block.rs | 6 +- crates/kirin-ir/src/builder/cfg.rs | 16 +- crates/kirin-ir/src/builder/context.rs | 6 +- crates/kirin-ir/src/builder/mod.rs | 4 +- crates/kirin-ir/src/builder/stage_info.rs | 6 +- crates/kirin-ir/src/language.rs | 28 ++-- crates/kirin-ir/src/lib.rs | 18 +- crates/kirin-ir/src/node/block.rs | 8 +- crates/kirin-ir/src/node/cfg.rs | 28 ++-- crates/kirin-ir/src/node/mod.rs | 4 +- crates/kirin-ir/src/node/stmt.rs | 6 +- crates/kirin-ir/src/query/info.rs | 6 +- crates/kirin-ir/src/stage/arenas.rs | 14 +- crates/kirin-ir/src/stage/info.rs | 6 +- crates/kirin-ir/src/stage/tests.rs | 20 +-- crates/kirin-ir/tests/builder_block.rs | 72 ++++---- crates/kirin-ir/tests/common.rs | 12 +- crates/kirin-liveness/src/lib.rs | 20 +-- crates/kirin-liveness/src/result.rs | 12 +- crates/kirin-liveness/tests/cfg.rs | 122 +++++++------- .../kirin-prettyless/src/document/builder.rs | 2 +- .../src/document/ir_render.rs | 14 +- .../kirin-prettyless/src/tests/edge_cases.rs | 20 +-- crates/kirin-prettyless/src/tests/impls.rs | 10 +- crates/kirin-prettyless/src/tests/mod.rs | 4 +- crates/kirin-prettyless/src/tests/pipeline.rs | 6 +- .../src/tests/sprint_with_globals.rs | 2 +- crates/kirin-prettyless/src/traits.rs | 4 +- crates/kirin-scf/src/interpreter.rs | 2 +- crates/kirin-scf/src/lib.rs | 6 +- crates/kirin-scf/src/tests.rs | 6 +- .../src/arith_function_language.rs | 4 +- .../src/bitwise_function_language.rs | 4 +- .../src/callable_language.rs | 4 +- .../src/namespaced_language.rs | 4 +- .../src/simple_language.rs | 4 +- crates/kirin-tuple/src/tests.rs | 10 +- .../formalism/state-environment-model.md | 2 +- docs/design/formalism/syntax.md | 12 +- docs/design/graph-body-rust-interface.md | 4 +- docs/design/graph-ir-node.md | 2 +- docs/design/hybrid_ir_visualization.dot | 4 +- docs/design/hybrid_ir_visualization.svg | 4 +- docs/design/interpreter/index.md | 8 +- example/simple.rs | 8 +- example/toy-lang/src/interpreter/mod.rs | 28 ++-- example/toy-lang/src/interpreter/tests.rs | 158 +++++++++--------- src/dialects/scf.rs | 2 +- tests/roundtrip/composite.rs | 4 +- tests/roundtrip/constant.rs | 2 +- tests/roundtrip/digraph.rs | 20 +-- tests/roundtrip/function.rs | 4 +- tests/roundtrip/scf.rs | 2 +- tests/roundtrip/tuple.rs | 2 +- tests/simple.rs | 2 +- 130 files changed, 1141 insertions(+), 1190 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a7c5ebc42d..e0523c3a4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,7 @@ Rust edition 2024. No `rust-toolchain.toml`; uses the default toolchain. Use [Conventional Commits](https://www.conventionalcommits.org/): `(): ` -Examples: `feat(chumsky): add region parser`, `fix(derive): handle empty enum variants` +Examples: `feat(chumsky): add cfg parser`, `fix(derive): handle empty enum variants` Avoid large paragraphs in commit messages, keep them concise and focused on the changes made. @@ -133,7 +133,7 @@ For user-defined dialects not in this table, ask the user for domain context dur ## IR Design Conventions -- **Block vs Region**: A `Block` is a single linear sequence of statements with an optional terminator. A `Region` is a container for multiple blocks (`LinkedList`). When modeling MLIR-style operations, check whether the MLIR op uses `SingleBlock` regions — if so, use `Block` in Kirin, not `Region`. For example, MLIR's `scf.if` and `scf.for` have `SingleBlock` + `SingleBlockImplicitTerminator` traits, so `kirin-scf` correctly uses `Block` fields for their bodies. +- **Block vs CFG**: A `Block` is a single linear sequence of statements with an optional terminator. A `Cfg` is a control-flow-graph body: a container for multiple blocks (`LinkedList`). (The `Cfg` type was originally named `Region` after MLIR, but in Kirin it is specifically the block-list CFG body — single-block bodies use `Block`, and graph-like bodies use `DiGraph`/`UnGraph`.) When modeling MLIR-style operations, check whether the MLIR op uses `SingleBlock` regions — if so, use `Block` in Kirin, not `Cfg`. For example, MLIR's `scf.if` and `scf.for` have `SingleBlock` + `SingleBlockImplicitTerminator` traits, so `kirin-scf` correctly uses `Block` fields for their bodies. - **`BlockInfo::terminator` is a cached pointer**: The `terminator` field in `BlockInfo` is a cached pointer to the last statement in the block — it is NOT a separate statement. `StatementIter` only iterates the linked list of non-terminator statements. When querying the last statement, use `Block::last_statement(stage)` which returns `terminator.or_else(|| statements.tail())`. Do not assume the terminator is distinct from the statements list. @@ -167,7 +167,7 @@ For user-defined dialects not in this table, ask the user for domain context dur - **Function dialect naming**: `kirin_function::Function` is the standard function statement. New code should use `Function` with `FunctionEntry` and `SparseForwardEffect::Call`/`SparseForwardEffect::Return`. -- **Backward analyses (implemented)**: liveness ships as **two** analyses in `kirin-liveness`, both real framework clients. *Strong liveness* (`analyze_demand`, `StrongDemand`) is per-SSA-value demand: summary owners ARE scope-qualified SSA values (`Scoped<(CompileStage, Region), SSAValue>`), the driver's default self-dependent index is the demand worklist, ordinary dialects are a one-liner (`interp.demand_uses_if_observable(self)` on `DemandInterp` — purity-aware via `IsPure`: impure statements and terminator/return operands are roots), and scf needs **no frames** (loop-carried demand converges on the value worklist; the scf rules use `block_params`/`terminator_args` queries). *Classic per-point liveness* (`analyze_dense`, `ClassicLiveness`) is the textbook kill-defs/gen-all-uses transfer over block owners with backward block walks (`DenseBlockFrame`, `absorb_edges` maps successor live-ins across edges with pass-through for dominated direct cross-block uses); scf owns dense frames (`DenseScfIfFrame` arm-join, `DenseScfForFrame` loop fixpoint) composed via `DenseFrameBuild`/`BuildDenseScf*` (see toy-lang's `ToyDenseBackwardFrame`). Strong per-point sets are the composition `dense ∩ demanded`, not a third analysis. Region topology (blocks incl. nested bodies, feeders) comes from `StageQuery` actions — enumeration only; use/def/edge-arg *semantics* stay in dialect rules. One dialect carries one rule per semantic key without coherence conflicts, and two keys can share one shape — the mock compile-time proofs were removed; the shipped dialects (each carrying `ForwardEval` + `StrongDemand` + `ClassicLiveness` rules) are the living evidence. +- **Backward analyses (implemented)**: liveness ships as **two** analyses in `kirin-liveness`, both real framework clients. *Strong liveness* (`analyze_demand`, `StrongDemand`) is per-SSA-value demand: summary owners ARE scope-qualified SSA values (`Scoped<(CompileStage, Cfg), SSAValue>`), the driver's default self-dependent index is the demand worklist, ordinary dialects are a one-liner (`interp.demand_uses_if_observable(self)` on `DemandInterp` — purity-aware via `IsPure`: impure statements and terminator/return operands are roots), and scf needs **no frames** (loop-carried demand converges on the value worklist; the scf rules use `block_params`/`terminator_args` queries). *Classic per-point liveness* (`analyze_dense`, `ClassicLiveness`) is the textbook kill-defs/gen-all-uses transfer over block owners with backward block walks (`DenseBlockFrame`, `absorb_edges` maps successor live-ins across edges with pass-through for dominated direct cross-block uses); scf owns dense frames (`DenseScfIfFrame` arm-join, `DenseScfForFrame` loop fixpoint) composed via `DenseFrameBuild`/`BuildDenseScf*` (see toy-lang's `ToyDenseBackwardFrame`). Strong per-point sets are the composition `dense ∩ demanded`, not a third analysis. CFG topology (blocks incl. nested bodies, feeders) comes from `StageQuery` actions — enumeration only; use/def/edge-arg *semantics* stay in dialect rules. One dialect carries one rule per semantic key without coherence conflicts, and two keys can share one shape — the mock compile-time proofs were removed; the shipped dialects (each carrying `ForwardEval` + `StrongDemand` + `ClassicLiveness` rules) are the living evidence. ## Chumsky Parser Conventions @@ -177,7 +177,7 @@ For user-defined dialects not in this table, ask the user for domain context dur - **`ParseDispatch` for pipeline parsing**: Multi-dialect pipeline parsing uses `ParseDispatch` (a monomorphic dispatch trait) instead of HRTB-based `SupportsStageDispatchMut`. Add `#[derive(ParseDispatch)]` alongside `#[derive(StageMeta)]` on stage enums. Single-dialect pipelines (`Pipeline>`) get a blanket `ParseDispatch` impl. Zero HRTB in the dispatch chain. -- **`#[wraps]` works with Region/Block-containing types**: Dialect types that contain `Region` or `Block` fields (e.g., `Lambda`, `Function`, SCF operations) can be composed via `#[wraps]` + `HasParser`. See `example/toy-lang/src/language.rs` where `Lexical` (contains `Function` with Region and `Lambda` with Region) and `StructuredControlFlow` (contains `If`/`For` with Block fields) are both used with `#[wraps]`. +- **`#[wraps]` works with Cfg/Block-containing types**: Dialect types that contain `Cfg` or `Block` fields (e.g., `Lambda`, `Function`, SCF operations) can be composed via `#[wraps]` + `HasParser`. See `example/toy-lang/src/language.rs` where `Lexical` (contains `Function` with a Cfg body and `Lambda` with a Cfg body) and `StructuredControlFlow` (contains `If`/`For` with Block fields) are both used with `#[wraps]`. - **`Ctx` default parameter for unified traits**: When the same trait method needs extra context for some implementors (e.g., `CompileStage` for `Pipeline`) but not others (e.g., `StageInfo`), use a default type parameter `Ctx = ()` on the trait. Pair with a blanket `Ext` trait that erases the `()` arg for ergonomic call sites. See `ParseStatementText` / `ParseStatementTextExt`. diff --git a/crates/kirin-bitwise/src/tests.rs b/crates/kirin-bitwise/src/tests.rs index 65356c1a54..7a43ba2db5 100644 --- a/crates/kirin-bitwise/src/tests.rs +++ b/crates/kirin-bitwise/src/tests.rs @@ -1,5 +1,5 @@ use kirin::ir::{ - HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, + HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsSpeculatable, IsTerminator, TestSSAValue, }; use kirin_test_types::UnitType; @@ -132,7 +132,7 @@ fn all_have_one_result() { } } -// --- HasSuccessors / HasBlocks / HasRegions: all empty --- +// --- HasSuccessors / HasBlocks / HasCfgs: all empty --- #[test] fn no_successors() { @@ -149,9 +149,9 @@ fn no_blocks() { } #[test] -fn no_regions() { +fn no_cfgs() { for op in all_variants() { - assert_eq!(op.regions().count(), 0); + assert_eq!(op.cfgs().count(), 0); } } diff --git a/crates/kirin-cf/src/tests.rs b/crates/kirin-cf/src/tests.rs index 419ea5e36b..9fe6dc3bf8 100644 --- a/crates/kirin-cf/src/tests.rs +++ b/crates/kirin-cf/src/tests.rs @@ -1,5 +1,5 @@ use kirin::ir::{ - Block, HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, + Block, HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsSpeculatable, IsTerminator, Successor, TestSSAValue, }; use kirin_test_types::UnitType; @@ -133,7 +133,7 @@ fn cond_branch_has_two_successors() { assert_eq!(succs.len(), 2); } -// --- HasBlocks / HasRegions: empty --- +// --- HasBlocks / HasCfgs: empty --- #[test] fn no_blocks() { @@ -142,9 +142,9 @@ fn no_blocks() { } #[test] -fn no_regions() { - assert_eq!(make_branch().regions().count(), 0); - assert_eq!(make_cond_branch().regions().count(), 0); +fn no_cfgs() { + assert_eq!(make_branch().cfgs().count(), 0); + assert_eq!(make_cond_branch().cfgs().count(), 0); } // --- Clone + PartialEq --- diff --git a/crates/kirin-chumsky/src/ast/blocks.rs b/crates/kirin-chumsky/src/ast/blocks.rs index aad1d88af5..dca789a2bb 100644 --- a/crates/kirin-chumsky/src/ast/blocks.rs +++ b/crates/kirin-chumsky/src/ast/blocks.rs @@ -65,7 +65,7 @@ pub struct Block<'src, TypeOutput, StmtOutput> { pub statements: Vec>, } -/// A region containing multiple blocks. +/// A CFG containing multiple blocks. /// /// Represents syntax like: /// ```ignore @@ -78,8 +78,8 @@ pub struct Block<'src, TypeOutput, StmtOutput> { /// The `TypeOutput` parameter is the parsed type representation. /// The `StmtOutput` parameter is the parsed statement representation. #[derive(Debug, Clone, PartialEq)] -pub struct Region<'src, TypeOutput, StmtOutput> { - /// The blocks in the region. +pub struct Cfg<'src, TypeOutput, StmtOutput> { + /// The blocks in the CFG. pub blocks: Vec>>, } @@ -102,7 +102,7 @@ where } /// Emit a single block AST node into the IR, reusing an existing block ID if -/// the name was already registered (e.g. by a two-pass Region emit). +/// the name was already registered (e.g. by a two-pass Cfg emit). /// /// Uses a two-phase approach: first creates the block with its arguments (to /// get real `BlockArgument` SSAs), then emits statements and attaches them. @@ -182,7 +182,7 @@ where ctx.stage .attach_statements_to_block(block, &stmts, terminator); - // Register the block only if not already registered (two-pass Region + // Register the block only if not already registered (two-pass Cfg // creates stubs first, so the name may already be present). if let Some(label) = block_ast.label && ctx.lookup_block(label.value).is_none() @@ -230,7 +230,7 @@ where } } -impl<'src, TypeOutput, StmtOutput> Region<'src, TypeOutput, StmtOutput> { +impl<'src, TypeOutput, StmtOutput> Cfg<'src, TypeOutput, StmtOutput> { pub fn emit_with( &self, ctx: &mut EmitContext<'_, IR>, @@ -238,7 +238,7 @@ impl<'src, TypeOutput, StmtOutput> Region<'src, TypeOutput, StmtOutput> { &StmtOutput, &mut EmitContext<'ctx, IR>, ) -> Result, - ) -> Result + ) -> Result where IR: Dialect, TypeOutput: EmitIR, @@ -280,8 +280,8 @@ impl<'src, TypeOutput, StmtOutput> Region<'src, TypeOutput, StmtOutput> { // Drop scope guard — inner names are discarded. drop(guard); - // Build the region using the stub IDs (now containing real data). - let mut builder = ctx.stage.region(); + // Build the cfg using the stub IDs (now containing real data). + let mut builder = ctx.stage.cfg(); for block in stub_blocks { builder = builder.add_block(block); } @@ -289,21 +289,21 @@ impl<'src, TypeOutput, StmtOutput> Region<'src, TypeOutput, StmtOutput> { } } -/// Implementation of EmitIR for Region AST nodes. +/// Implementation of EmitIR for Cfg AST nodes. /// -/// This builds an IR region containing all the parsed blocks. +/// This builds an IR CFG containing all the parsed blocks. /// Uses two-pass emit to support forward block references (e.g. `br ^exit` /// before `^exit` is defined). /// /// The `TypeOutput: EmitIR` bound allows proper type -/// conversion for block arguments within the region via the EmitIR trait. -impl<'src, TypeOutput, StmtOutput, IR> EmitIR for Region<'src, TypeOutput, StmtOutput> +/// conversion for block arguments within the CFG via the EmitIR trait. +impl<'src, TypeOutput, StmtOutput, IR> EmitIR for Cfg<'src, TypeOutput, StmtOutput> where IR: Dialect, TypeOutput: EmitIR, StmtOutput: EmitIR, { - type Output = kirin_ir::Region; + type Output = kirin_ir::Cfg; fn emit(&self, ctx: &mut EmitContext<'_, IR>) -> Result { self.emit_with(ctx, &|stmt, ctx| stmt.emit(ctx)) diff --git a/crates/kirin-chumsky/src/function_text/dispatch.rs b/crates/kirin-chumsky/src/function_text/dispatch.rs index 15426dd41c..34dd094e1a 100644 --- a/crates/kirin-chumsky/src/function_text/dispatch.rs +++ b/crates/kirin-chumsky/src/function_text/dispatch.rs @@ -3,7 +3,7 @@ //! [`ParseDispatch`] replaces the HRTB-based `SupportsStageDispatchMut` path //! for pipeline text parsing. Each stage enum variant dispatches to a concrete //! dialect parser with concrete lifetimes, which avoids the E0275 trait-solver -//! overflow that occurs when `Block`/`Region`-containing types use `#[wraps]`. +//! overflow that occurs when `Block`/`Cfg`-containing types use `#[wraps]`. //! //! For single-dialect pipelines (`Pipeline>`), a blanket impl is //! provided so no derive macro is needed. diff --git a/crates/kirin-chumsky/src/function_text/syntax.rs b/crates/kirin-chumsky/src/function_text/syntax.rs index d9e5a872b2..94110b3bfe 100644 --- a/crates/kirin-chumsky/src/function_text/syntax.rs +++ b/crates/kirin-chumsky/src/function_text/syntax.rs @@ -70,7 +70,7 @@ where } /// Body span scanner. Matches an optional keyword prefix (e.g. `digraph`, -/// `ungraph`) followed by a brace-balanced `{ ... }` region. Returns the +/// `ungraph`) followed by a brace-balanced `{ ... }` CFG. Returns the /// span covering everything from the first non-brace token (or the opening /// brace) through the matching closing brace. Does not parse body contents. fn body_span<'src, I>() -> impl Parser<'src, I, SimpleSpan, ParserError<'src>> diff --git a/crates/kirin-chumsky/src/function_text/tests.rs b/crates/kirin-chumsky/src/function_text/tests.rs index 49e86a9ce3..de72f1bada 100644 --- a/crates/kirin-chumsky/src/function_text/tests.rs +++ b/crates/kirin-chumsky/src/function_text/tests.rs @@ -2,8 +2,8 @@ use std::collections::BTreeSet; use chumsky::prelude::*; use kirin_ir::{ - Function, FunctionInfo, GlobalSymbol, HasBottom, HasTop, InternTable, Lattice, Pipeline, - Placeholder, Region, Signature, StageInfo, TypeLattice, + Cfg, Function, FunctionInfo, GlobalSymbol, HasBottom, HasTop, InternTable, Lattice, Pipeline, + Placeholder, Signature, StageInfo, TypeLattice, }; use kirin_prettyless::PrintExt; @@ -89,7 +89,7 @@ trivial_type_lattice!(I32Type, "i32", just(Token::Identifier("i32"))); #[kirin(builders, type = UnitType, crate = kirin_ir)] #[chumsky(crate = crate, format = "fn {:name}{sig} {body}")] struct FunctionBody { - body: Region, + body: Cfg, sig: Signature, } @@ -97,7 +97,7 @@ struct FunctionBody { #[kirin(builders, type = I32Type, crate = kirin_ir)] #[chumsky(crate = crate, format = "fn {:name}{sig} {body}")] struct LowerBody { - body: Region, + body: Cfg, sig: Signature, } @@ -286,8 +286,8 @@ fn test_pipeline_roundtrip_print_parse_print() { pipeline.stage_mut(stage_a).unwrap().with_builder(|b| { let block = b.block().new(); - let region = b.region().add_block(block).new(); - let body = FunctionBody::new(b, region, Signature::new(vec![], UnitType, ())); + let cfg = b.cfg().add_block(block).new(); + let body = FunctionBody::new(b, cfg, Signature::new(vec![], UnitType, ())); b.specialize() .staged_func(staged_function) .signature(unit_sig()) diff --git a/crates/kirin-chumsky/src/parsers/blocks.rs b/crates/kirin-chumsky/src/parsers/blocks.rs index 60e42c138e..6025f10763 100644 --- a/crates/kirin-chumsky/src/parsers/blocks.rs +++ b/crates/kirin-chumsky/src/parsers/blocks.rs @@ -162,7 +162,7 @@ where }) } -/// Parses a region containing multiple blocks. +/// Parses a CFG containing multiple blocks. /// /// Matches: /// ```text @@ -176,10 +176,10 @@ where /// /// The type parameter `T` specifies the type annotation type (typically the TypeLattice). /// The type parameter `S` is the statement AST type produced by the language parser. -/// The parser produces `Region<'t, ::Output, S>`. -pub fn region<'t, I, T, S>( +/// The parser produces `Cfg<'t, ::Output, S>`. +pub fn cfg<'t, I, T, S>( language: RecursiveParser<'t, I, S>, -) -> impl Parser<'t, I, Region<'t, >::Output, S>, ParserError<'t>> +) -> impl Parser<'t, I, Cfg<'t, >::Output, S>, ParserError<'t>> where I: TokenInput<'t>, T: HasParser<'t>, @@ -190,8 +190,8 @@ where .repeated() .collect::>() .delimited_by(just(Token::LBrace), just(Token::RBrace)) - .map(|blocks| Region { blocks }) - .labelled("region") + .map(|blocks| Cfg { blocks }) + .labelled("cfg") } /// Parses block body statements (without header, without braces). @@ -217,12 +217,12 @@ where .labelled("block body statements") } -/// Parses region body (blocks without outer braces). +/// Parses CFG body (blocks without outer braces). /// /// Matches a sequence of blocks, each optionally terminated by a semicolon. -/// This is the inner content of a region, used for `:body` projections on -/// Region fields where the caller provides surrounding syntax via the format string. -pub fn region_body<'t, I, T, S>( +/// This is the inner content of a CFG, used for `:body` projections on +/// Cfg fields where the caller provides surrounding syntax via the format string. +pub fn cfg_body<'t, I, T, S>( language: RecursiveParser<'t, I, S>, ) -> impl Parser<'t, I, Vec>::Output, S>>>, ParserError<'t>> where @@ -234,5 +234,5 @@ where .then_ignore(just(Token::Semicolon).or_not()) .repeated() .collect::>() - .labelled("region body") + .labelled("cfg body") } diff --git a/crates/kirin-chumsky/src/traits/emit_ir.rs b/crates/kirin-chumsky/src/traits/emit_ir.rs index f73f6f40f3..d40898fffb 100644 --- a/crates/kirin-chumsky/src/traits/emit_ir.rs +++ b/crates/kirin-chumsky/src/traits/emit_ir.rs @@ -42,7 +42,7 @@ type ForwardRefCreator = fn(&mut BuilderStageInfo, &str) -> kirin_ir::SSAV /// Context for emitting IR from parsed AST, tracking name mappings. /// /// The `stage` field is a `&mut BuilderStageInfo` since emit is a build-time -/// operation that needs access to builder methods (block, region, ssa, etc.). +/// operation that needs access to builder methods (block, cfg, ssa, etc.). pub struct EmitContext<'a, L: Dialect> { pub stage: &'a mut BuilderStageInfo, /// Scope stack for SSA name bindings. Inner scopes shadow outer ones. diff --git a/crates/kirin-chumsky/src/traits/has_parser.rs b/crates/kirin-chumsky/src/traits/has_parser.rs index 3760c2c3be..bb7687eecd 100644 --- a/crates/kirin-chumsky/src/traits/has_parser.rs +++ b/crates/kirin-chumsky/src/traits/has_parser.rs @@ -33,7 +33,7 @@ pub trait HasParser<'t> { /// /// This trait provides recursive parsing capabilities for dialects. /// The AST type is parameterized by `TypeOutput` (for type annotations) and -/// `LanguageOutput` (for nested statements in blocks/regions). +/// `LanguageOutput` (for nested statements in blocks/cfgs). /// /// Using explicit type parameters instead of GAT projections avoids infinite /// compilation times when the Language type is self-referential. @@ -43,7 +43,7 @@ pub trait HasDialectParser<'t>: Sized { /// The AST type produced by parsing this dialect. /// /// - `TypeOutput`: The parsed representation of type annotations - /// - `LanguageOutput`: The AST type for statements in blocks/regions + /// - `LanguageOutput`: The AST type for statements in blocks/cfgs type Output: Clone + PartialEq where TypeOutput: Clone + PartialEq + 't, diff --git a/crates/kirin-chumsky/src/traits/parse_emit.rs b/crates/kirin-chumsky/src/traits/parse_emit.rs index d9107a0bf1..fb0596280b 100644 --- a/crates/kirin-chumsky/src/traits/parse_emit.rs +++ b/crates/kirin-chumsky/src/traits/parse_emit.rs @@ -64,15 +64,15 @@ impl From for ChumskyError { /// /// 1. **Derive**: `#[derive(HasParser)]` generates this automatically. /// 2. **Marker**: Implement `SimpleParseEmit` for non-recursive dialects -/// (no `Block`/`Region` fields) to get a blanket impl for free. +/// (no `Block`/`Cfg` fields) to get a blanket impl for free. /// 3. **Manual**: Implement directly for full control over parse+emit. /// /// # Decision table /// /// | Dialect characteristics | Recommended path | /// |------------------------------------------|--------------------------| -/// | Has `Block`, `Region`, or `DiGraph` fields | `#[derive(HasParser)]` | -/// | No `Block`/`Region`/`DiGraph`, no recursion | `impl SimpleParseEmit` | +/// | Has `Block`, `Cfg`, or `DiGraph` fields | `#[derive(HasParser)]` | +/// | No `Block`/`Cfg`/`DiGraph`, no recursion | `impl SimpleParseEmit` | /// | Custom parse logic or non-standard emit | `impl ParseEmit` manually | pub trait ParseEmit: Dialect { /// Parse input text and emit a single IR statement. diff --git a/crates/kirin-cmp/src/tests.rs b/crates/kirin-cmp/src/tests.rs index 925520c80b..0c0288210e 100644 --- a/crates/kirin-cmp/src/tests.rs +++ b/crates/kirin-cmp/src/tests.rs @@ -1,5 +1,5 @@ use kirin::ir::{ - HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, + HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsSpeculatable, IsTerminator, TestSSAValue, }; use kirin_test_types::UnitType; @@ -141,13 +141,13 @@ fn no_blocks() { } } -// --- HasRegions: no regions --- +// --- HasCfgs: no cfgs --- #[test] -fn no_regions() { +fn no_cfgs() { for op in all_variants() { - let regions: Vec<_> = op.regions().collect(); - assert_eq!(regions.len(), 0, "expected 0 regions for {op:?}"); + let cfgs: Vec<_> = op.cfgs().collect(); + assert_eq!(cfgs.len(), 0, "expected 0 cfgs for {op:?}"); } } diff --git a/crates/kirin-constant/src/tests.rs b/crates/kirin-constant/src/tests.rs index 3257d8bd7a..cc983b142b 100644 --- a/crates/kirin-constant/src/tests.rs +++ b/crates/kirin-constant/src/tests.rs @@ -1,6 +1,6 @@ use kirin::ir::{ - HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, - IsTerminator, TestSSAValue, Typeof, + HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsTerminator, + TestSSAValue, Typeof, }; use kirin::pretty::{ArenaDoc, DocAllocator, Document, PrettyPrint}; @@ -92,7 +92,7 @@ fn one_result() { assert_eq!(c.results().count(), 1); } -// --- HasSuccessors / HasBlocks / HasRegions: all empty --- +// --- HasSuccessors / HasBlocks / HasCfgs: all empty --- #[test] fn no_successors() { @@ -105,8 +105,8 @@ fn no_blocks() { } #[test] -fn no_regions() { - assert_eq!(make_constant(0).regions().count(), 0); +fn no_cfgs() { + assert_eq!(make_constant(0).cfgs().count(), 0); } // --- Clone + PartialEq --- diff --git a/crates/kirin-derive-chumsky/src/codegen/emit_ir/field_emit.rs b/crates/kirin-derive-chumsky/src/codegen/emit_ir/field_emit.rs index ce55096152..d8f6bfa011 100644 --- a/crates/kirin-derive-chumsky/src/codegen/emit_ir/field_emit.rs +++ b/crates/kirin-derive-chumsky/src/codegen/emit_ir/field_emit.rs @@ -55,7 +55,7 @@ impl GenerateEmitIR { FieldCategory::Block => quote! { let #emitted_var = #var.value.emit_with(ctx, emit_language_output)?; }, - FieldCategory::Region => quote! { + FieldCategory::Cfg => quote! { let #emitted_var = #var.emit_with(ctx, emit_language_output)?; }, FieldCategory::DiGraph => quote! { diff --git a/crates/kirin-derive-chumsky/src/codegen/emit_ir/generate.rs b/crates/kirin-derive-chumsky/src/codegen/emit_ir/generate.rs index a15e9142de..54b41133d5 100644 --- a/crates/kirin-derive-chumsky/src/codegen/emit_ir/generate.rs +++ b/crates/kirin-derive-chumsky/src/codegen/emit_ir/generate.rs @@ -117,7 +117,7 @@ impl GenerateEmitIR { matches!( field.category(), FieldCategory::Block - | FieldCategory::Region + | FieldCategory::Cfg | FieldCategory::DiGraph | FieldCategory::UnGraph ) @@ -132,7 +132,7 @@ impl GenerateEmitIR { matches!( field.category(), FieldCategory::Block - | FieldCategory::Region + | FieldCategory::Cfg | FieldCategory::DiGraph | FieldCategory::UnGraph ) diff --git a/crates/kirin-derive-chumsky/src/codegen/parser/chain.rs b/crates/kirin-derive-chumsky/src/codegen/parser/chain.rs index fae28f10f8..a580779f56 100644 --- a/crates/kirin-derive-chumsky/src/codegen/parser/chain.rs +++ b/crates/kirin-derive-chumsky/src/codegen/parser/chain.rs @@ -662,14 +662,14 @@ impl GenerateHasDialectParser { } } } - FieldCategory::Region => { - // region_body returns Vec> + FieldCategory::Cfg => { + // cfg_body returns Vec> let blocks_expr = find_var(BodyProjection::Body) .map(|v| quote! { #v }) .unwrap_or_else(|| quote! { ::std::vec::Vec::new() }); quote! { - #crate_path::Region { + #crate_path::Cfg { blocks: #blocks_expr, } } diff --git a/crates/kirin-derive-chumsky/src/field_kind.rs b/crates/kirin-derive-chumsky/src/field_kind.rs index ee988103b2..767406976c 100644 --- a/crates/kirin-derive-chumsky/src/field_kind.rs +++ b/crates/kirin-derive-chumsky/src/field_kind.rs @@ -26,7 +26,7 @@ impl FieldCategoryExt for FieldCategory { FieldCategory::Result => "result_value", FieldCategory::Block => "block", FieldCategory::Successor => "successor", - FieldCategory::Region => "region", + FieldCategory::Cfg => "cfg", FieldCategory::Symbol => "symbol", FieldCategory::Value => "value", FieldCategory::DiGraph => "digraph", @@ -66,8 +66,8 @@ pub fn ast_type( FieldCategory::Successor => { quote! { #crate_path::BlockLabel<'t> } } - FieldCategory::Region => { - quote! { #crate_path::Region<'t, #type_output, LanguageOutput> } + FieldCategory::Cfg => { + quote! { #crate_path::Cfg<'t, #type_output, LanguageOutput> } } FieldCategory::Symbol => { quote! { #crate_path::SymbolName<'t> } @@ -141,14 +141,14 @@ pub fn parser_expr( FieldCategory::Successor => { quote! { #crate_path::block_label() } } - FieldCategory::Region => match opt { + FieldCategory::Cfg => match opt { FormatOption::Default => { - quote! { #crate_path::region::<_, #ir_type, _>(language.clone()) } + quote! { #crate_path::cfg::<_, #ir_type, _>(language.clone()) } } FormatOption::Body(BodyProjection::Body) => { - quote! { #crate_path::region_body::<_, #ir_type, _>(language.clone()) } + quote! { #crate_path::cfg_body::<_, #ir_type, _>(language.clone()) } } - _ => unreachable!("validation prevents other projections on Region fields"), + _ => unreachable!("validation prevents other projections on Cfg fields"), }, FieldCategory::Symbol => { quote! { #crate_path::symbol() } @@ -269,18 +269,16 @@ pub fn print_expr( FieldCategory::Successor => quote! { #prettyless_path::PrettyPrint::pretty_print(#field_ref, doc) }, - FieldCategory::Region => match opt { - FormatOption::Default => quote! { doc.print_region(#field_ref) }, + FieldCategory::Cfg => match opt { + FormatOption::Default => quote! { doc.print_cfg(#field_ref) }, FormatOption::Body(BodyProjection::Body) => quote! { - doc.print_region_body_only(#field_ref) + doc.print_cfg_body_only(#field_ref) }, FormatOption::Body(_) => { - unreachable!( - "Ports/Captures/Yields/Args projections are not valid on Region fields" - ) + unreachable!("Ports/Captures/Yields/Args projections are not valid on Cfg fields") } FormatOption::Name | FormatOption::Type | FormatOption::Signature(_) => { - unreachable!("Name/Type/Signature projections are not valid on Region fields") + unreachable!("Name/Type/Signature projections are not valid on Cfg fields") } }, FieldCategory::Symbol => quote! { diff --git a/crates/kirin-derive-chumsky/src/format.rs b/crates/kirin-derive-chumsky/src/format.rs index 19024e90bc..d9e5cc3685 100644 --- a/crates/kirin-derive-chumsky/src/format.rs +++ b/crates/kirin-derive-chumsky/src/format.rs @@ -40,7 +40,7 @@ //! | Result | -- | -- | yes | | | | | | | //! | Block | yes | | | | | yes | yes | | | //! | Successor | yes | | | | | | | | | -//! | Region | yes | | | | | | yes | | | +//! | Cfg | yes | | | | | | yes | | | //! | Symbol | yes | | | | | | | | | //! | Value | yes | | | | | | | | | //! | DiGraph | yes | | | yes | yes | | yes | | | diff --git a/crates/kirin-derive-chumsky/src/input.rs b/crates/kirin-derive-chumsky/src/input.rs index 6625f8919c..ffc3a6441c 100644 --- a/crates/kirin-derive-chumsky/src/input.rs +++ b/crates/kirin-derive-chumsky/src/input.rs @@ -7,7 +7,7 @@ use crate::{ChumskyLayout, PrettyPrintLayout}; /// Parses derive input for chumsky macros. /// /// For value-only definitions, `#[kirin(type = ...)]` is optional. -/// For dialect-like definitions using SSA/Result/Block/Region fields, +/// For dialect-like definitions using SSA/Result/Block/Cfg fields, /// `#[kirin(type = ...)]` remains required. pub fn parse_derive_input( ast: &syn::DeriveInput, @@ -26,7 +26,7 @@ pub fn parse_derive_input( if input_requires_ir_type(&input) { return Err(darling::Error::custom( - "`#[kirin(type = ...)]` is required when using SSAValue, ResultValue, Block, or Region fields", + "`#[kirin(type = ...)]` is required when using SSAValue, ResultValue, Block, or Cfg fields", ) .with_span(&ast.ident)); } @@ -59,7 +59,7 @@ fn statement_requires_ir_type( FieldCategory::Argument | FieldCategory::Result | FieldCategory::Block - | FieldCategory::Region + | FieldCategory::Cfg ) }) } @@ -83,7 +83,7 @@ pub fn parse_pretty_derive_input( if input_requires_ir_type(&input) { return Err(darling::Error::custom( - "`#[kirin(type = ...)]` is required when using SSAValue, ResultValue, Block, or Region fields", + "`#[kirin(type = ...)]` is required when using SSAValue, ResultValue, Block, or Cfg fields", ) .with_span(&ast.ident)); } diff --git a/crates/kirin-derive-chumsky/src/validation.rs b/crates/kirin-derive-chumsky/src/validation.rs index 3ecd863c0d..28601c1127 100644 --- a/crates/kirin-derive-chumsky/src/validation.rs +++ b/crates/kirin-derive-chumsky/src/validation.rs @@ -350,7 +350,7 @@ impl<'ir> ValidationVisitor<'ir> { FieldCategory::Block => { (&[BodyProjection::Args, BodyProjection::Body], "Block") } - FieldCategory::Region => (&[BodyProjection::Body], "Region"), + FieldCategory::Cfg => (&[BodyProjection::Body], "Cfg"), _ => continue, }; let missing: Vec<&str> = required @@ -492,7 +492,7 @@ impl<'ir> FormatVisitor<'ir> for ValidationVisitor<'ir> { category, FieldCategory::DiGraph | FieldCategory::UnGraph - | FieldCategory::Region + | FieldCategory::Cfg | FieldCategory::Block ) } @@ -508,7 +508,7 @@ impl<'ir> FormatVisitor<'ir> for ValidationVisitor<'ir> { let valid_on = match proj { BodyProjection::Ports | BodyProjection::Captures => "DiGraph or UnGraph", BodyProjection::Args => "Block", - BodyProjection::Body => "DiGraph, UnGraph, Region, or Block", + BodyProjection::Body => "DiGraph, UnGraph, Cfg, or Block", }; self.add_error(format!( "'{}' projection is only valid on {} fields, but '{}' is a {} field", @@ -763,12 +763,12 @@ mod tests { } } - fn make_region(index: usize, name: &str) -> FieldInfo { + fn make_cfg(index: usize, name: &str) -> FieldInfo { FieldInfo { index, ident: Some(syn::Ident::new(name, proc_macro2::Span::call_site())), collection: Collection::Single, - data: FieldData::Region, + data: FieldData::Cfg, } } @@ -808,8 +808,8 @@ mod tests { } #[test] - fn body_projection_on_region_is_valid() { - let fields = vec![make_region(0, "body")]; + fn body_projection_on_cfg_is_valid() { + let fields = vec![make_cfg(0, "body")]; let stmt = make_stmt(fields.clone()); let format = Format::parse("{body:body}", None).unwrap(); assert!(validate_format(&stmt, &format, &fields).is_ok()); @@ -824,8 +824,8 @@ mod tests { } #[test] - fn ports_projection_on_region_is_invalid() { - let fields = vec![make_region(0, "body")]; + fn ports_projection_on_cfg_is_invalid() { + let fields = vec![make_cfg(0, "body")]; let stmt = make_stmt(fields.clone()); let format = Format::parse("{body:ports}", None).unwrap(); let err = validate_format(&stmt, &format, &fields).unwrap_err(); @@ -837,8 +837,8 @@ mod tests { } #[test] - fn args_projection_on_region_is_invalid() { - let fields = vec![make_region(0, "body")]; + fn args_projection_on_cfg_is_invalid() { + let fields = vec![make_cfg(0, "body")]; let stmt = make_stmt(fields.clone()); let format = Format::parse("{body:args}", None).unwrap(); let err = validate_format(&stmt, &format, &fields).unwrap_err(); diff --git a/crates/kirin-derive-ir/src/generate.rs b/crates/kirin-derive-ir/src/generate.rs index 8485cd740d..f1ae211c28 100644 --- a/crates/kirin-derive-ir/src/generate.rs +++ b/crates/kirin-derive-ir/src/generate.rs @@ -83,20 +83,20 @@ pub(crate) const HAS_SUCCESSORS_MUT: FieldIterConfig = FieldIterConfig { trait_method: "successors_mut", trait_type_iter: "IterMut", }; -pub(crate) const HAS_REGIONS: FieldIterConfig = FieldIterConfig { - kind: FieldIterKind::Regions, +pub(crate) const HAS_CFGS: FieldIterConfig = FieldIterConfig { + kind: FieldIterKind::Cfgs, mutable: false, - trait_name: "HasRegions", - matching_type: "Region", - trait_method: "regions", + trait_name: "HasCfgs", + matching_type: "Cfg", + trait_method: "cfgs", trait_type_iter: "Iter", }; -pub(crate) const HAS_REGIONS_MUT: FieldIterConfig = FieldIterConfig { - kind: FieldIterKind::Regions, +pub(crate) const HAS_CFGS_MUT: FieldIterConfig = FieldIterConfig { + kind: FieldIterKind::Cfgs, mutable: true, - trait_name: "HasRegionsMut", - matching_type: "Region", - trait_method: "regions_mut", + trait_name: "HasCfgsMut", + matching_type: "Cfg", + trait_method: "cfgs_mut", trait_type_iter: "IterMut", }; pub(crate) const HAS_DIGRAPHS: FieldIterConfig = FieldIterConfig { @@ -141,8 +141,8 @@ pub(crate) const FIELD_ITER_CONFIGS: [FieldIterConfig; 14] = [ HAS_BLOCKS_MUT, HAS_SUCCESSORS, HAS_SUCCESSORS_MUT, - HAS_REGIONS, - HAS_REGIONS_MUT, + HAS_CFGS, + HAS_CFGS_MUT, HAS_DIGRAPHS, HAS_DIGRAPHS_MUT, HAS_UNGRAPHS, diff --git a/crates/kirin-derive-ir/src/lib.rs b/crates/kirin-derive-ir/src/lib.rs index aed2b5ce32..40765ce67d 100644 --- a/crates/kirin-derive-ir/src/lib.rs +++ b/crates/kirin-derive-ir/src/lib.rs @@ -76,8 +76,8 @@ derive_field_iter_macro!( HasSuccessorsMut, HAS_SUCCESSORS_MUT ); -derive_field_iter_macro!(derive_has_regions, HasRegions, HAS_REGIONS); -derive_field_iter_macro!(derive_has_regions_mut, HasRegionsMut, HAS_REGIONS_MUT); +derive_field_iter_macro!(derive_has_cfgs, HasCfgs, HAS_CFGS); +derive_field_iter_macro!(derive_has_cfgs_mut, HasCfgsMut, HAS_CFGS_MUT); derive_field_iter_macro!(derive_has_digraphs, HasDigraphs, HAS_DIGRAPHS); derive_field_iter_macro!(derive_has_digraphs_mut, HasDigraphsMut, HAS_DIGRAPHS_MUT); derive_field_iter_macro!(derive_has_ungraphs, HasUngraphs, HAS_UNGRAPHS); diff --git a/crates/kirin-derive-ir/src/tests/dialect.rs b/crates/kirin-derive-ir/src/tests/dialect.rs index aa03ceb808..b11b7d7a9e 100644 --- a/crates/kirin-derive-ir/src/tests/dialect.rs +++ b/crates/kirin-derive-ir/src/tests/dialect.rs @@ -27,14 +27,14 @@ fn test_dialect_derive_struct_with_ssa_fields() { } #[test] -fn test_dialect_derive_struct_with_region_block() { +fn test_dialect_derive_struct_with_cfg_block() { let input: syn::DeriveInput = syn::parse_quote! { #[kirin(type = SimpleType)] struct IfOp { condition: Value, then_block: Block, else_block: Block, - body: Region, + body: Cfg, } }; insta::assert_snapshot!(generate_dialect_code(input)); diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap index 161ac18cdc..27cc8e1bdc 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap @@ -171,43 +171,43 @@ impl<'a> Iterator for NopSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> kirin_ir::HasRegions<'a> for Nop { - type Iter = NopRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { - NopRegionsIter { - inner: std::iter::empty::<&'a kirin_ir::Region>(), +impl<'a> kirin_ir::HasCfgs<'a> for Nop { + type Iter = NopCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { + NopCfgsIter { + inner: std::iter::empty::<&'a kirin_ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct NopRegionsIter<'a> { - inner: std::iter::Empty<&'a kirin_ir::Region>, +pub struct NopCfgsIter<'a> { + inner: std::iter::Empty<&'a kirin_ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for NopRegionsIter<'a> { - type Item = &'a kirin_ir::Region; +impl<'a> Iterator for NopCfgsIter<'a> { + type Item = &'a kirin_ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> kirin_ir::HasRegionsMut<'a> for Nop { - type IterMut = NopRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { - NopRegionsMutIter { - inner: std::iter::empty::<&'a mut kirin_ir::Region>(), +impl<'a> kirin_ir::HasCfgsMut<'a> for Nop { + type IterMut = NopCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { + NopCfgsMutIter { + inner: std::iter::empty::<&'a mut kirin_ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct NopRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut kirin_ir::Region>, +pub struct NopCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut kirin_ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for NopRegionsMutIter<'a> { - type Item = &'a mut kirin_ir::Region; +impl<'a> Iterator for NopCfgsMutIter<'a> { + type Item = &'a mut kirin_ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap index 237570a66f..56e07774fa 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap @@ -243,28 +243,28 @@ impl<'a> Iterator for MixedOpsSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for MixedOps { - type Iter = MixedOpsRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for MixedOps { + type Iter = MixedOpsCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { match self { Self::Add(field_0) => { - MixedOpsRegionsIter::Add(::regions(field_0)) + MixedOpsCfgsIter::Add(::cfgs(field_0)) } Self::Literal { value } => { - MixedOpsRegionsIter::Literal(std::iter::empty::<&'a ::kirin::ir::Region>()) + MixedOpsCfgsIter::Literal(std::iter::empty::<&'a ::kirin::ir::Cfg>()) } } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedOpsRegionsIter<'a> { - Add(>::Iter), - Literal(std::iter::Empty<&'a ::kirin::ir::Region>), +pub enum MixedOpsCfgsIter<'a> { + Add(>::Iter), + Literal(std::iter::Empty<&'a ::kirin::ir::Cfg>), } #[automatically_derived] -impl<'a> Iterator for MixedOpsRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for MixedOpsCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Add(inner) => inner.next(), @@ -273,28 +273,28 @@ impl<'a> Iterator for MixedOpsRegionsIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for MixedOps { - type IterMut = MixedOpsRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for MixedOps { + type IterMut = MixedOpsCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { match self { - Self::Add(field_0) => MixedOpsRegionsMutIter::Add( - ::regions_mut(field_0), - ), + Self::Add(field_0) => { + MixedOpsCfgsMutIter::Add(::cfgs_mut(field_0)) + } Self::Literal { value } => { - MixedOpsRegionsMutIter::Literal(std::iter::empty::<&'a mut ::kirin::ir::Region>()) + MixedOpsCfgsMutIter::Literal(std::iter::empty::<&'a mut ::kirin::ir::Cfg>()) } } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedOpsRegionsMutIter<'a> { - Add(>::IterMut), - Literal(std::iter::Empty<&'a mut ::kirin::ir::Region>), +pub enum MixedOpsCfgsMutIter<'a> { + Add(>::IterMut), + Literal(std::iter::Empty<&'a mut ::kirin::ir::Cfg>), } #[automatically_derived] -impl<'a> Iterator for MixedOpsRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for MixedOpsCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Add(inner) => inner.next(), diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap index 398e15a1a3..90fee95a67 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap @@ -243,28 +243,28 @@ impl<'a> Iterator for MixedOpsSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for MixedOps { - type Iter = MixedOpsRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for MixedOps { + type Iter = MixedOpsCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { match self { Self::Add(field_0) => { - MixedOpsRegionsIter::Add(::regions(field_0)) + MixedOpsCfgsIter::Add(::cfgs(field_0)) } Self::Literal { value } => { - MixedOpsRegionsIter::Literal(std::iter::empty::<&'a ::kirin::ir::Region>()) + MixedOpsCfgsIter::Literal(std::iter::empty::<&'a ::kirin::ir::Cfg>()) } } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedOpsRegionsIter<'a> { - Add(>::Iter), - Literal(std::iter::Empty<&'a ::kirin::ir::Region>), +pub enum MixedOpsCfgsIter<'a> { + Add(>::Iter), + Literal(std::iter::Empty<&'a ::kirin::ir::Cfg>), } #[automatically_derived] -impl<'a> Iterator for MixedOpsRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for MixedOpsCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Add(inner) => inner.next(), @@ -273,28 +273,28 @@ impl<'a> Iterator for MixedOpsRegionsIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for MixedOps { - type IterMut = MixedOpsRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for MixedOps { + type IterMut = MixedOpsCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { match self { - Self::Add(field_0) => MixedOpsRegionsMutIter::Add( - ::regions_mut(field_0), - ), + Self::Add(field_0) => { + MixedOpsCfgsMutIter::Add(::cfgs_mut(field_0)) + } Self::Literal { value } => { - MixedOpsRegionsMutIter::Literal(std::iter::empty::<&'a mut ::kirin::ir::Region>()) + MixedOpsCfgsMutIter::Literal(std::iter::empty::<&'a mut ::kirin::ir::Cfg>()) } } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedOpsRegionsMutIter<'a> { - Add(>::IterMut), - Literal(std::iter::Empty<&'a mut ::kirin::ir::Region>), +pub enum MixedOpsCfgsMutIter<'a> { + Add(>::IterMut), + Literal(std::iter::Empty<&'a mut ::kirin::ir::Cfg>), } #[automatically_derived] -impl<'a> Iterator for MixedOpsRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for MixedOpsCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Add(inner) => inner.next(), diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap index ababe00eba..39fc25296d 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap @@ -283,32 +283,32 @@ impl<'a> Iterator for CompositeOpsSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for CompositeOps { - type Iter = CompositeOpsRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for CompositeOps { + type Iter = CompositeOpsCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { match self { - Self::Alpha(field_0) => CompositeOpsRegionsIter::Alpha( - ::regions(field_0), - ), + Self::Alpha(field_0) => { + CompositeOpsCfgsIter::Alpha(::cfgs(field_0)) + } Self::Beta(field_0) => { - CompositeOpsRegionsIter::Beta(::regions(field_0)) + CompositeOpsCfgsIter::Beta(::cfgs(field_0)) + } + Self::Gamma(field_0) => { + CompositeOpsCfgsIter::Gamma(::cfgs(field_0)) } - Self::Gamma(field_0) => CompositeOpsRegionsIter::Gamma( - ::regions(field_0), - ), } } } #[automatically_derived] #[doc(hidden)] -pub enum CompositeOpsRegionsIter<'a> { - Alpha(>::Iter), - Beta(>::Iter), - Gamma(>::Iter), +pub enum CompositeOpsCfgsIter<'a> { + Alpha(>::Iter), + Beta(>::Iter), + Gamma(>::Iter), } #[automatically_derived] -impl<'a> Iterator for CompositeOpsRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for CompositeOpsCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Alpha(inner) => inner.next(), @@ -318,32 +318,32 @@ impl<'a> Iterator for CompositeOpsRegionsIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for CompositeOps { - type IterMut = CompositeOpsRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for CompositeOps { + type IterMut = CompositeOpsCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { match self { - Self::Alpha(field_0) => CompositeOpsRegionsMutIter::Alpha( - ::regions_mut(field_0), + Self::Alpha(field_0) => CompositeOpsCfgsMutIter::Alpha( + ::cfgs_mut(field_0), ), - Self::Beta(field_0) => CompositeOpsRegionsMutIter::Beta( - ::regions_mut(field_0), + Self::Beta(field_0) => CompositeOpsCfgsMutIter::Beta( + ::cfgs_mut(field_0), ), - Self::Gamma(field_0) => CompositeOpsRegionsMutIter::Gamma( - ::regions_mut(field_0), + Self::Gamma(field_0) => CompositeOpsCfgsMutIter::Gamma( + ::cfgs_mut(field_0), ), } } } #[automatically_derived] #[doc(hidden)] -pub enum CompositeOpsRegionsMutIter<'a> { - Alpha(>::IterMut), - Beta(>::IterMut), - Gamma(>::IterMut), +pub enum CompositeOpsCfgsMutIter<'a> { + Alpha(>::IterMut), + Beta(>::IterMut), + Gamma(>::IterMut), } #[automatically_derived] -impl<'a> Iterator for CompositeOpsRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for CompositeOpsCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Alpha(inner) => inner.next(), diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap index bb9a5ab655..3464c95666 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap @@ -243,28 +243,28 @@ impl<'a> Iterator for ArithLanguageSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for ArithLanguage { - type Iter = ArithLanguageRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for ArithLanguage { + type Iter = ArithLanguageCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { match self { Self::Add(field_0) => { - ArithLanguageRegionsIter::Add(::regions(field_0)) + ArithLanguageCfgsIter::Add(::cfgs(field_0)) } Self::Sub(field_0) => { - ArithLanguageRegionsIter::Sub(::regions(field_0)) + ArithLanguageCfgsIter::Sub(::cfgs(field_0)) } } } } #[automatically_derived] #[doc(hidden)] -pub enum ArithLanguageRegionsIter<'a> { - Add(>::Iter), - Sub(>::Iter), +pub enum ArithLanguageCfgsIter<'a> { + Add(>::Iter), + Sub(>::Iter), } #[automatically_derived] -impl<'a> Iterator for ArithLanguageRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for ArithLanguageCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Add(inner) => inner.next(), @@ -273,28 +273,28 @@ impl<'a> Iterator for ArithLanguageRegionsIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for ArithLanguage { - type IterMut = ArithLanguageRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for ArithLanguage { + type IterMut = ArithLanguageCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { match self { - Self::Add(field_0) => ArithLanguageRegionsMutIter::Add( - ::regions_mut(field_0), - ), - Self::Sub(field_0) => ArithLanguageRegionsMutIter::Sub( - ::regions_mut(field_0), - ), + Self::Add(field_0) => { + ArithLanguageCfgsMutIter::Add(::cfgs_mut(field_0)) + } + Self::Sub(field_0) => { + ArithLanguageCfgsMutIter::Sub(::cfgs_mut(field_0)) + } } } } #[automatically_derived] #[doc(hidden)] -pub enum ArithLanguageRegionsMutIter<'a> { - Add(>::IterMut), - Sub(>::IterMut), +pub enum ArithLanguageCfgsMutIter<'a> { + Add(>::IterMut), + Sub(>::IterMut), } #[automatically_derived] -impl<'a> Iterator for ArithLanguageRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for ArithLanguageCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Add(inner) => inner.next(), diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap index 9b400179a2..b7d99bd7e6 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap @@ -243,28 +243,28 @@ impl<'a> Iterator for MixedWrapsSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for MixedWraps { - type Iter = MixedWrapsRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for MixedWraps { + type Iter = MixedWrapsCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { match self { - Self::Simple(field_0) => MixedWrapsRegionsIter::Simple( - ::regions(field_0), - ), + Self::Simple(field_0) => { + MixedWrapsCfgsIter::Simple(::cfgs(field_0)) + } Self::Wrapped { inner, tag } => { - MixedWrapsRegionsIter::Wrapped(::regions(inner)) + MixedWrapsCfgsIter::Wrapped(::cfgs(inner)) } } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedWrapsRegionsIter<'a> { - Simple(>::Iter), - Wrapped(>::Iter), +pub enum MixedWrapsCfgsIter<'a> { + Simple(>::Iter), + Wrapped(>::Iter), } #[automatically_derived] -impl<'a> Iterator for MixedWrapsRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for MixedWrapsCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Simple(inner) => inner.next(), @@ -273,28 +273,28 @@ impl<'a> Iterator for MixedWrapsRegionsIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for MixedWraps { - type IterMut = MixedWrapsRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for MixedWraps { + type IterMut = MixedWrapsCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { match self { - Self::Simple(field_0) => MixedWrapsRegionsMutIter::Simple( - ::regions_mut(field_0), + Self::Simple(field_0) => MixedWrapsCfgsMutIter::Simple( + ::cfgs_mut(field_0), ), - Self::Wrapped { inner, tag } => MixedWrapsRegionsMutIter::Wrapped( - ::regions_mut(inner), + Self::Wrapped { inner, tag } => MixedWrapsCfgsMutIter::Wrapped( + ::cfgs_mut(inner), ), } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedWrapsRegionsMutIter<'a> { - Simple(>::IterMut), - Wrapped(>::IterMut), +pub enum MixedWrapsCfgsMutIter<'a> { + Simple(>::IterMut), + Wrapped(>::IterMut), } #[automatically_derived] -impl<'a> Iterator for MixedWrapsRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for MixedWrapsCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Simple(inner) => inner.next(), diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap index bcf53762e5..b7d99bd7e6 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap @@ -1,6 +1,5 @@ --- source: crates/kirin-derive-ir/src/tests/dialect.rs -assertion_line: 268 expression: code --- #[automatically_derived] @@ -244,28 +243,28 @@ impl<'a> Iterator for MixedWrapsSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for MixedWraps { - type Iter = MixedWrapsRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for MixedWraps { + type Iter = MixedWrapsCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { match self { - Self::Simple(field_0) => MixedWrapsRegionsIter::Simple( - ::regions(field_0), - ), + Self::Simple(field_0) => { + MixedWrapsCfgsIter::Simple(::cfgs(field_0)) + } Self::Wrapped { inner, tag } => { - MixedWrapsRegionsIter::Wrapped(::regions(inner)) + MixedWrapsCfgsIter::Wrapped(::cfgs(inner)) } } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedWrapsRegionsIter<'a> { - Simple(>::Iter), - Wrapped(>::Iter), +pub enum MixedWrapsCfgsIter<'a> { + Simple(>::Iter), + Wrapped(>::Iter), } #[automatically_derived] -impl<'a> Iterator for MixedWrapsRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for MixedWrapsCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Simple(inner) => inner.next(), @@ -274,28 +273,28 @@ impl<'a> Iterator for MixedWrapsRegionsIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for MixedWraps { - type IterMut = MixedWrapsRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for MixedWraps { + type IterMut = MixedWrapsCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { match self { - Self::Simple(field_0) => MixedWrapsRegionsMutIter::Simple( - ::regions_mut(field_0), + Self::Simple(field_0) => MixedWrapsCfgsMutIter::Simple( + ::cfgs_mut(field_0), ), - Self::Wrapped { inner, tag } => MixedWrapsRegionsMutIter::Wrapped( - ::regions_mut(inner), + Self::Wrapped { inner, tag } => MixedWrapsCfgsMutIter::Wrapped( + ::cfgs_mut(inner), ), } } } #[automatically_derived] #[doc(hidden)] -pub enum MixedWrapsRegionsMutIter<'a> { - Simple(>::IterMut), - Wrapped(>::IterMut), +pub enum MixedWrapsCfgsMutIter<'a> { + Simple(>::IterMut), + Wrapped(>::IterMut), } #[automatically_derived] -impl<'a> Iterator for MixedWrapsRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for MixedWrapsCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Simple(inner) => inner.next(), diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap index 7525b13a69..0a6ae7203d 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap @@ -243,28 +243,28 @@ impl<'a> Iterator for CfOpsSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for CfOps { - type Iter = CfOpsRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for CfOps { + type Iter = CfOpsCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { match self { Self::Branch(field_0) => { - CfOpsRegionsIter::Branch(::regions(field_0)) + CfOpsCfgsIter::Branch(::cfgs(field_0)) } Self::Return(field_0) => { - CfOpsRegionsIter::Return(::regions(field_0)) + CfOpsCfgsIter::Return(::cfgs(field_0)) } } } } #[automatically_derived] #[doc(hidden)] -pub enum CfOpsRegionsIter<'a> { - Branch(>::Iter), - Return(>::Iter), +pub enum CfOpsCfgsIter<'a> { + Branch(>::Iter), + Return(>::Iter), } #[automatically_derived] -impl<'a> Iterator for CfOpsRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for CfOpsCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Branch(inner) => inner.next(), @@ -273,28 +273,28 @@ impl<'a> Iterator for CfOpsRegionsIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for CfOps { - type IterMut = CfOpsRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for CfOps { + type IterMut = CfOpsCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { match self { - Self::Branch(field_0) => CfOpsRegionsMutIter::Branch( - ::regions_mut(field_0), - ), - Self::Return(field_0) => CfOpsRegionsMutIter::Return( - ::regions_mut(field_0), - ), + Self::Branch(field_0) => { + CfOpsCfgsMutIter::Branch(::cfgs_mut(field_0)) + } + Self::Return(field_0) => { + CfOpsCfgsMutIter::Return(::cfgs_mut(field_0)) + } } } } #[automatically_derived] #[doc(hidden)] -pub enum CfOpsRegionsMutIter<'a> { - Branch(>::IterMut), - Return(>::IterMut), +pub enum CfOpsCfgsMutIter<'a> { + Branch(>::IterMut), + Return(>::IterMut), } #[automatically_derived] -impl<'a> Iterator for CfOpsRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for CfOpsCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { match self { Self::Branch(inner) => inner.next(), diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap index e2621cbdbe..47ec80a8ea 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for ConstantSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for Constant { - type Iter = ConstantRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for Constant { + type Iter = ConstantCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { result } = self; - ConstantRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + ConstantCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ConstantRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct ConstantCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ConstantRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for ConstantCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for Constant { - type IterMut = ConstantRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for Constant { + type IterMut = ConstantCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { result } = self; - ConstantRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + ConstantCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ConstantRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct ConstantCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ConstantRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for ConstantCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap index 60d1acbf00..476ff6e3b4 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for ZxWireSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for ZxWire { - type Iter = ZxWireRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for ZxWire { + type Iter = ZxWireCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { res } = self; - ZxWireRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + ZxWireCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ZxWireRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct ZxWireCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ZxWireRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for ZxWireCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for ZxWire { - type IterMut = ZxWireRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for ZxWire { + type IterMut = ZxWireCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { res } = self; - ZxWireRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + ZxWireCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ZxWireRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct ZxWireCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ZxWireRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for ZxWireCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap index 483626c23b..fdbea115b1 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap @@ -171,43 +171,43 @@ impl<'a> Iterator for NopSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for Nop { - type Iter = NopRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { - NopRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), +impl<'a> ::kirin::ir::HasCfgs<'a> for Nop { + type Iter = NopCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { + NopCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct NopRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct NopCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for NopRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for NopCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for Nop { - type IterMut = NopRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { - NopRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), +impl<'a> ::kirin::ir::HasCfgsMut<'a> for Nop { + type IterMut = NopCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { + NopCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct NopRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct NopCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for NopRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for NopCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap index 603d50e378..61f11f0eb5 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap @@ -217,53 +217,53 @@ impl<'a> Iterator for ConditionalOpSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for ConditionalOp { - type Iter = ConditionalOpRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for ConditionalOp { + type Iter = ConditionalOpCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { cond, then_block, else_block, } = self; - ConditionalOpRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + ConditionalOpCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ConditionalOpRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct ConditionalOpCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ConditionalOpRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for ConditionalOpCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for ConditionalOp { - type IterMut = ConditionalOpRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for ConditionalOp { + type IterMut = ConditionalOpCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { cond, then_block, else_block, } = self; - ConditionalOpRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + ConditionalOpCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ConditionalOpRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct ConditionalOpCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ConditionalOpRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for ConditionalOpCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap index 6403144f9c..071e9d813f 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap @@ -177,45 +177,45 @@ impl<'a> Iterator for CallExternSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for CallExtern { - type Iter = CallExternRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for CallExtern { + type Iter = CallExternCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { target, args } = self; - CallExternRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + CallExternCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct CallExternRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct CallExternCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for CallExternRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for CallExternCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for CallExtern { - type IterMut = CallExternRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for CallExtern { + type IterMut = CallExternCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { target, args } = self; - CallExternRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + CallExternCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct CallExternRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct CallExternCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for CallExternRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for CallExternCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap index c2605ad4ec..e250632cab 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for ReturnSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for Return { - type Iter = ReturnRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for Return { + type Iter = ReturnCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { value } = self; - ReturnRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + ReturnCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ReturnRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct ReturnCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ReturnRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for ReturnCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for Return { - type IterMut = ReturnRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for Return { + type IterMut = ReturnCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { value } = self; - ReturnRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + ReturnCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ReturnRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct ReturnCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ReturnRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for ReturnCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap index 81f12313cc..37cea93750 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap @@ -177,45 +177,45 @@ impl<'a> Iterator for CallOpSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for CallOp { - type Iter = CallOpRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for CallOp { + type Iter = CallOpCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { args, result } = self; - CallOpRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + CallOpCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct CallOpRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct CallOpCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for CallOpRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for CallOpCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for CallOp { - type IterMut = CallOpRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for CallOp { + type IterMut = CallOpCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { args, result } = self; - CallOpRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + CallOpCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct CallOpRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct CallOpCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for CallOpRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for CallOpCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap index 6d9c12ebc1..9df3eadebc 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap @@ -225,55 +225,55 @@ impl<'a> Iterator for QuantumEvalSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for QuantumEval { - type Iter = QuantumEvalRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for QuantumEval { + type Iter = QuantumEvalCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { qubit, angle, body, res, } = self; - QuantumEvalRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + QuantumEvalCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct QuantumEvalRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct QuantumEvalCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for QuantumEvalRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for QuantumEvalCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for QuantumEval { - type IterMut = QuantumEvalRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for QuantumEval { + type IterMut = QuantumEvalCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { qubit, angle, body, res, } = self; - QuantumEvalRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + QuantumEvalCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct QuantumEvalRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct QuantumEvalCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for QuantumEvalRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for QuantumEvalCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap index 8503ec711b..6f10414957 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for BinaryOpSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for BinaryOp { - type Iter = BinaryOpRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for BinaryOp { + type Iter = BinaryOpCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { result, lhs, rhs } = self; - BinaryOpRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + BinaryOpCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct BinaryOpRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct BinaryOpCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for BinaryOpRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for BinaryOpCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for BinaryOp { - type IterMut = BinaryOpRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for BinaryOp { + type IterMut = BinaryOpCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { result, lhs, rhs } = self; - BinaryOpRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + BinaryOpCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct BinaryOpRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct BinaryOpCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for BinaryOpRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for BinaryOpCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap index e614b48cfc..78fbcaf0e8 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for BranchSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for Branch { - type Iter = BranchRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for Branch { + type Iter = BranchCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { target, args } = self; - BranchRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + BranchCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct BranchRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct BranchCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for BranchRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for BranchCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for Branch { - type IterMut = BranchRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for Branch { + type IterMut = BranchCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { target, args } = self; - BranchRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + BranchCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct BranchRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct BranchCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for BranchRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for BranchCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap index 6b337973be..2e8d2e4bf3 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap @@ -217,53 +217,53 @@ impl<'a> Iterator for ZxEvalSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for ZxEval { - type Iter = ZxEvalRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for ZxEval { + type Iter = ZxEvalCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self { boundary, captures, body, } = self; - ZxEvalRegionsIter { - inner: std::iter::empty::<&'a ::kirin::ir::Region>(), + ZxEvalCfgsIter { + inner: std::iter::empty::<&'a ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ZxEvalRegionsIter<'a> { - inner: std::iter::Empty<&'a ::kirin::ir::Region>, +pub struct ZxEvalCfgsIter<'a> { + inner: std::iter::Empty<&'a ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ZxEvalRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for ZxEvalCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for ZxEval { - type IterMut = ZxEvalRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for ZxEval { + type IterMut = ZxEvalCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self { boundary, captures, body, } = self; - ZxEvalRegionsMutIter { - inner: std::iter::empty::<&'a mut ::kirin::ir::Region>(), + ZxEvalCfgsMutIter { + inner: std::iter::empty::<&'a mut ::kirin::ir::Cfg>(), } } } #[automatically_derived] #[doc(hidden)] -pub struct ZxEvalRegionsMutIter<'a> { - inner: std::iter::Empty<&'a mut ::kirin::ir::Region>, +pub struct ZxEvalCfgsMutIter<'a> { + inner: std::iter::Empty<&'a mut ::kirin::ir::Cfg>, } #[automatically_derived] -impl<'a> Iterator for ZxEvalRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for ZxEvalCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap index d36f1bbea9..52d8f7de10 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for WrapperOpSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for WrapperOp { - type Iter = WrapperOpRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for WrapperOp { + type Iter = WrapperOpCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self(field_0) = self; - WrapperOpRegionsIter { - inner: ::regions(field_0), + WrapperOpCfgsIter { + inner: ::cfgs(field_0), } } } #[automatically_derived] #[doc(hidden)] -pub struct WrapperOpRegionsIter<'a> { - inner: >::Iter, +pub struct WrapperOpCfgsIter<'a> { + inner: >::Iter, } #[automatically_derived] -impl<'a> Iterator for WrapperOpRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for WrapperOpCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for WrapperOp { - type IterMut = WrapperOpRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for WrapperOp { + type IterMut = WrapperOpCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self(field_0) = self; - WrapperOpRegionsMutIter { - inner: ::regions_mut(field_0), + WrapperOpCfgsMutIter { + inner: ::cfgs_mut(field_0), } } } #[automatically_derived] #[doc(hidden)] -pub struct WrapperOpRegionsMutIter<'a> { - inner: >::IterMut, +pub struct WrapperOpCfgsMutIter<'a> { + inner: >::IterMut, } #[automatically_derived] -impl<'a> Iterator for WrapperOpRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for WrapperOpCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap index 998d5d3f78..8e36c4f73b 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for WrapperOpSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for WrapperOp { - type Iter = WrapperOpRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for WrapperOp { + type Iter = WrapperOpCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self(field_0) = self; - WrapperOpRegionsIter { - inner: ::regions(field_0), + WrapperOpCfgsIter { + inner: ::cfgs(field_0), } } } #[automatically_derived] #[doc(hidden)] -pub struct WrapperOpRegionsIter<'a> { - inner: >::Iter, +pub struct WrapperOpCfgsIter<'a> { + inner: >::Iter, } #[automatically_derived] -impl<'a> Iterator for WrapperOpRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for WrapperOpCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for WrapperOp { - type IterMut = WrapperOpRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for WrapperOp { + type IterMut = WrapperOpCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self(field_0) = self; - WrapperOpRegionsMutIter { - inner: ::regions_mut(field_0), + WrapperOpCfgsMutIter { + inner: ::cfgs_mut(field_0), } } } #[automatically_derived] #[doc(hidden)] -pub struct WrapperOpRegionsMutIter<'a> { - inner: >::IterMut, +pub struct WrapperOpCfgsMutIter<'a> { + inner: >::IterMut, } #[automatically_derived] -impl<'a> Iterator for WrapperOpRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for WrapperOpCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap index d36f1bbea9..52d8f7de10 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap @@ -179,45 +179,45 @@ impl<'a> Iterator for WrapperOpSuccessorsMutIter<'a> { } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegions<'a> for WrapperOp { - type Iter = WrapperOpRegionsIter<'a>; - fn regions(&'a self) -> Self::Iter { +impl<'a> ::kirin::ir::HasCfgs<'a> for WrapperOp { + type Iter = WrapperOpCfgsIter<'a>; + fn cfgs(&'a self) -> Self::Iter { let Self(field_0) = self; - WrapperOpRegionsIter { - inner: ::regions(field_0), + WrapperOpCfgsIter { + inner: ::cfgs(field_0), } } } #[automatically_derived] #[doc(hidden)] -pub struct WrapperOpRegionsIter<'a> { - inner: >::Iter, +pub struct WrapperOpCfgsIter<'a> { + inner: >::Iter, } #[automatically_derived] -impl<'a> Iterator for WrapperOpRegionsIter<'a> { - type Item = &'a ::kirin::ir::Region; +impl<'a> Iterator for WrapperOpCfgsIter<'a> { + type Item = &'a ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } } #[automatically_derived] -impl<'a> ::kirin::ir::HasRegionsMut<'a> for WrapperOp { - type IterMut = WrapperOpRegionsMutIter<'a>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> ::kirin::ir::HasCfgsMut<'a> for WrapperOp { + type IterMut = WrapperOpCfgsMutIter<'a>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { let Self(field_0) = self; - WrapperOpRegionsMutIter { - inner: ::regions_mut(field_0), + WrapperOpCfgsMutIter { + inner: ::cfgs_mut(field_0), } } } #[automatically_derived] #[doc(hidden)] -pub struct WrapperOpRegionsMutIter<'a> { - inner: >::IterMut, +pub struct WrapperOpCfgsMutIter<'a> { + inner: >::IterMut, } #[automatically_derived] -impl<'a> Iterator for WrapperOpRegionsMutIter<'a> { - type Item = &'a mut ::kirin::ir::Region; +impl<'a> Iterator for WrapperOpCfgsMutIter<'a> { + type Item = &'a mut ::kirin::ir::Cfg; fn next(&mut self) -> Option { self.inner.next() } diff --git a/crates/kirin-derive-ir/src/tests/standalone.rs b/crates/kirin-derive-ir/src/tests/standalone.rs index 91ef216c72..a94bf95e57 100644 --- a/crates/kirin-derive-ir/src/tests/standalone.rs +++ b/crates/kirin-derive-ir/src/tests/standalone.rs @@ -1,5 +1,5 @@ //! Snapshot tests for standalone single-trait derive macros -//! (HasArguments, HasResults, HasRegions, HasDigraphs, HasUngraphs, IsTerminator, IsEdge). +//! (HasArguments, HasResults, HasCfgs, HasDigraphs, HasUngraphs, IsTerminator, IsEdge). use crate::generate::*; use kirin_test_utils::rustfmt; @@ -46,14 +46,14 @@ fn test_standalone_has_results() { } #[test] -fn test_standalone_has_regions() { +fn test_standalone_has_cfgs() { let input: syn::DeriveInput = syn::parse_quote! { #[kirin(type = SimpleType)] struct Lambda { - body: Region, + body: Cfg, } }; - let tokens = generate_field_iter(&input, HAS_REGIONS).expect("Failed to generate HasRegions"); + let tokens = generate_field_iter(&input, HAS_CFGS).expect("Failed to generate HasCfgs"); insta::assert_snapshot!(rustfmt(tokens.to_string())); } diff --git a/crates/kirin-derive-toolkit/src/ir/fields/data.rs b/crates/kirin-derive-toolkit/src/ir/fields/data.rs index f917973034..2615a3f456 100644 --- a/crates/kirin-derive-toolkit/src/ir/fields/data.rs +++ b/crates/kirin-derive-toolkit/src/ir/fields/data.rs @@ -13,8 +13,8 @@ pub enum FieldCategory { Block, /// Control-flow successor (`Successor`). Successor, - /// Nested region (`Region` / `Region`). - Region, + /// Nested CFG (`Cfg` / `Cfg`). + Cfg, /// Directed graph body (`DiGraph`). DiGraph, /// Undirected graph body (`UnGraph`). @@ -54,7 +54,7 @@ pub enum FieldData { }, Block, Successor, - Region, + Cfg, DiGraph, UnGraph, Signature, @@ -82,7 +82,7 @@ impl Clone for FieldData { }, FieldData::Block => FieldData::Block, FieldData::Successor => FieldData::Successor, - FieldData::Region => FieldData::Region, + FieldData::Cfg => FieldData::Cfg, FieldData::DiGraph => FieldData::DiGraph, FieldData::UnGraph => FieldData::UnGraph, FieldData::Signature => FieldData::Signature, @@ -127,8 +127,8 @@ mod tests { } #[test] - fn field_category_is_ssa_like_region() { - assert!(!FieldCategory::Region.is_ssa_like()); + fn field_category_is_ssa_like_cfg() { + assert!(!FieldCategory::Cfg.is_ssa_like()); } #[test] diff --git a/crates/kirin-derive-toolkit/src/ir/fields/info.rs b/crates/kirin-derive-toolkit/src/ir/fields/info.rs index 801fa8081b..8fb50f4cda 100644 --- a/crates/kirin-derive-toolkit/src/ir/fields/info.rs +++ b/crates/kirin-derive-toolkit/src/ir/fields/info.rs @@ -47,7 +47,7 @@ impl FieldInfo { FieldData::Result { .. } => FieldCategory::Result, FieldData::Block => FieldCategory::Block, FieldData::Successor => FieldCategory::Successor, - FieldData::Region => FieldCategory::Region, + FieldData::Cfg => FieldCategory::Cfg, FieldData::DiGraph => FieldCategory::DiGraph, FieldData::UnGraph => FieldCategory::UnGraph, FieldData::Signature => FieldCategory::Signature, @@ -63,7 +63,7 @@ impl FieldInfo { FieldCategory::Result => "result", FieldCategory::Block => "block", FieldCategory::Successor => "successor", - FieldCategory::Region => "region", + FieldCategory::Cfg => "cfg", FieldCategory::DiGraph => "digraph", FieldCategory::UnGraph => "ungraph", FieldCategory::Signature => "signature", @@ -237,7 +237,7 @@ mod tests { ), (FieldData::Block, "block"), (FieldData::Successor, "successor"), - (FieldData::Region, "region"), + (FieldData::Cfg, "cfg"), (FieldData::Signature, "signature"), (FieldData::Symbol, "symbol"), ( diff --git a/crates/kirin-derive-toolkit/src/ir/fields/mod.rs b/crates/kirin-derive-toolkit/src/ir/fields/mod.rs index 3e7a6326d2..e06f3d1c38 100644 --- a/crates/kirin-derive-toolkit/src/ir/fields/mod.rs +++ b/crates/kirin-derive-toolkit/src/ir/fields/mod.rs @@ -8,7 +8,7 @@ //! | `ResultValue` / `ResultValue` | [`Result`](FieldCategory::Result) | SSA output value | //! | `Block` | [`Block`](FieldCategory::Block) | Basic block reference | //! | `Successor` | [`Successor`](FieldCategory::Successor) | Control-flow successor | -//! | `Region` / `Region` | [`Region`](FieldCategory::Region) | Nested region | +//! | `Cfg` / `Cfg` | [`Cfg`](FieldCategory::Cfg) | Nested CFG | //! | `Symbol` | [`Symbol`](FieldCategory::Symbol) | Symbol reference | //! | anything else | [`Value`](FieldCategory::Value) | Plain Rust value | //! diff --git a/crates/kirin-derive-toolkit/src/ir/statement/accessors.rs b/crates/kirin-derive-toolkit/src/ir/statement/accessors.rs index 55674d418d..1a75d3c197 100644 --- a/crates/kirin-derive-toolkit/src/ir/statement/accessors.rs +++ b/crates/kirin-derive-toolkit/src/ir/statement/accessors.rs @@ -37,11 +37,11 @@ impl Statement { .filter(|f| f.category() == FieldCategory::Successor) } - /// Iterates fields classified as [`FieldCategory::Region`]. - pub fn regions(&self) -> impl Iterator> { + /// Iterates fields classified as [`FieldCategory::Cfg`]. + pub fn cfgs(&self) -> impl Iterator> { self.fields .iter() - .filter(|f| f.category() == FieldCategory::Region) + .filter(|f| f.category() == FieldCategory::Cfg) } /// Iterates fields classified as [`FieldCategory::DiGraph`]. diff --git a/crates/kirin-derive-toolkit/src/ir/statement/definition.rs b/crates/kirin-derive-toolkit/src/ir/statement/definition.rs index dcbebd059d..5c61eeb11c 100644 --- a/crates/kirin-derive-toolkit/src/ir/statement/definition.rs +++ b/crates/kirin-derive-toolkit/src/ir/statement/definition.rs @@ -226,12 +226,12 @@ impl Statement { }); } - if let Some(collection) = Collection::from_type(ty, "Region") { + if let Some(collection) = Collection::from_type(ty, "Cfg") { return Ok(FieldInfo { index, ident, collection, - data: FieldData::Region, + data: FieldData::Cfg, }); } diff --git a/crates/kirin-derive-toolkit/src/parse_dispatch.rs b/crates/kirin-derive-toolkit/src/parse_dispatch.rs index 5f777ec2d4..4d829e6246 100644 --- a/crates/kirin-derive-toolkit/src/parse_dispatch.rs +++ b/crates/kirin-derive-toolkit/src/parse_dispatch.rs @@ -2,7 +2,7 @@ //! //! Generates a monomorphic [`ParseDispatch`] implementation that dispatches to //! concrete dialect parsers with concrete lifetimes, avoiding the HRTB bounds -//! that cause E0275 with `Block`/`Region`-containing types. +//! that cause E0275 with `Block`/`Cfg`-containing types. //! //! Reuses the same `#[stage(...)]` attribute parsing as `StageMeta`. diff --git a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs index 92d4031c43..1431604dea 100644 --- a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs +++ b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs @@ -101,7 +101,7 @@ fn build_fn_inputs(info: &StatementInfo, ir_type: &syn::Path) -> Vec { @@ -147,7 +147,7 @@ fn build_fn_let_inputs(info: &StatementInfo, ir_type: &syn::Path) -> Vec { @@ -166,7 +166,7 @@ fn field_type_for_category(collection: &Collection, category: FieldCategory) -> FieldCategory::Result => "ResultValue", FieldCategory::Block => "Block", FieldCategory::Successor => "Successor", - FieldCategory::Region => "Region", + FieldCategory::Cfg => "Cfg", FieldCategory::DiGraph => "DiGraph", FieldCategory::UnGraph => "UnGraph", FieldCategory::Symbol => "Symbol", diff --git a/crates/kirin-derive-toolkit/src/template/method_pattern/field_collection.rs b/crates/kirin-derive-toolkit/src/template/method_pattern/field_collection.rs index abd6e74790..d0117bc8bc 100644 --- a/crates/kirin-derive-toolkit/src/template/method_pattern/field_collection.rs +++ b/crates/kirin-derive-toolkit/src/template/method_pattern/field_collection.rs @@ -20,8 +20,8 @@ pub enum FieldIterKind { Blocks, /// Successor block references. Successors, - /// Nested region fields. - Regions, + /// Nested CFG fields. + Cfgs, /// Directed graph body fields. Digraphs, /// Undirected graph body fields. @@ -99,10 +99,7 @@ impl FieldCollection { .successors() .map(FieldAccess::from_field_info) .collect(), - FieldIterKind::Regions => statement - .regions() - .map(FieldAccess::from_field_info) - .collect(), + FieldIterKind::Cfgs => statement.cfgs().map(FieldAccess::from_field_info).collect(), FieldIterKind::Digraphs => statement .digraphs() .map(FieldAccess::from_field_info) diff --git a/crates/kirin-derive-toolkit/src/template/trait_impl.rs b/crates/kirin-derive-toolkit/src/template/trait_impl.rs index faea401484..b5770babb5 100644 --- a/crates/kirin-derive-toolkit/src/template/trait_impl.rs +++ b/crates/kirin-derive-toolkit/src/template/trait_impl.rs @@ -303,15 +303,15 @@ impl Template for MarkerTemplate { /// Configuration for a field iterator trait. pub struct FieldIterConfig { - /// Which field category to iterate over (e.g., regions, blocks, successors). + /// Which field category to iterate over (e.g., cfgs, blocks, successors). pub kind: FieldIterKind, /// Whether the iterator yields mutable references. pub mutable: bool, - /// Fully qualified trait name (e.g., `"HasRegions"`). + /// Fully qualified trait name (e.g., `"HasCfgs"`). pub trait_name: &'static str, - /// The IR type that fields must match (e.g., `"Region"`). + /// The IR type that fields must match (e.g., `"Cfg"`). pub matching_type: &'static str, - /// Method name on the trait (e.g., `"regions"`). + /// Method name on the trait (e.g., `"cfgs"`). pub trait_method: &'static str, /// Associated type name for the iterator (e.g., `"Iter"`). pub trait_type_iter: &'static str, diff --git a/crates/kirin-function/src/body.rs b/crates/kirin-function/src/body.rs index cc7d1104b6..f1ffd776fe 100644 --- a/crates/kirin-function/src/body.rs +++ b/crates/kirin-function/src/body.rs @@ -9,14 +9,14 @@ use kirin::prelude::*; #[kirin(builders, type = T)] #[chumsky(format = "fn {:name}{sig} {body}")] pub struct Function { - pub(crate) body: Region, + pub(crate) body: Cfg, pub(crate) sig: Signature, #[kirin(default)] marker: std::marker::PhantomData, } -impl HasRegionBody for Function { - fn region(&self) -> &Region { +impl HasCfgBody for Function { + fn cfg(&self) -> &Cfg { &self.body } } diff --git a/crates/kirin-function/src/call/tests.rs b/crates/kirin-function/src/call/tests.rs index f561208a50..c95c2ae22a 100644 --- a/crates/kirin-function/src/call/tests.rs +++ b/crates/kirin-function/src/call/tests.rs @@ -1,6 +1,6 @@ use super::*; use kirin::ir::{ - HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, + HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsSpeculatable, IsTerminator, TestSSAValue, }; use kirin_test_types::UnitType; @@ -51,8 +51,8 @@ fn no_blocks() { } #[test] -fn no_regions() { - assert_eq!(make_call(0).regions().count(), 0); +fn no_cfgs() { + assert_eq!(make_call(0).cfgs().count(), 0); } #[test] diff --git a/crates/kirin-function/src/interpreter.rs b/crates/kirin-function/src/interpreter.rs index 3e948a4533..05029a515f 100644 --- a/crates/kirin-function/src/interpreter.rs +++ b/crates/kirin-function/src/interpreter.rs @@ -1,4 +1,4 @@ -use kirin::prelude::{CompileTimeValue, HasBottom, HasRegionBody, Product, SSAValue}; +use kirin::prelude::{CompileTimeValue, HasBottom, HasCfgBody, Product, SSAValue}; use kirin_interpreter::dialect::{ CallEffect, Callee, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, ForwardEval, FunctionBody, FunctionEntry, Interp, Interpretable, InterpreterError, @@ -99,7 +99,7 @@ where args: Product, _interp: &mut I, ) -> Result, I::Error> { - Ok(FunctionBody::new(*self.region()).args(args)) + Ok(FunctionBody::new(*self.cfg()).args(args)) } } @@ -113,7 +113,7 @@ where args: Product, _interp: &mut I, ) -> Result, I::Error> { - Ok(FunctionBody::new(*self.region()).args(args)) + Ok(FunctionBody::new(*self.cfg()).args(args)) } } diff --git a/crates/kirin-function/src/lambda.rs b/crates/kirin-function/src/lambda.rs index 44c918de96..6bc865c02d 100644 --- a/crates/kirin-function/src/lambda.rs +++ b/crates/kirin-function/src/lambda.rs @@ -11,12 +11,12 @@ use kirin::prelude::*; /// a `sig: Signature` field. The reasons are: /// /// - **Parameters are implicit in block arguments.** A lambda's parameter types -/// are defined by the block arguments of its `body` region's entry block. +/// are defined by the block arguments of its `body` CFG's entry block. /// Duplicating them in a `Signature` would create a consistency hazard. /// /// - **Return type is already present.** The `res: ResultValue` field carries /// the lambda's return type, which is the only part of the signature that -/// cannot be recovered from the body region alone. +/// cannot be recovered from the body cfg alone. /// /// - **Captures are not part of the function type.** In PL theory, a closure's /// *function type* describes its parameter and return types, not its captured @@ -36,14 +36,14 @@ use kirin::prelude::*; pub struct Lambda { name: Symbol, captures: Vec, - pub(crate) body: Region, + pub(crate) body: Cfg, res: ResultValue, #[kirin(default)] marker: std::marker::PhantomData, } -impl HasRegionBody for Lambda { - fn region(&self) -> &Region { +impl HasCfgBody for Lambda { + fn cfg(&self) -> &Cfg { &self.body } } diff --git a/crates/kirin-function/src/ret.rs b/crates/kirin-function/src/ret.rs index f15eb0541c..11f5680a35 100644 --- a/crates/kirin-function/src/ret.rs +++ b/crates/kirin-function/src/ret.rs @@ -13,7 +13,7 @@ pub struct Return { mod tests { use super::*; use kirin::ir::{ - HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, + HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsSpeculatable, IsTerminator, TestSSAValue, }; use kirin_test_types::UnitType; @@ -78,8 +78,8 @@ mod tests { } #[test] - fn no_regions() { - assert_eq!(make_return().regions().count(), 0); + fn no_cfgs() { + assert_eq!(make_return().cfgs().count(), 0); } #[test] diff --git a/crates/kirin-interpreter/src/core/effect.rs b/crates/kirin-interpreter/src/core/effect.rs index 8a0f1a8114..fa33570af4 100644 --- a/crates/kirin-interpreter/src/core/effect.rs +++ b/crates/kirin-interpreter/src/core/effect.rs @@ -1,5 +1,5 @@ use kirin_ir::{ - Block, CompileStage, Function, Product, Region, SSAValue, SpecializedFunction, StagedFunction, + Block, Cfg, CompileStage, Function, Product, SSAValue, SpecializedFunction, StagedFunction, Symbol, }; @@ -24,7 +24,7 @@ use kirin_ir::{ pub enum SparseForwardEffect { /// Statement done; continue with the next statement. Next, - /// Unconditional transfer to a block in the current region. + /// Unconditional transfer to a block in the current CFG. Jump(Edge), /// Conditional transfer whose condition is undecided in the value domain. Branch(Vec>), @@ -86,27 +86,27 @@ pub enum Callee { Specialized(SpecializedFunction), } -/// The body a callable statement enters when invoked: a CFG region plus the +/// The body a callable statement enters when invoked: a CFG plus the /// entry arguments bound to its entry block. /// /// This is the function-call entry descriptor — the call mechanism, not a /// structured-control abstraction. A [`FunctionEntry`](crate::FunctionEntry) -/// rule returns one; the engine builds the body frame that walks the region. +/// rule returns one; the engine builds the body frame that walks the CFG. pub struct FunctionBody { - pub region: Region, + pub cfg: Cfg, pub args: Product, } impl FunctionBody { - /// A function body over `region`, with no entry arguments yet. - pub fn new(region: Region) -> Self { + /// A function body over `cfg`, with no entry arguments yet. + pub fn new(cfg: Cfg) -> Self { Self { - region, + cfg, args: Product::new(), } } - /// Entry arguments bound to the region entry block's parameters. + /// Entry arguments bound to the CFG entry block's parameters. pub fn args(mut self, args: impl IntoIterator) -> Self { self.args = args.into_iter().collect(); self diff --git a/crates/kirin-interpreter/src/core/error.rs b/crates/kirin-interpreter/src/core/error.rs index 24d2d26597..5211e87d26 100644 --- a/crates/kirin-interpreter/src/core/error.rs +++ b/crates/kirin-interpreter/src/core/error.rs @@ -39,8 +39,8 @@ pub enum InterpreterError { }, #[error("missing call target {0:?}")] MissingCallTarget(Symbol), - #[error("region has no entry block")] - EmptyRegion, + #[error("cfg has no entry block")] + EmptyCfg, #[error("block {0:?} fell through without a terminator effect")] BlockFellThrough(Block), #[error("function body fell through without returning")] diff --git a/crates/kirin-interpreter/src/core/frame.rs b/crates/kirin-interpreter/src/core/frame.rs index 2f6de5ed8e..688eefe8fb 100644 --- a/crates/kirin-interpreter/src/core/frame.rs +++ b/crates/kirin-interpreter/src/core/frame.rs @@ -7,7 +7,7 @@ use std::hash::Hash; -use kirin_ir::{Block, CompileStage, Product, Region, SSAValue, Statement}; +use kirin_ir::{Block, Cfg, CompileStage, Product, SSAValue, Statement}; use crate::{ CallEffect, Callee, Env, EnvIndex, FunctionBody, FunctionTarget, Interp, InterpreterError, @@ -139,11 +139,7 @@ pub trait ForwardFrameDriver: Env { block: Block, after: Statement, ) -> Result, Self::Error>; - fn region_entry( - &self, - stage: CompileStage, - region: Region, - ) -> Result, Self::Error>; + fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, Self::Error>; /// Bind a block's parameters to incoming actuals in `env` (arity-checked). fn bind_block_args( diff --git a/crates/kirin-interpreter/src/core/query.rs b/crates/kirin-interpreter/src/core/query.rs index e54ee3e14d..d871a2c0ca 100644 --- a/crates/kirin-interpreter/src/core/query.rs +++ b/crates/kirin-interpreter/src/core/query.rs @@ -1,20 +1,20 @@ //! Engine-internal IR queries over stage enums. //! //! Engines need a handful of language-independent facts (block parameters, -//! statement order, region entry, function specialization) from typed +//! statement order, CFG entry, function specialization) from typed //! `StageInfo` values. Each query is a [`StageAction`] dispatched through //! kirin-ir's `StageDispatch` machinery; [`StageQuery`] bundles them into one //! bound that any well-formed stage enum satisfies automatically. use kirin_ir::{ - Block, CompileStage, Dialect, GetInfo, HasArguments, HasBlocks, HasRegions, HasStageInfo, - HasSuccessors, Pipeline, Region, SSAKind, SSAValue, SpecializedFunction, StageAction, - StageInfo, StageMeta, StagedFunction, Statement, SupportsStageDispatch, Symbol, + Block, Cfg, CompileStage, Dialect, GetInfo, HasArguments, HasBlocks, HasCfgs, HasStageInfo, + HasSuccessors, Pipeline, SSAKind, SSAValue, SpecializedFunction, StageAction, StageInfo, + StageMeta, StagedFunction, Statement, SupportsStageDispatch, Symbol, UniqueLiveSpecializationError, }; use crate::InterpreterError; -use crate::facts::topology::{self, RegionTopology}; +use crate::facts::topology::{self, CfgTopology}; /// Block parameters as SSA values. pub struct BlockParams(pub Block); @@ -95,10 +95,10 @@ where } } -/// Entry block of a region. -pub struct RegionEntry(pub Region); +/// Entry block of a CFG. +pub struct CfgEntry(pub Cfg); -impl StageAction for RegionEntry +impl StageAction for CfgEntry where S: StageMeta + HasStageInfo, L: Dialect, @@ -230,17 +230,17 @@ where } } -/// The topology of a region: blocks (including nested structured bodies), +/// The topology of a CFG: blocks (including nested structured bodies), /// statements per block, CFG successors, and block feeders. -pub struct RegionTopologyQuery(pub Region); +pub struct CfgTopologyQuery(pub Cfg); -impl StageAction for RegionTopologyQuery +impl StageAction for CfgTopologyQuery where S: StageMeta + HasStageInfo, L: Dialect, - for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasRegions<'a>, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a>, { - type Output = RegionTopology; + type Output = CfgTopology; type Error = InterpreterError; fn run( @@ -248,7 +248,7 @@ where _stage: CompileStage, info: &StageInfo, ) -> Result { - Ok(topology::region_topology(info, &self.0)) + Ok(topology::cfg_topology(info, &self.0)) } } @@ -282,7 +282,7 @@ pub trait StageQuery: + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> - + SupportsStageDispatch, InterpreterError> + + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch< UniqueSpecialization, Result, @@ -291,7 +291,7 @@ pub trait StageQuery: + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch, InterpreterError> - + SupportsStageDispatch + + SupportsStageDispatch { } @@ -300,7 +300,7 @@ impl StageQuery for S where + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> - + SupportsStageDispatch, InterpreterError> + + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch< UniqueSpecialization, Result, @@ -309,7 +309,7 @@ impl StageQuery for S where + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch, InterpreterError> - + SupportsStageDispatch + + SupportsStageDispatch { } @@ -354,12 +354,12 @@ pub(crate) fn next_statement( dispatch(pipeline, stage, NextStatement { block, after }) } -pub(crate) fn region_entry( +pub(crate) fn cfg_entry( pipeline: &Pipeline, stage: CompileStage, - region: Region, + cfg: Cfg, ) -> Result, InterpreterError> { - dispatch(pipeline, stage, RegionEntry(region)) + dispatch(pipeline, stage, CfgEntry(cfg)) } pub(crate) fn unique_specialization( @@ -402,10 +402,10 @@ pub(crate) fn terminator_arguments( dispatch(pipeline, stage, TerminatorArguments(block)) } -pub(crate) fn region_topology( +pub(crate) fn cfg_topology( pipeline: &Pipeline, stage: CompileStage, - region: Region, -) -> Result { - dispatch(pipeline, stage, RegionTopologyQuery(region)) + cfg: Cfg, +) -> Result { + dispatch(pipeline, stage, CfgTopologyQuery(cfg)) } diff --git a/crates/kirin-interpreter/src/engines/concrete/frames.rs b/crates/kirin-interpreter/src/engines/concrete/frames.rs index ec6b87220a..1972bd4b46 100644 --- a/crates/kirin-interpreter/src/engines/concrete/frames.rs +++ b/crates/kirin-interpreter/src/engines/concrete/frames.rs @@ -2,7 +2,7 @@ //! protocol. //! //! These are the default total frames for [`ConcreteInterpreter`](crate::ConcreteInterpreter): -//! [`BodyFrame`] (walks a function-body CFG region or a single body block) and +//! [`BodyFrame`] (walks a function-body CFG or a single body block) and //! [`CallFrame`] (call/return). They implement the shared [`Frame`] trait by //! consuming the dialect [`SparseForwardEffect`] and driving a single deterministic //! path. Structured-control dialects do not get a framework "scope": they push @@ -13,7 +13,7 @@ //! via [`FrameBuild`] plus its dialect frames. The forward abstract analogue //! lives in [`sparse_forward::frames`](crate::engines::sparse_forward::frames). -use kirin_ir::{Block, CompileStage, Product, Region, SSAValue, Statement}; +use kirin_ir::{Block, Cfg, CompileStage, Product, SSAValue, Statement}; use crate::{ CallEffect, Callee, EnvIndex, Frame, FrameDriver, FrameEffect, InterpreterError, @@ -44,7 +44,7 @@ pub trait FrameBuild: Sized { fn from_call(frame: CallFrame) -> Self; } -/// Traversal of one body: a function-body CFG region (multi-block, with jumps) +/// Traversal of one body: a function-body CFG (multi-block, with jumps) /// or a single body block (scf-style, terminated by a yield). pub struct BodyFrame { stage: CompileStage, @@ -68,21 +68,21 @@ where V: Clone, E: From, { - /// Walk a function body: start at the entry block of `region`, binding + /// Walk a function body: start at the entry block of `cfg`, binding /// `args` to its parameters. Owns the activation and is the return boundary. pub fn function( interp: &mut I, stage: CompileStage, index: EnvIndex, - region: Region, + cfg: Cfg, args: Product, ) -> Result where I: FrameDriver, { let entry = interp - .region_entry(stage, region)? - .ok_or_else(|| E::from(InterpreterError::EmptyRegion))?; + .cfg_entry(stage, cfg)? + .ok_or_else(|| E::from(InterpreterError::EmptyCfg))?; Self::start(interp, stage, index, entry, args, true, true) } @@ -289,8 +289,7 @@ where let target = interp.resolve_call(resolve_stage, &callee)?; let index = interp.alloc_env(); let body = interp.enter_function(target.stage, target.body, args, index)?; - let frame = - BodyFrame::function(interp, target.stage, index, body.region, body.args)?; + let frame = BodyFrame::function(interp, target.stage, index, body.cfg, body.args)?; Ok(FrameEffect::Push { parent: F::from_call(CallFrame::Awaiting { caller_env, diff --git a/crates/kirin-interpreter/src/engines/concrete/interp.rs b/crates/kirin-interpreter/src/engines/concrete/interp.rs index 1c36f5cfa9..6c7665ac06 100644 --- a/crates/kirin-interpreter/src/engines/concrete/interp.rs +++ b/crates/kirin-interpreter/src/engines/concrete/interp.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use kirin_ir::{Block, CompileStage, Pipeline, Product, Region, SSAValue, StageMeta, Statement}; +use kirin_ir::{Block, Cfg, CompileStage, Pipeline, Product, SSAValue, StageMeta, Statement}; use crate::core::query; use crate::{ @@ -191,8 +191,8 @@ where query::next_statement(self.pipeline, stage, block, after).map_err(E::from) } - fn region_entry(&self, stage: CompileStage, region: Region) -> Result, E> { - query::region_entry(self.pipeline, stage, region).map_err(E::from) + fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, E> { + query::cfg_entry(self.pipeline, stage, cfg).map_err(E::from) } } @@ -233,7 +233,7 @@ where let index = self.alloc_env(); let args: Product = args.into_iter().collect(); let body = self.enter_function(target.stage, target.body, args, index)?; - let frame = BodyFrame::function(self, target.stage, index, body.region, body.args)?; + let frame = BodyFrame::function(self, target.stage, index, body.cfg, body.args)?; self.frames.push(F::from_body(frame)); self.run() } diff --git a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs index e908b73680..823ff776bd 100644 --- a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs @@ -46,17 +46,17 @@ use std::marker::PhantomData; use kirin_ir::{ - Block, CompileStage, HasArguments, HasBottom, HasResults, Lattice, Pipeline, Region, SSAValue, + Block, Cfg, CompileStage, HasArguments, HasBottom, HasResults, Lattice, Pipeline, SSAValue, StageMeta, Statement, }; use super::frames::{DenseBlockFrame, DenseFrameBuild}; use crate::core::query; -use crate::engines::sparse_backward::RegionScope; +use crate::engines::sparse_backward::CfgScope; use crate::{ - AbstractInterpreter, BackwardSummaryDeps, ClassicLiveness, DenseBackwardSemantic, + AbstractInterpreter, BackwardSummaryDeps, CfgTopology, ClassicLiveness, DenseBackwardSemantic, DensePointStore, EnvIndex, FixpointProfile, Frame, Interp, InterpDispatch, InterpLocation, - InterpreterError, OwnerSemantics, ProgramPoint, RegionTopology, Scoped, StageQuery, + InterpreterError, OwnerSemantics, ProgramPoint, Scoped, StageQuery, StandardFixpointInterpreter, Summary, SummaryDependency, SummaryDependencyIndex, SummaryEffect, }; @@ -321,7 +321,7 @@ where E: From, Sem: DenseBackwardSemantic, { - type SummaryKey = Scoped; + type SummaryKey = Scoped; type Summary = BlockLiveness; type Frame = F; type Completion = DenseBackwardCompletion; @@ -337,11 +337,11 @@ pub enum DenseBackwardCompletion { } /// Analysis-local state carried in the driver's `store` slot: the scope, -/// the region topology, and an optional per-point recorder filled by the +/// the CFG topology, and an optional per-point recorder filled by the /// block frames during [`reconstruct_points`](DenseBackwardInterpreter::reconstruct_points). pub struct DenseAnalysisState { - scope: Option, - topology: RegionTopology, + scope: Option, + topology: CfgTopology, recorder: Option>, } @@ -349,7 +349,7 @@ impl Default for DenseAnalysisState { fn default() -> Self { Self { scope: None, - topology: RegionTopology::default(), + topology: CfgTopology::default(), recorder: None, } } @@ -362,7 +362,7 @@ pub type DenseBackwardDriver<'ir, S, V, E, F, Sem = ClassicLiveness> = StandardF DenseBackwardTransfer<'ir, S, V, E, F, Sem>, DenseBackwardProfile, DenseAnalysisState, - BackwardSummaryDeps>, + BackwardSummaryDeps>, >; // =========================================================================== @@ -548,7 +548,7 @@ struct DenseBackwardSemantics; impl<'ir, S, V, E, F, Sem> OwnerSemantics< DenseBackwardDriver<'ir, S, V, E, F, Sem>, - Scoped, + Scoped, BlockLiveness, F, DenseBackwardCompletion, @@ -564,7 +564,7 @@ where fn bottom_summary( &mut self, _interp: &mut DenseBackwardDriver<'ir, S, V, E, F, Sem>, - _owner: &Scoped, + _owner: &Scoped, ) -> Result, E> { Ok(BlockLiveness::bottom()) } @@ -572,10 +572,10 @@ where fn entry_frame( &mut self, interp: &mut DenseBackwardDriver<'ir, S, V, E, F, Sem>, - owner: &Scoped, + owner: &Scoped, _summary: &BlockLiveness, ) -> Result { - let (stage, _region) = owner.scope; + let (stage, _cfg) = owner.scope; // Each owner walk starts from an empty exit state; the terminator's // absorbed edges seed the real live-out. interp.replace_state(V::bottom()); @@ -585,9 +585,9 @@ where fn complete_owner( &mut self, _interp: &mut DenseBackwardDriver<'ir, S, V, E, F, Sem>, - owner: Scoped, + owner: Scoped, completion: DenseBackwardCompletion, - ) -> Result, BlockLiveness>, E> { + ) -> Result, BlockLiveness>, E> { match completion { DenseBackwardCompletion::Block { live_in, live_out } => Ok(SummaryEffect::Update { owner, @@ -608,8 +608,8 @@ where /// /// ```ignore /// let mut analysis = DenseBackwardInterpreter::::new(&pipeline); -/// analysis.analyze(stage, region)?; -/// let boundary = analysis.block_summary(stage, region, block); +/// analysis.analyze(stage, cfg)?; +/// let boundary = analysis.block_summary(stage, cfg, block); /// ``` pub struct DenseBackwardInterpreter< 'ir, @@ -649,18 +649,18 @@ where self.driver.inner().pipeline() } - /// The converged boundary states of `block` under the `(stage, region)` + /// The converged boundary states of `block` under the `(stage, cfg)` /// scope. pub fn block_summary( &self, stage: CompileStage, - region: Region, + cfg: Cfg, block: Block, ) -> Option<&BlockLiveness> { - self.driver.summary(&Scoped::new((stage, region), block)) + self.driver.summary(&Scoped::new((stage, cfg), block)) } - /// The analyzed region's CFG blocks (post-`analyze`). + /// The analyzed CFG's own top-level blocks (post-`analyze`). pub fn cfg_blocks(&self) -> Vec { self.driver .store() @@ -680,13 +680,13 @@ where F: Frame, Completion = DenseBackwardCompletion> + DenseFrameBuild, { - /// Run the block-boundary fixpoint over `region` in `stage`: seed every + /// Run the block-boundary fixpoint over `cfg` in `stage`: seed every /// CFG block (a backward analysis must visit them all) and drain the /// worklist; dependencies are discovered from the terminators' edges. - pub fn analyze(&mut self, stage: CompileStage, region: Region) -> Result<(), E> { - let scope = (stage, region); - let topology = query::region_topology(self.driver.inner().pipeline(), stage, region)?; - let owners: Vec> = topology + pub fn analyze(&mut self, stage: CompileStage, cfg: Cfg) -> Result<(), E> { + let scope = (stage, cfg); + let topology = query::cfg_topology(self.driver.inner().pipeline(), stage, cfg)?; + let owners: Vec> = topology .cfg_blocks() .map(|block| Scoped::new(scope, block.block)) .collect(); @@ -708,9 +708,9 @@ where pub fn reconstruct_points( &mut self, stage: CompileStage, - region: Region, + cfg: Cfg, ) -> Result, E> { - let scope = (stage, region); + let scope = (stage, cfg); self.driver.store_mut().recorder = Some(DensePointStore::new()); for block in self.cfg_blocks() { // The CfgOwner walk re-absorbs the converged successor summaries, diff --git a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs index 63b13fff5a..e5851b7b97 100644 --- a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs @@ -20,11 +20,11 @@ //! the real dispatch location, and the per-rule demand buffer. //! - the **[`StandardFixpointInterpreter`]** driver owns the demand facts //! (summaries keyed by [`Scoped`] SSA values — never bare values), the value -//! worklist, and the analysis state (scope + region topology). +//! worklist, and the analysis state (scope + CFG topology). //! //! # Owners are values; scheduling is demand propagation //! -//! `SummaryKey = Scoped<(CompileStage, Region), SSAValue>`: the fact anchor is +//! `SummaryKey = Scoped<(CompileStage, Cfg), SSAValue>`: the fact anchor is //! the owner. The driver's *default* self-dependent index //! ([`OwnerSummaryDeps`]) is exactly demand propagation — a value whose fact //! rises is rescheduled, and analyzing a value means dispatching the rules @@ -33,7 +33,7 @@ //! - a statement **result** → the defining statement's backward rule; //! - a **block argument** → each of the block's *feeders* (terminators //! targeting the block, statements owning it as a structured body) from the -//! [`RegionTopology`]; +//! [`CfgTopology`]; //! - a graph **port** → unsupported (loud error). //! //! Rules read converged facts ([`DemandInterp::is_demanded`]) and raise new @@ -49,23 +49,23 @@ use std::marker::PhantomData; use std::mem; use kirin_ir::{ - Block, CompileStage, HasArguments, HasBottom, HasResults, HasTop, IsPure, Lattice, Pipeline, - Region, SSAKind, SSAValue, StageMeta, Statement, + Block, Cfg, CompileStage, HasArguments, HasBottom, HasResults, HasTop, IsPure, Lattice, + Pipeline, SSAKind, SSAValue, StageMeta, Statement, }; use crate::core::query; use crate::{ - AbstractInterpreter, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, InterpDispatch, - InterpLocation, InterpreterError, OwnerSemantics, OwnerSummaryDeps, RegionTopology, Scoped, + AbstractInterpreter, CfgTopology, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, + InterpDispatch, InterpLocation, InterpreterError, OwnerSemantics, OwnerSummaryDeps, Scoped, SparseBackwardSemantic, SparseStore, StageQuery, StandardFixpointInterpreter, StrongDemand, Summary, SummaryEffect, }; -/// The scope a region-level backward analysis qualifies its facts with. +/// The scope a CFG-level backward analysis qualifies its facts with. /// /// Arena ids are per-stage, so the stage is part of the scope; analyzing two -/// regions in one engine keeps their facts distinct. -pub type RegionScope = (CompileStage, Region); +/// cfgs in one engine keeps their facts distinct. +pub type CfgScope = (CompileStage, Cfg); // =========================================================================== // Effect + dialect-facing trait @@ -275,18 +275,18 @@ where E: From, Sem: SparseBackwardSemantic, { - type SummaryKey = Scoped; + type SummaryKey = Scoped; type Summary = DemandSummary; type Frame = DemandFrame; type Completion = Vec<(SSAValue, V)>; } /// Analysis-local state carried in the driver's `store` slot: the scope facts -/// are qualified with, and the region topology (feeders for block arguments). +/// are qualified with, and the CFG topology (feeders for block arguments). #[derive(Default)] pub struct BackwardAnalysisState { - scope: Option, - topology: RegionTopology, + scope: Option, + topology: CfgTopology, } /// The sparse backward driver: a [`StandardFixpointInterpreter`] over @@ -296,7 +296,7 @@ pub type SparseBackwardDriver<'ir, S, V, E, Sem = StrongDemand> = StandardFixpoi SparseBackwardTransfer<'ir, S, V, E, Sem>, SparseBackwardProfile, BackwardAnalysisState, - OwnerSummaryDeps>, + OwnerSummaryDeps>, >; // =========================================================================== @@ -444,7 +444,7 @@ struct SparseBackwardSemantics; impl<'ir, S, V, E, Sem> OwnerSemantics< SparseBackwardDriver<'ir, S, V, E, Sem>, - Scoped, + Scoped, DemandSummary, DemandFrame, Vec<(SSAValue, V)>, @@ -459,7 +459,7 @@ where fn bottom_summary( &mut self, _interp: &mut SparseBackwardDriver<'ir, S, V, E, Sem>, - _owner: &Scoped, + _owner: &Scoped, ) -> Result, E> { Ok(DemandSummary(V::bottom())) } @@ -467,10 +467,10 @@ where fn entry_frame( &mut self, interp: &mut SparseBackwardDriver<'ir, S, V, E, Sem>, - owner: &Scoped, + owner: &Scoped, _summary: &DemandSummary, ) -> Result, E> { - let (stage, _region) = owner.scope; + let (stage, _cfg) = owner.scope; let kind = query::value_kind(interp.inner().pipeline(), stage, owner.item)?; let work = match kind { SSAKind::Result(statement, _) => vec![statement], @@ -487,9 +487,9 @@ where fn complete_owner( &mut self, _interp: &mut SparseBackwardDriver<'ir, S, V, E, Sem>, - owner: Scoped, + owner: Scoped, completion: Vec<(SSAValue, V)>, - ) -> Result, DemandSummary>, E> { + ) -> Result, DemandSummary>, E> { // Scope-qualify the bare values the rules demanded. Ok(SummaryEffect::Many( completion @@ -508,8 +508,8 @@ where /// /// ```ignore /// let mut analysis = SparseBackwardInterpreter::::new(&pipeline); -/// analysis.analyze(stage, region)?; -/// let demanded = analysis.is_demanded(stage, region, value); +/// analysis.analyze(stage, cfg)?; +/// let demanded = analysis.is_demanded(stage, cfg, value); /// ``` pub struct SparseBackwardInterpreter<'ir, S: StageMeta, V, E = InterpreterError, Sem = StrongDemand> where @@ -542,25 +542,16 @@ where self.driver.inner().pipeline() } - /// The converged demand fact for `value` under the `(stage, region)` scope. - pub fn fact( - &self, - stage: CompileStage, - region: Region, - value: impl Into, - ) -> Option<&V> { + /// The converged demand fact for `value` under the `(stage, cfg)` scope. + pub fn fact(&self, stage: CompileStage, cfg: Cfg, value: impl Into) -> Option<&V> { self.driver - .summary(&Scoped::new((stage, region), value.into())) + .summary(&Scoped::new((stage, cfg), value.into())) .map(|summary| &summary.0) } - /// All converged `(value, fact)` pairs under the `(stage, region)` scope. - pub fn facts( - &self, - stage: CompileStage, - region: Region, - ) -> impl Iterator { - let scope = (stage, region); + /// All converged `(value, fact)` pairs under the `(stage, cfg)` scope. + pub fn facts(&self, stage: CompileStage, cfg: Cfg) -> impl Iterator { + let scope = (stage, cfg); self.driver .summaries() .iter() @@ -568,11 +559,11 @@ where .map(|(owner, summary)| (owner.item, &summary.0)) } - /// The converged facts under the `(stage, region)` scope as a + /// The converged facts under the `(stage, cfg)` scope as a /// [`SparseStore`] (the sparse per-SSA-value fact view; absent = bottom). - pub fn fact_store(&self, stage: CompileStage, region: Region) -> SparseStore { + pub fn fact_store(&self, stage: CompileStage, cfg: Cfg) -> SparseStore { let mut store = SparseStore::new(); - for (value, fact) in self.facts(stage, region) { + for (value, fact) in self.facts(stage, cfg) { store.set(value, fact.clone()); } store @@ -586,16 +577,16 @@ where E: From, Sem: SparseBackwardSemantic, { - /// Run the demand fixpoint over `region` in `stage`. + /// Run the demand fixpoint over `cfg` in `stage`. /// - /// **Prepass**: enumerate the region topology (blocks including structured + /// **Prepass**: enumerate the CFG topology (blocks including structured /// bodies, statements, feeders), then run every statement's rule once with /// nothing demanded — impure statements and terminators contribute the /// demand roots. **Propagation**: drain the value worklist; each risen /// value dispatches the rules that translate its demand. - pub fn analyze(&mut self, stage: CompileStage, region: Region) -> Result<(), E> { - let scope = (stage, region); - let topology = query::region_topology(self.driver.inner().pipeline(), stage, region)?; + pub fn analyze(&mut self, stage: CompileStage, cfg: Cfg) -> Result<(), E> { + let scope = (stage, cfg); + let topology = query::cfg_topology(self.driver.inner().pipeline(), stage, cfg)?; let statements: Vec = topology .blocks .iter() @@ -628,16 +619,11 @@ where } /// `true` iff `value` carries a non-bottom demand fact under the scope. - pub fn is_demanded( - &self, - stage: CompileStage, - region: Region, - value: impl Into, - ) -> bool + pub fn is_demanded(&self, stage: CompileStage, cfg: Cfg, value: impl Into) -> bool where V: HasBottom, { - self.fact(stage, region, value) + self.fact(stage, cfg, value) .is_some_and(|fact| *fact != V::bottom()) } } diff --git a/crates/kirin-interpreter/src/engines/sparse_backward/mod.rs b/crates/kirin-interpreter/src/engines/sparse_backward/mod.rs index b358fb66d1..eb2afc6176 100644 --- a/crates/kirin-interpreter/src/engines/sparse_backward/mod.rs +++ b/crates/kirin-interpreter/src/engines/sparse_backward/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod interp; pub use interp::{ - BackwardAnalysisState, DemandFrame, DemandInterp, DemandSummary, RegionScope, + BackwardAnalysisState, CfgScope, DemandFrame, DemandInterp, DemandSummary, SparseBackwardDriver, SparseBackwardEffect, SparseBackwardInterp, SparseBackwardInterpreter, SparseBackwardProfile, SparseBackwardTransfer, }; diff --git a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs index 785ec4b010..f97af1e723 100644 --- a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs @@ -33,7 +33,7 @@ use std::hash::Hash; use std::marker::PhantomData; use kirin_ir::{ - Block, CompileStage, HasBottom, Pipeline, Product, Region, SSAValue, SpecializedFunction, + Block, Cfg, CompileStage, HasBottom, Pipeline, Product, SSAValue, SpecializedFunction, StageMeta, Statement, Widen, }; @@ -684,8 +684,8 @@ where query::next_statement(self.pipeline, stage, block, after).map_err(E::from) } - fn region_entry(&self, stage: CompileStage, region: Region) -> Result, E> { - query::region_entry(self.pipeline, stage, region).map_err(E::from) + fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, E> { + query::cfg_entry(self.pipeline, stage, cfg).map_err(E::from) } } @@ -750,8 +750,8 @@ where self.inner().next_statement(stage, block, after) } - fn region_entry(&self, stage: CompileStage, region: Region) -> Result, E> { - self.inner().region_entry(stage, region) + fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, E> { + self.inner().cfg_entry(stage, cfg) } } @@ -1052,8 +1052,8 @@ where .expect("function summary present"); let body_info = self.enter_function(stage, body, entry_args, env)?; let entry_block = self - .region_entry(stage, body_info.region)? - .ok_or_else(|| E::from(InterpreterError::EmptyRegion))?; + .cfg_entry(stage, body_info.cfg)? + .ok_or_else(|| E::from(InterpreterError::EmptyCfg))?; if let Some(function) = self .summary_mut(&Owner::Function(key.clone())) .and_then(|info| info.as_function_mut()) diff --git a/crates/kirin-interpreter/src/facts/anchor.rs b/crates/kirin-interpreter/src/facts/anchor.rs index 7fe407220e..6d40d05472 100644 --- a/crates/kirin-interpreter/src/facts/anchor.rs +++ b/crates/kirin-interpreter/src/facts/anchor.rs @@ -71,9 +71,9 @@ impl LatticeAnchor for DenseAnchor {} /// An anchor or owner qualified by the scope/context it belongs to. /// /// Framework-level summary keys are never bare anchors: the same [`SSAValue`] -/// or [`Block`] under two scopes (two stages, two analyzed regions, two call -/// contexts) is two distinct facts, so keys carry their scope. Region-level -/// analyses use `(CompileStage, Region)` as the scope; interprocedural +/// or [`Block`] under two scopes (two stages, two analyzed cfgs, two call +/// contexts) is two distinct facts, so keys carry their scope. Cfg-level +/// analyses use `(CompileStage, Cfg)` as the scope; interprocedural /// analyses generalize `K` to a call-context key (the backward analogue of the /// forward engine's context-qualified value keys). #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/crates/kirin-interpreter/src/facts/mod.rs b/crates/kirin-interpreter/src/facts/mod.rs index 706ed4d8a5..abb02d8cca 100644 --- a/crates/kirin-interpreter/src/facts/mod.rs +++ b/crates/kirin-interpreter/src/facts/mod.rs @@ -1,5 +1,5 @@ //! Dataflow fact vocabulary: anchors (*where* facts attach), the polymorphic -//! fact stores, and region topology enumeration. Fixpoint clients use these, +//! fact stores, and CFG topology enumeration. Fixpoint clients use these, //! but they are dataflow vocabulary, not the convergence driver itself. pub(crate) mod anchor; @@ -8,4 +8,4 @@ pub(crate) mod topology; pub use anchor::{Change, DenseAnchor, LatticeAnchor, ProgramPoint, Scoped}; pub use store::{DenseBlockStore, DensePointStore, FactStore, ScopedSparseStore, SparseStore}; -pub use topology::{BlockTopology, RegionTopology, region_topology}; +pub use topology::{BlockTopology, CfgTopology, cfg_topology}; diff --git a/crates/kirin-interpreter/src/facts/store.rs b/crates/kirin-interpreter/src/facts/store.rs index 7f3d530850..f2e688aa8f 100644 --- a/crates/kirin-interpreter/src/facts/store.rs +++ b/crates/kirin-interpreter/src/facts/store.rs @@ -170,7 +170,7 @@ impl DenseBlockStore { #[cfg(test)] mod tests { - use kirin_ir::{Block, CompileStage, Id, Region, Statement, TestSSAValue}; + use kirin_ir::{Block, Cfg, CompileStage, Id, Statement, TestSSAValue}; use super::*; @@ -205,14 +205,14 @@ mod tests { #[test] fn scoped_anchors_keep_scopes_distinct() { - type Scope = (CompileStage, Region); + type Scope = (CompileStage, Cfg); let scope_a: Scope = ( CompileStage::from(Id::from(ssa(100))), - Region::from(Id::from(ssa(200))), + Cfg::from(Id::from(ssa(200))), ); let scope_b: Scope = ( CompileStage::from(Id::from(ssa(101))), - Region::from(Id::from(ssa(200))), + Cfg::from(Id::from(ssa(200))), ); let mut store: ScopedSparseStore = FactStore::new(); diff --git a/crates/kirin-interpreter/src/facts/topology.rs b/crates/kirin-interpreter/src/facts/topology.rs index 4abab5e5e8..d56675da6f 100644 --- a/crates/kirin-interpreter/src/facts/topology.rs +++ b/crates/kirin-interpreter/src/facts/topology.rs @@ -1,20 +1,18 @@ -//! Dialect-neutral region topology enumeration. +//! Dialect-neutral CFG topology enumeration. //! -//! Backward analyses need the *shape* of a region: which blocks exist +//! Backward analyses need the *shape* of a CFG: which blocks exist //! (including blocks nested inside structured statements), each block's //! statements, the CFG successor relation, and each block's *feeders* — the //! statements whose rules can translate demand on that block's parameters //! (terminators targeting it, statements owning it). This is topology only — //! uses/defs/edge-argument *semantics* stay in dialect //! [`Interpretable`](crate::Interpretable) rules; the enumeration consumes the -//! generic [`HasSuccessors`]/[`HasBlocks`]/[`HasRegions`] contract every +//! generic [`HasSuccessors`]/[`HasBlocks`]/[`HasCfgs`] contract every //! dialect derives. use std::collections::{HashMap, HashSet}; -use kirin_ir::{ - Block, Dialect, HasBlocks, HasRegions, HasSuccessors, Region, StageInfo, Statement, -}; +use kirin_ir::{Block, Cfg, Dialect, HasBlocks, HasCfgs, HasSuccessors, StageInfo, Statement}; /// The shape of one block: its statements and CFG successors. #[derive(Clone, Debug)] @@ -25,19 +23,19 @@ pub struct BlockTopology { /// CFG successor blocks (targets of the block's terminator). pub successors: Vec, /// `true` for blocks nested inside a statement (structured bodies), - /// `false` for the analyzed region's own CFG blocks. + /// `false` for the analyzed CFG's own top-level blocks. pub nested: bool, } -/// The shape of a region: all blocks (region CFG blocks first-level and +/// The shape of a CFG: all blocks (the CFG's own top-level blocks and /// structured bodies, recursively) plus the block-feeder index. #[derive(Clone, Debug, Default)] -pub struct RegionTopology { +pub struct CfgTopology { pub blocks: Vec, feeders: HashMap>, } -impl RegionTopology { +impl CfgTopology { /// The statements whose rules can translate demand on `block`'s parameters: /// terminators with an edge into `block`, plus statements owning `block` /// as a structured body. @@ -45,21 +43,21 @@ impl RegionTopology { self.feeders.get(&block).map(Vec::as_slice).unwrap_or(&[]) } - /// The analyzed region's own CFG blocks (excluding nested bodies). + /// The analyzed CFG's own top-level blocks (excluding nested bodies). pub fn cfg_blocks(&self) -> impl Iterator { self.blocks.iter().filter(|block| !block.nested) } } -/// Enumerate the topology of `region` in the finalized `stage`. -pub fn region_topology(stage: &StageInfo, region: &Region) -> RegionTopology +/// Enumerate the topology of `cfg` in the finalized `stage`. +pub fn cfg_topology(stage: &StageInfo, cfg: &Cfg) -> CfgTopology where L: Dialect, - for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasRegions<'a>, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a>, { - let mut topology = RegionTopology::default(); + let mut topology = CfgTopology::default(); let mut visited = HashSet::new(); - for block in region.blocks(stage) { + for block in cfg.blocks(stage) { collect_block(stage, block, false, &mut topology, &mut visited); } topology @@ -69,11 +67,11 @@ fn collect_block( stage: &StageInfo, block: Block, nested: bool, - topology: &mut RegionTopology, + topology: &mut CfgTopology, visited: &mut HashSet, ) where L: Dialect, - for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasRegions<'a>, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a>, { if !visited.insert(block) { return; @@ -105,13 +103,13 @@ fn collect_block( for &stmt in &stmts { let definition = stmt.definition(stage); let owned_blocks: Vec = definition.blocks().copied().collect(); - let owned_regions: Vec = definition.regions().copied().collect(); + let owned_cfgs: Vec = definition.cfgs().copied().collect(); for owned in owned_blocks { topology.feeders.entry(owned).or_default().push(stmt); collect_block(stage, owned, true, topology, visited); } - for owned_region in owned_regions { - for owned in owned_region.blocks(stage) { + for owned_cfg in owned_cfgs { + for owned in owned_cfg.blocks(stage) { topology.feeders.entry(owned).or_default().push(stmt); collect_block(stage, owned, true, topology, visited); } diff --git a/crates/kirin-interpreter/src/lib.rs b/crates/kirin-interpreter/src/lib.rs index 9537d42d35..4f8055eea3 100644 --- a/crates/kirin-interpreter/src/lib.rs +++ b/crates/kirin-interpreter/src/lib.rs @@ -94,7 +94,7 @@ pub use engines::sparse_forward::{ }; // Sparse backward engine (`Sem = StrongDemand`). pub use engines::sparse_backward::{ - BackwardAnalysisState, DemandFrame, DemandInterp, DemandSummary, RegionScope, + BackwardAnalysisState, CfgScope, DemandFrame, DemandInterp, DemandSummary, SparseBackwardDriver, SparseBackwardEffect, SparseBackwardInterp, SparseBackwardInterpreter, SparseBackwardProfile, SparseBackwardTransfer, }; @@ -107,11 +107,11 @@ pub use engines::dense_backward::{ }; // Lattice anchors (*where* facts attach), scope qualification, the polymorphic -// fact stores, and region topology enumeration. Anchor family is a property of +// fact stores, and cfg topology enumeration. Anchor family is a property of // the solver shape; dispatch meaning lives in `semantics`. pub use facts::{ - BlockTopology, Change, DenseAnchor, DenseBlockStore, DensePointStore, FactStore, LatticeAnchor, - ProgramPoint, RegionTopology, Scoped, ScopedSparseStore, SparseStore, region_topology, + BlockTopology, CfgTopology, Change, DenseAnchor, DenseBlockStore, DensePointStore, FactStore, + LatticeAnchor, ProgramPoint, Scoped, ScopedSparseStore, SparseStore, cfg_topology, }; // Semantic keys (*what* a rule means — the `Interpretable`/`Interp::Semantics` diff --git a/crates/kirin-ir/src/builder/block.rs b/crates/kirin-ir/src/builder/block.rs index 3149a4bb88..8508457b03 100644 --- a/crates/kirin-ir/src/builder/block.rs +++ b/crates/kirin-ir/src/builder/block.rs @@ -5,7 +5,7 @@ use crate::{BuilderStageInfo, Dialect}; pub struct BlockBuilder<'a, L: Dialect> { stage: &'a mut BuilderStageInfo, - parent: Option, + parent: Option, name: Option, arguments: Vec<(L::Type, Option)>, statements: Vec, @@ -24,8 +24,8 @@ impl<'a, L: Dialect> BlockBuilder<'a, L> { } } - /// Attach the block to a parent region without pushing it to the region's block list. - pub fn parent(mut self, parent: Region) -> Self { + /// Attach the block to a parent cfg without pushing it to the CFG's block list. + pub fn parent(mut self, parent: Cfg) -> Self { self.parent = Some(parent); self } diff --git a/crates/kirin-ir/src/builder/cfg.rs b/crates/kirin-ir/src/builder/cfg.rs index 0300f552a1..f28aa77c75 100644 --- a/crates/kirin-ir/src/builder/cfg.rs +++ b/crates/kirin-ir/src/builder/cfg.rs @@ -1,12 +1,12 @@ -use crate::{Block, BuilderStageInfo, Dialect, Region, Statement, node::RegionInfo}; +use crate::{Block, BuilderStageInfo, Cfg, Dialect, Statement, node::CfgInfo}; -pub struct RegionBuilder<'a, L: Dialect> { +pub struct CfgBuilder<'a, L: Dialect> { pub(super) stage: &'a mut BuilderStageInfo, pub(super) parent: Option, pub(super) blocks: Vec, } -impl<'a, L: Dialect> RegionBuilder<'a, L> { +impl<'a, L: Dialect> CfgBuilder<'a, L> { pub fn from_stage(stage: &'a mut BuilderStageInfo) -> Self { Self { stage, @@ -22,21 +22,21 @@ impl<'a, L: Dialect> RegionBuilder<'a, L> { pub fn add_block(mut self, block: Block) -> Self { if self.blocks.contains(&block) { - panic!("Block `{}` is already added to the region", block); + panic!("Block `{}` is already added to the cfg", block); } self.blocks.push(block); self } #[allow(clippy::wrong_self_convention, clippy::new_ret_no_self)] - pub fn new(self) -> Region { - let id = self.stage.regions.next_id(); - let info = RegionInfo::builder() + pub fn new(self) -> Cfg { + let id = self.stage.cfgs.next_id(); + let info = CfgInfo::builder() .id(id) .blocks(self.stage.link_blocks(&self.blocks)) .maybe_parent(self.parent) .new(); - let _ = self.stage.regions.alloc(info); + let _ = self.stage.cfgs.alloc(info); id } } diff --git a/crates/kirin-ir/src/builder/context.rs b/crates/kirin-ir/src/builder/context.rs index 262bd707dc..12a64bbaec 100644 --- a/crates/kirin-ir/src/builder/context.rs +++ b/crates/kirin-ir/src/builder/context.rs @@ -1,6 +1,6 @@ use super::block::BlockBuilder; +use super::cfg::CfgBuilder; use super::digraph::DiGraphBuilder; -use super::region::RegionBuilder; use super::ungraph::UnGraphBuilder; use crate::{BuilderStageInfo, Dialect}; @@ -10,8 +10,8 @@ impl BuilderStageInfo { BlockBuilder::from_stage(self) } - pub fn region(&mut self) -> RegionBuilder<'_, L> { - RegionBuilder::from_stage(self) + pub fn cfg(&mut self) -> CfgBuilder<'_, L> { + CfgBuilder::from_stage(self) } pub fn digraph(&mut self) -> DiGraphBuilder<'_, L> { diff --git a/crates/kirin-ir/src/builder/mod.rs b/crates/kirin-ir/src/builder/mod.rs index c9c9b30f7b..570fb98287 100644 --- a/crates/kirin-ir/src/builder/mod.rs +++ b/crates/kirin-ir/src/builder/mod.rs @@ -12,7 +12,7 @@ //! → stage.statement() create statements //! → stage.block_argument() create placeholder SSAs //! → stage.block() build blocks (resolves placeholders) -//! → stage.region() group blocks into regions +//! → stage.cfg() group blocks into cfgs //! → stage.staged_function() register callable functions //! → stage.specialize() add specializations //! → stage.finalize() validate → StageInfo (clean SSAInfo) @@ -21,12 +21,12 @@ //! See [`BuilderStageInfo`] for detailed usage examples. mod block; +mod cfg; mod context; pub mod digraph; pub mod error; mod graph_common; mod redefine; -mod region; mod stage_info; mod staged; pub mod ungraph; diff --git a/crates/kirin-ir/src/builder/stage_info.rs b/crates/kirin-ir/src/builder/stage_info.rs index 77b3220b66..5147833c81 100644 --- a/crates/kirin-ir/src/builder/stage_info.rs +++ b/crates/kirin-ir/src/builder/stage_info.rs @@ -64,7 +64,7 @@ impl std::error::Error for FinalizeError {} /// Builder for constructing IR within a single compilation stage. /// /// `BuilderStageInfo` holds the same node arenas as [`StageInfo`] (blocks, -/// statements, regions, graphs, etc.) but uses [`BuilderSSAInfo`] for the SSA +/// statements, cfgs, graphs, etc.) but uses [`BuilderSSAInfo`] for the SSA /// arena — allowing `Option` and [`BuilderSSAKind`] placeholders during /// construction. /// @@ -105,11 +105,11 @@ impl std::error::Error for FinalizeError {} /// .new(); /// ``` /// -/// Regions (containers of blocks): +/// Cfgs (containers of blocks): /// ```ignore /// let entry = stage.block().new(); /// let exit = stage.block().new(); -/// let region = stage.region().add_block(entry).add_block(exit).new(); +/// let cfg = stage.cfg().add_block(entry).add_block(exit).new(); /// ``` /// /// # Finalization diff --git a/crates/kirin-ir/src/language.rs b/crates/kirin-ir/src/language.rs index 1eab8ba445..6b735e0c59 100644 --- a/crates/kirin-ir/src/language.rs +++ b/crates/kirin-ir/src/language.rs @@ -42,14 +42,14 @@ pub trait HasSuccessorsMut<'a> { fn successors_mut(&'a mut self) -> Self::IterMut; } -pub trait HasRegions<'a> { - type Iter: Iterator; - fn regions(&'a self) -> Self::Iter; +pub trait HasCfgs<'a> { + type Iter: Iterator; + fn cfgs(&'a self) -> Self::Iter; } -pub trait HasRegionsMut<'a> { - type IterMut: Iterator; - fn regions_mut(&'a mut self) -> Self::IterMut; +pub trait HasCfgsMut<'a> { + type IterMut: Iterator; + fn cfgs_mut(&'a mut self) -> Self::IterMut; } pub trait HasDigraphs<'a> { @@ -72,17 +72,17 @@ pub trait HasUngraphsMut<'a> { fn ungraphs_mut(&'a mut self) -> Self::IterMut; } -/// Structural trait for dialect operations that have a single region body. +/// Structural trait for dialect operations that have a single CFG body. /// /// This trait is intentionally not a supertrait of `Dialect` — it applies to /// individual operations (e.g., `FunctionBody`, `Lambda`) that contain a single -/// `Region`, not to the dialect enum itself. It enables shared helper functions -/// for interpreter and analysis code that operate on region-bearing operations. -pub trait HasRegionBody { - fn region(&self) -> &crate::Region; +/// `Cfg`, not to the dialect enum itself. It enables shared helper functions +/// for interpreter and analysis code that operate on CFG-bearing operations. +pub trait HasCfgBody { + fn cfg(&self) -> &crate::Cfg; fn entry_block(&self, stage: &crate::StageInfo) -> Option { - self.region().blocks(stage).next() + self.cfg().blocks(stage).next() } } @@ -127,8 +127,8 @@ pub trait Dialect: + for<'a> HasBlocksMut<'a> + for<'a> HasSuccessors<'a> + for<'a> HasSuccessorsMut<'a> - + for<'a> HasRegions<'a> - + for<'a> HasRegionsMut<'a> + + for<'a> HasCfgs<'a> + + for<'a> HasCfgsMut<'a> + for<'a> HasDigraphs<'a> + for<'a> HasDigraphsMut<'a> + for<'a> HasUngraphs<'a> diff --git a/crates/kirin-ir/src/lib.rs b/crates/kirin-ir/src/lib.rs index a2b23f527b..4c972f4a45 100644 --- a/crates/kirin-ir/src/lib.rs +++ b/crates/kirin-ir/src/lib.rs @@ -25,17 +25,17 @@ pub use comptime::{CompileTimeValue, Placeholder, Typeof}; pub use detach::Detach; pub use intern::InternTable; pub use language::{ - Dialect, HasArguments, HasArgumentsMut, HasBlocks, HasBlocksMut, HasDigraphs, HasDigraphsMut, - HasRegionBody, HasRegions, HasRegionsMut, HasResults, HasResultsMut, HasSuccessors, + Dialect, HasArguments, HasArgumentsMut, HasBlocks, HasBlocksMut, HasCfgBody, HasCfgs, + HasCfgsMut, HasDigraphs, HasDigraphsMut, HasResults, HasResultsMut, HasSuccessors, HasSuccessorsMut, HasUngraphs, HasUngraphsMut, IsConstant, IsEdge, IsPure, IsSpeculatable, IsTerminator, }; pub use lattice::{FiniteLattice, HasBottom, HasTop, Lattice, TypeLattice, Widen}; pub use node::{ - Block, BlockArgument, BlockInfo, BuilderKey, BuilderSSAInfo, BuilderSSAKind, CompileStage, + Block, BlockArgument, BlockInfo, BuilderKey, BuilderSSAInfo, BuilderSSAKind, Cfg, CompileStage, DeletedSSAValue, DiGraph, DiGraphExtra, DiGraphInfo, Function, FunctionInfo, GlobalSymbol, - GraphInfo, LinkedList, LinkedListNode, Port, PortParent, Region, ResolutionInfo, ResultValue, - SSAInfo, SSAKind, SSAValue, SpecializedFunction, SpecializedFunctionInfo, StagedFunction, + GraphInfo, LinkedList, LinkedListNode, Port, PortParent, ResolutionInfo, ResultValue, SSAInfo, + SSAKind, SSAValue, SpecializedFunction, SpecializedFunctionInfo, StagedFunction, StagedFunctionInfo, StagedNamePolicy, Statement, StatementInfo, StatementParent, Successor, Symbol, TestSSAValue, UnGraph, UnGraphExtra, UnGraphInfo, UniqueLiveSpecializationError, }; @@ -54,9 +54,9 @@ pub use stage::{ /// Re-exports of the most commonly used types for dialect authors. pub mod prelude { pub use crate::{ - Block, BuilderStageInfo, CompileStage, Dialect, Function, GetInfo, HasRegionBody, - HasSignature, HasStageInfo, Pipeline, Region, ResultValue, SSAValue, Signature, - SignatureSemantics, StageInfo, StageMeta, Statement, + Block, BuilderStageInfo, Cfg, CompileStage, Dialect, Function, GetInfo, HasCfgBody, + HasSignature, HasStageInfo, Pipeline, ResultValue, SSAValue, Signature, SignatureSemantics, + StageInfo, StageMeta, Statement, }; pub use crate::{ CompileTimeValue, HasProduct, Placeholder, Product, Project, ProjectError, TryProject, @@ -66,7 +66,7 @@ pub mod prelude { #[cfg(feature = "derive")] pub use kirin_derive_ir::{ - Dialect, HasArguments, HasDigraphs, HasRegions, HasResults, HasSuccessors, HasUngraphs, + Dialect, HasArguments, HasCfgs, HasDigraphs, HasResults, HasSuccessors, HasUngraphs, IsConstant, IsEdge, IsPure, IsSpeculatable, IsTerminator, LiftProject, ParseDispatch, StageMeta, }; diff --git a/crates/kirin-ir/src/node/block.rs b/crates/kirin-ir/src/node/block.rs index b20f900f8c..9768678411 100644 --- a/crates/kirin-ir/src/node/block.rs +++ b/crates/kirin-ir/src/node/block.rs @@ -2,7 +2,7 @@ use crate::{ Dialect, Symbol, arena::{GetInfo, Id, Item}, identifier, - node::region::Region, + node::cfg::Cfg, }; use super::{ @@ -51,7 +51,7 @@ impl std::fmt::Display for Successor { #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BlockInfo { - pub parent: Option, + pub parent: Option, pub name: Option, pub node: LinkedListNode, pub arguments: Vec, @@ -64,8 +64,8 @@ pub struct BlockInfo { impl BlockInfo { #[builder(finish_fn = new)] pub(crate) fn new( - /// The parent region of this block. - parent: Option, + /// The parent cfg of this block. + parent: Option, /// The name of this block. name: Option, /// The linked list node for this block. diff --git a/crates/kirin-ir/src/node/cfg.rs b/crates/kirin-ir/src/node/cfg.rs index 07d5d24dae..963d9f0e62 100644 --- a/crates/kirin-ir/src/node/cfg.rs +++ b/crates/kirin-ir/src/node/cfg.rs @@ -6,27 +6,27 @@ use super::linked_list::LinkedList; use super::stmt::Statement; identifier! { - /// A unique identifier for a region. - struct Region + /// A unique identifier for a CFG (a block-list control-flow body). + struct Cfg } #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct RegionInfo { - pub(crate) id: Region, +pub struct CfgInfo { + pub(crate) id: Cfg, pub(crate) parent: Option, pub(crate) blocks: LinkedList, _marker: std::marker::PhantomData, } #[bon::bon] -impl RegionInfo { +impl CfgInfo { #[builder(finish_fn = new)] pub fn new( - /// The unique identifier for this region. - id: Region, - /// The parent statement of this region, if any. + /// The unique identifier for this CFG. + id: Cfg, + /// The parent statement of this CFG, if any. parent: Option, - /// The blocks contained in this region. + /// The blocks contained in this CFG. blocks: LinkedList, ) -> Self { Self { @@ -38,19 +38,19 @@ impl RegionInfo { } } -impl GetInfo for Region { - type Info = Item>; +impl GetInfo for Cfg { + type Info = Item>; fn get_info<'a>(&self, stage: &'a crate::StageInfo) -> Option<&'a Self::Info> { - stage.regions.get(*self) + stage.cfgs.get(*self) } fn get_info_mut<'a>(&self, stage: &'a mut crate::StageInfo) -> Option<&'a mut Self::Info> { - stage.regions.get_mut(*self) + stage.cfgs.get_mut(*self) } } -impl Region { +impl Cfg { pub fn blocks<'a, L: Dialect>(&self, stage: &'a crate::StageInfo) -> BlockIter<'a, L> { let info = self.expect_info(stage); BlockIter { diff --git a/crates/kirin-ir/src/node/mod.rs b/crates/kirin-ir/src/node/mod.rs index 33a54e3469..c3e1a848d0 100644 --- a/crates/kirin-ir/src/node/mod.rs +++ b/crates/kirin-ir/src/node/mod.rs @@ -1,16 +1,17 @@ pub mod block; +pub mod cfg; pub(crate) mod digraph; pub mod function; pub(crate) mod graph; pub mod linked_list; pub(crate) mod port; -pub mod region; pub mod ssa; pub mod stmt; pub mod symbol; pub(crate) mod ungraph; pub use block::{Block, BlockInfo, Successor}; +pub use cfg::{Cfg, CfgInfo}; pub use digraph::{DiGraph, DiGraphInfo}; pub use function::{ CompileStage, Function, FunctionInfo, SpecializedFunction, SpecializedFunctionInfo, @@ -19,7 +20,6 @@ pub use function::{ pub use graph::{DiGraphExtra, GraphInfo, UnGraphExtra}; pub use linked_list::{LinkedList, LinkedListNode}; pub use port::{Port, PortParent}; -pub use region::{Region, RegionInfo}; pub use ssa::{ BlockArgument, BuilderKey, BuilderSSAInfo, BuilderSSAKind, DeletedSSAValue, ResolutionInfo, ResultValue, SSAInfo, SSAKind, SSAValue, TestSSAValue, diff --git a/crates/kirin-ir/src/node/stmt.rs b/crates/kirin-ir/src/node/stmt.rs index a0296e5c4e..0cad0d9ea4 100644 --- a/crates/kirin-ir/src/node/stmt.rs +++ b/crates/kirin-ir/src/node/stmt.rs @@ -60,11 +60,11 @@ impl Statement { self.expect_info(stage).definition.arguments() } - pub fn regions<'a, L: Dialect>( + pub fn cfgs<'a, L: Dialect>( &self, stage: &'a crate::StageInfo, - ) -> >::Iter { - self.expect_info(stage).definition.regions() + ) -> >::Iter { + self.expect_info(stage).definition.cfgs() } pub fn blocks<'a, L: Dialect>( diff --git a/crates/kirin-ir/src/query/info.rs b/crates/kirin-ir/src/query/info.rs index d4307b56ee..6dbfbb21ea 100644 --- a/crates/kirin-ir/src/query/info.rs +++ b/crates/kirin-ir/src/query/info.rs @@ -1,7 +1,7 @@ use crate::{ Dialect, LinkedList, node::{ - Block, BlockInfo, LinkedListNode, Region, RegionInfo, Statement, StatementInfo, + Block, BlockInfo, Cfg, CfgInfo, LinkedListNode, Statement, StatementInfo, stmt::StatementParent, }, }; @@ -26,7 +26,7 @@ impl ParentInfo for StatementInfo { } impl ParentInfo for BlockInfo { - type ParentPtr = Region; + type ParentPtr = Cfg; fn get_parent(&self) -> &Option { &self.parent } @@ -82,7 +82,7 @@ impl LinkedListInfo for BlockInfo { } } -impl LinkedListInfo for RegionInfo { +impl LinkedListInfo for CfgInfo { type Ptr = Block; fn get_linked_list(&self) -> &LinkedList { &self.blocks diff --git a/crates/kirin-ir/src/stage/arenas.rs b/crates/kirin-ir/src/stage/arenas.rs index 8a6b3e0795..8f40c24a0e 100644 --- a/crates/kirin-ir/src/stage/arenas.rs +++ b/crates/kirin-ir/src/stage/arenas.rs @@ -1,7 +1,7 @@ use crate::arena::Arena; +use crate::node::cfg::CfgInfo; use crate::node::digraph::{DiGraph, DiGraphInfo}; use crate::node::function::CompileStage; -use crate::node::region::RegionInfo; use crate::node::ungraph::{UnGraph, UnGraphInfo}; use crate::{Dialect, InternTable, node::*}; @@ -22,7 +22,7 @@ pub struct Arenas { pub(crate) stage_id: Option, pub(crate) staged_functions: Arena>, pub(crate) staged_name_policy: StagedNamePolicy, - pub(crate) regions: Arena>, + pub(crate) cfgs: Arena>, pub(crate) blocks: Arena>, pub(crate) statements: Arena>, pub(crate) digraphs: Arena>, @@ -40,7 +40,7 @@ where stage_id: None, staged_functions: Arena::default(), staged_name_policy: StagedNamePolicy::default(), - regions: Arena::default(), + cfgs: Arena::default(), blocks: Arena::default(), statements: Arena::default(), digraphs: Arena::default(), @@ -61,7 +61,7 @@ where stage_id: self.stage_id, staged_functions: self.staged_functions.clone(), staged_name_policy: self.staged_name_policy, - regions: self.regions.clone(), + cfgs: self.cfgs.clone(), blocks: self.blocks.clone(), statements: self.statements.clone(), digraphs: self.digraphs.clone(), @@ -127,9 +127,9 @@ impl Arenas { self.staged_name_policy = policy; } - /// Get a reference to the regions arena. - pub fn region_arena(&self) -> &Arena> { - &self.regions + /// Get a reference to the cfgs arena. + pub fn cfg_arena(&self) -> &Arena> { + &self.cfgs } /// Get a reference to the blocks arena. diff --git a/crates/kirin-ir/src/stage/info.rs b/crates/kirin-ir/src/stage/info.rs index d38c156f90..aadf8112cd 100644 --- a/crates/kirin-ir/src/stage/info.rs +++ b/crates/kirin-ir/src/stage/info.rs @@ -8,7 +8,7 @@ use super::arenas::Arenas; /// Finalized IR for a single compilation stage. /// -/// `StageInfo` holds the node arenas (blocks, statements, regions, graphs, +/// `StageInfo` holds the node arenas (blocks, statements, cfgs, graphs, /// functions) and a clean SSA arena where every value has a resolved type and /// kind. It is the read-only output of [`BuilderStageInfo::finalize`]. /// @@ -55,8 +55,8 @@ use super::arenas::Arenas; /// let arg = b.block_argument().index(0); /// let ret = b.statement().definition(MyDialect::Return(arg)).new(); /// let block = b.block().argument(MyType::I64).terminator(ret).new(); -/// let region = b.region().add_block(block).new(); -/// let body = b.statement().definition(MyDialect::FuncBody(region)).new(); +/// let cfg = b.cfg().add_block(block).new(); +/// let body = b.statement().definition(MyDialect::FuncBody(cfg)).new(); /// /// b.specialize().staged_func(sf).body(body).new().unwrap(); /// }); diff --git a/crates/kirin-ir/src/stage/tests.rs b/crates/kirin-ir/src/stage/tests.rs index 59495b651f..4cfc10bd91 100644 --- a/crates/kirin-ir/src/stage/tests.rs +++ b/crates/kirin-ir/src/stage/tests.rs @@ -2,11 +2,11 @@ use std::convert::Infallible; use super::*; use crate::{ - Block, CompileStage, DiGraph, Dialect, GlobalSymbol, HasArguments, HasArgumentsMut, HasBlocks, - HasBlocksMut, HasDigraphs, HasDigraphsMut, HasRegions, HasRegionsMut, HasResults, + Block, Cfg, CompileStage, DiGraph, Dialect, GlobalSymbol, HasArguments, HasArgumentsMut, + HasBlocks, HasBlocksMut, HasCfgs, HasCfgsMut, HasDigraphs, HasDigraphsMut, HasResults, HasResultsMut, HasStageInfo, HasSuccessors, HasSuccessorsMut, HasUngraphs, HasUngraphsMut, Id, - IsConstant, IsEdge, IsPure, IsSpeculatable, IsTerminator, Pipeline, Region, ResultValue, - SSAValue, StageInfo, StageMeta, StagedNamePolicy, Successor, UnGraph, + IsConstant, IsEdge, IsPure, IsSpeculatable, IsTerminator, Pipeline, ResultValue, SSAValue, + StageInfo, StageMeta, StagedNamePolicy, Successor, UnGraph, }; #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] @@ -89,18 +89,18 @@ macro_rules! impl_empty_dialect_traits { } } - impl<'a> HasRegions<'a> for $dialect { - type Iter = std::iter::Empty<&'a Region>; + impl<'a> HasCfgs<'a> for $dialect { + type Iter = std::iter::Empty<&'a Cfg>; - fn regions(&'a self) -> Self::Iter { + fn cfgs(&'a self) -> Self::Iter { std::iter::empty() } } - impl<'a> HasRegionsMut<'a> for $dialect { - type IterMut = std::iter::Empty<&'a mut Region>; + impl<'a> HasCfgsMut<'a> for $dialect { + type IterMut = std::iter::Empty<&'a mut Cfg>; - fn regions_mut(&'a mut self) -> Self::IterMut { + fn cfgs_mut(&'a mut self) -> Self::IterMut { std::iter::empty() } } diff --git a/crates/kirin-ir/tests/builder_block.rs b/crates/kirin-ir/tests/builder_block.rs index 860d4d45ed..6473ad6e78 100644 --- a/crates/kirin-ir/tests/builder_block.rs +++ b/crates/kirin-ir/tests/builder_block.rs @@ -1,4 +1,4 @@ -//! Integration tests for block builder, region builder, statement iteration, +//! Integration tests for block builder, cfg builder, statement iteration, //! detach, SSA creation, and linked list helpers. mod common; @@ -6,13 +6,13 @@ mod common; use common::{BuilderDialect, TestType, new_stage}; use kirin_ir::*; -struct RegionBodyOp { - region: Region, +struct CfgBodyOp { + cfg: Cfg, } -impl HasRegionBody for RegionBodyOp { - fn region(&self) -> &Region { - &self.region +impl HasCfgBody for CfgBodyOp { + fn cfg(&self) -> &Cfg { + &self.cfg } } @@ -226,25 +226,20 @@ fn block_argument_placeholder_substitution_with_zero_args() { assert!(info.arguments.is_empty()); } -// --- RegionBuilder tests --- +// --- CfgBuilder tests --- #[test] -fn region_builder_creates_region_with_ordered_blocks() { +fn cfg_builder_creates_cfg_with_ordered_blocks() { let mut stage = new_stage(); let b0 = stage.block().new(); let b1 = stage.block().new(); let b2 = stage.block().new(); - let region = stage - .region() - .add_block(b0) - .add_block(b1) - .add_block(b2) - .new(); + let cfg = stage.cfg().add_block(b0).add_block(b1).add_block(b2).new(); let stage = stage.finalize().unwrap(); - assert_eq!(region.blocks(&stage).len(), 3); - let blocks: Vec<_> = region.blocks(&stage).collect(); + assert_eq!(cfg.blocks(&stage).len(), 3); + let blocks: Vec<_> = cfg.blocks(&stage).collect(); assert_eq!(blocks, vec![b0, b1, b2]); let b0_info = b0.expect_info(&stage); @@ -258,52 +253,47 @@ fn region_builder_creates_region_with_ordered_blocks() { } #[test] -fn has_region_body_entry_block_returns_first_block() { +fn has_cfg_body_entry_block_returns_first_block() { let mut stage = new_stage(); let b0 = stage.block().new(); let b1 = stage.block().new(); - let region = stage.region().add_block(b0).add_block(b1).new(); - let op = RegionBodyOp { region }; + let cfg = stage.cfg().add_block(b0).add_block(b1).new(); + let op = CfgBodyOp { cfg }; let stage = stage.finalize().unwrap(); assert_eq!(op.entry_block(&stage), Some(b0)); } #[test] -#[should_panic(expected = "already added to the region")] -fn region_builder_panics_on_duplicate_block() { +#[should_panic(expected = "already added to the cfg")] +fn cfg_builder_panics_on_duplicate_block() { let mut stage = new_stage(); let b0 = stage.block().new(); - let _ = stage.region().add_block(b0).add_block(b0).new(); + let _ = stage.cfg().add_block(b0).add_block(b0).new(); } #[test] -fn region_block_iter_single_block() { +fn cfg_block_iter_single_block() { let mut stage = new_stage(); let b0 = stage.block().new(); - let region = stage.region().add_block(b0).new(); + let cfg = stage.cfg().add_block(b0).new(); let stage = stage.finalize().unwrap(); - let blocks: Vec<_> = region.blocks(&stage).collect(); + let blocks: Vec<_> = cfg.blocks(&stage).collect(); assert_eq!(blocks, vec![b0]); - assert_eq!(region.blocks(&stage).len(), 1); + assert_eq!(cfg.blocks(&stage).len(), 1); } #[test] -fn region_block_iter_double_ended() { +fn cfg_block_iter_double_ended() { let mut stage = new_stage(); let b0 = stage.block().new(); let b1 = stage.block().new(); let b2 = stage.block().new(); - let region = stage - .region() - .add_block(b0) - .add_block(b1) - .add_block(b2) - .new(); + let cfg = stage.cfg().add_block(b0).add_block(b1).add_block(b2).new(); let stage = stage.finalize().unwrap(); - let mut iter = region.blocks(&stage); + let mut iter = cfg.blocks(&stage); assert_eq!(iter.next_back(), Some(b2)); assert_eq!(iter.next(), Some(b0)); assert_eq!(iter.next_back(), Some(b1)); @@ -312,14 +302,14 @@ fn region_block_iter_double_ended() { } #[test] -fn region_block_iter_exact_size() { +fn cfg_block_iter_exact_size() { let mut stage = new_stage(); let b0 = stage.block().new(); let b1 = stage.block().new(); - let region = stage.region().add_block(b0).add_block(b1).new(); + let cfg = stage.cfg().add_block(b0).add_block(b1).new(); let stage = stage.finalize().unwrap(); - let mut iter = region.blocks(&stage); + let mut iter = cfg.blocks(&stage); assert_eq!(iter.len(), 2); iter.next(); assert_eq!(iter.len(), 1); @@ -328,14 +318,14 @@ fn region_block_iter_exact_size() { } #[test] -fn empty_region() { +fn empty_cfg() { let mut stage = new_stage(); - let region = stage.region().new(); + let cfg = stage.cfg().new(); let stage = stage.finalize().unwrap(); - let blocks: Vec<_> = region.blocks(&stage).collect(); + let blocks: Vec<_> = cfg.blocks(&stage).collect(); assert!(blocks.is_empty()); - assert_eq!(region.blocks(&stage).len(), 0); + assert_eq!(cfg.blocks(&stage).len(), 0); } // --- Detach tests --- diff --git a/crates/kirin-ir/tests/common.rs b/crates/kirin-ir/tests/common.rs index 52c3603c07..e32a46e762 100644 --- a/crates/kirin-ir/tests/common.rs +++ b/crates/kirin-ir/tests/common.rs @@ -124,16 +124,16 @@ impl<'a> HasSuccessorsMut<'a> for BuilderDialect { } } -impl<'a> HasRegions<'a> for BuilderDialect { - type Iter = std::iter::Empty<&'a Region>; - fn regions(&'a self) -> Self::Iter { +impl<'a> HasCfgs<'a> for BuilderDialect { + type Iter = std::iter::Empty<&'a Cfg>; + fn cfgs(&'a self) -> Self::Iter { std::iter::empty() } } -impl<'a> HasRegionsMut<'a> for BuilderDialect { - type IterMut = std::iter::Empty<&'a mut Region>; - fn regions_mut(&'a mut self) -> Self::IterMut { +impl<'a> HasCfgsMut<'a> for BuilderDialect { + type IterMut = std::iter::Empty<&'a mut Cfg>; + fn cfgs_mut(&'a mut self) -> Self::IterMut { std::iter::empty() } } diff --git a/crates/kirin-liveness/src/lib.rs b/crates/kirin-liveness/src/lib.rs index 74b78534a2..56f90893a2 100644 --- a/crates/kirin-liveness/src/lib.rs +++ b/crates/kirin-liveness/src/lib.rs @@ -15,7 +15,7 @@ //! sets are the intersection of the dense sets with this demand set. //! //! ```ignore -//! let result = kirin_liveness::analyze_demand(&pipeline, stage, region)?; +//! let result = kirin_liveness::analyze_demand(&pipeline, stage, cfg)?; //! assert!(result.is_demanded(some_value)); //! ``` @@ -29,7 +29,7 @@ use kirin_interpreter::{ DenseBackwardInterpreter, DenseBackwardTransfer, InterpDispatch, InterpreterError, SparseBackwardDriver, SparseBackwardInterpreter, StageQuery, StandardDenseBackwardFrame, }; -use kirin_ir::{CompileStage, Pipeline, Region, StageMeta}; +use kirin_ir::{Cfg, CompileStage, Pipeline, StageMeta}; /// The sparse backward demand engine instantiated at the [`Live`] lattice: /// strong liveness. @@ -41,11 +41,11 @@ pub type Demand<'ir, S, E = InterpreterError> = SparseBackwardInterpreter<'ir, S pub type DenseLiveness<'ir, S, E = InterpreterError, F = StandardDenseBackwardFrame> = DenseBackwardInterpreter<'ir, S, LiveSet, E, F>; -/// Run strong liveness (sparse backward demand) over `region` in `stage`. +/// Run strong liveness (sparse backward demand) over `cfg` in `stage`. pub fn analyze_demand<'ir, S>( pipeline: &'ir Pipeline, stage: CompileStage, - region: Region, + cfg: Cfg, ) -> Result where S: StageMeta @@ -53,18 +53,18 @@ where + InterpDispatch>, { let mut engine = Demand::::new(pipeline); - engine.analyze(stage, region)?; - Ok(DemandResult::from_engine(&engine, stage, region)) + engine.analyze(stage, cfg)?; + Ok(DemandResult::from_engine(&engine, stage, cfg)) } -/// Run classic per-point liveness (dense backward) over `region` in `stage`, +/// Run classic per-point liveness (dense backward) over `cfg` in `stage`, /// with the standard (structured-control-free) frames. Languages with scf /// compose [`DenseLiveness`] with their own frame type and build the result /// via [`DenseLivenessResult::from_engine`]. pub fn analyze_dense<'ir, S>( pipeline: &'ir Pipeline, stage: CompileStage, - region: Region, + cfg: Cfg, ) -> Result where S: StageMeta @@ -80,6 +80,6 @@ where >, { let mut engine = DenseLiveness::::new(pipeline); - engine.analyze(stage, region)?; - DenseLivenessResult::from_engine(&mut engine, stage, region) + engine.analyze(stage, cfg)?; + DenseLivenessResult::from_engine(&mut engine, stage, cfg) } diff --git a/crates/kirin-liveness/src/result.rs b/crates/kirin-liveness/src/result.rs index 348a05c5ee..b7e337608f 100644 --- a/crates/kirin-liveness/src/result.rs +++ b/crates/kirin-liveness/src/result.rs @@ -6,7 +6,7 @@ use kirin_interpreter::{ DenseBlockStore, DenseFrameBuild, DensePointStore, Frame, InterpDispatch, InterpreterError, ProgramPoint, SparseBackwardInterpreter, StageQuery, }; -use kirin_ir::{Block, CompileStage, Lattice, Region, SSAValue, StageMeta, Statement}; +use kirin_ir::{Block, Cfg, CompileStage, Lattice, SSAValue, StageMeta, Statement}; use crate::live::{Live, LiveSet}; @@ -21,10 +21,10 @@ impl DemandResult { pub(crate) fn from_engine( engine: &SparseBackwardInterpreter<'_, S, Live, InterpreterError>, stage: CompileStage, - region: Region, + cfg: Cfg, ) -> Self { // The engine's sparse fact view; the demand set is its live support. - let facts = engine.fact_store(stage, region); + let facts = engine.fact_store(stage, cfg); let demanded = facts .iter() .filter(|(_, fact)| fact.is_live()) @@ -64,7 +64,7 @@ impl DenseLivenessResult { pub fn from_engine<'ir, S, F>( engine: &mut DenseBackwardInterpreter<'ir, S, LiveSet, InterpreterError, F>, stage: CompileStage, - region: Region, + cfg: Cfg, ) -> Result where S: StageMeta @@ -77,12 +77,12 @@ impl DenseLivenessResult { { let mut blocks = DenseBlockStore::new(); for block in engine.cfg_blocks() { - if let Some(summary) = engine.block_summary(stage, region, block) { + if let Some(summary) = engine.block_summary(stage, cfg, block) { blocks.set_entry(block, summary.live_in.clone()); blocks.set_exit(block, summary.live_out.clone()); } } - let points = engine.reconstruct_points(stage, region)?; + let points = engine.reconstruct_points(stage, cfg)?; Ok(Self { blocks, points }) } diff --git a/crates/kirin-liveness/tests/cfg.rs b/crates/kirin-liveness/tests/cfg.rs index 855f4de1c0..a58f82eed0 100644 --- a/crates/kirin-liveness/tests/cfg.rs +++ b/crates/kirin-liveness/tests/cfg.rs @@ -47,10 +47,10 @@ fn parse(program: &str) -> Pipeline> { pipeline } -/// The finalized stage id and the body region of `@main`. -fn main_region( +/// The finalized stage id and the body cfg of `@main`. +fn main_cfg( pipeline: &Pipeline>, -) -> (kirin::prelude::CompileStage, kirin_ir::Region) { +) -> (kirin::prelude::CompileStage, kirin_ir::Cfg) { let stage_id = pipeline.stage_by_name("test").expect("stage @test exists"); let stage = pipeline.stage(stage_id).expect("stage info"); @@ -61,25 +61,22 @@ fn main_region( let spec = &sf_info.specializations()[0]; let body = *spec.body(); - let region = match body.definition(stage) { + let cfg = match body.definition(stage) { ArithFunctionLanguage::Function { body, .. } => *body, other => panic!("expected a function body, got {other:?}"), }; - (stage_id, region) + (stage_id, cfg) } -/// The parameters of the `index`-th block of `region`, as SSA values. +/// The parameters of the `index`-th block of `cfg`, as SSA values. fn block_params( pipeline: &Pipeline>, - region: kirin_ir::Region, + cfg: kirin_ir::Cfg, index: usize, ) -> Vec { let stage_id = pipeline.stage_by_name("test").expect("stage @test exists"); let stage = pipeline.stage(stage_id).expect("stage info"); - let block = region - .blocks(stage) - .nth(index) - .expect("block index in range"); + let block = cfg.blocks(stage).nth(index).expect("block index in range"); block .expect_info(stage) .arguments @@ -89,16 +86,16 @@ fn block_params( .collect() } -/// Find an `Arith` statement in `region` matching `select`, returning what it +/// Find an `Arith` statement in `cfg` matching `select`, returning what it /// selects (e.g. the result and operands of the one `add`). fn find_arith( pipeline: &Pipeline>, - region: kirin_ir::Region, + cfg: kirin_ir::Cfg, select: impl Fn(&Arith) -> Option, ) -> R { let stage_id = pipeline.stage_by_name("test").expect("stage @test exists"); let stage = pipeline.stage(stage_id).expect("stage info"); - for block in region.blocks(stage) { + for block in cfg.blocks(stage) { for stmt in block.statements(stage) { if let ArithFunctionLanguage::Arith(op) = stmt.definition(stage) && let Some(selected) = select(op) @@ -107,23 +104,23 @@ fn find_arith( } } } - panic!("no matching arith statement in region"); + panic!("no matching arith statement in cfg"); } #[test] fn strong_liveness_over_branching_function() { let pipeline = parse(PROGRAM); - let (stage, region) = main_region(&pipeline); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let entry_params = block_params(&pipeline, region, 0); + let entry_params = block_params(&pipeline, cfg, 0); let (x, cond) = (entry_params[0], entry_params[1]); - let then_param = block_params(&pipeline, region, 1)[0]; - let else_param = block_params(&pipeline, region, 2)[0]; + let then_param = block_params(&pipeline, cfg, 1)[0]; + let else_param = block_params(&pipeline, cfg, 2)[0]; // The dead `add` result is never demanded — its rule is purity-aware, so // it contributes no operand demand of its own. - let (dead, add_lhs) = find_arith(&pipeline, region, |op| match op { + let (dead, add_lhs) = find_arith(&pipeline, cfg, |op| match op { Arith::Add { lhs, result, .. } => Some((SSAValue::from(*result), *lhs)), _ => None, }); @@ -143,7 +140,7 @@ fn strong_liveness_over_branching_function() { "both edges pass %x into demanded params" ); - let neg = find_arith(&pipeline, region, |op| match op { + let neg = find_arith(&pipeline, cfg, |op| match op { Arith::Neg { result, .. } => Some(SSAValue::from(*result)), _ => None, }); @@ -153,13 +150,13 @@ fn strong_liveness_over_branching_function() { #[test] fn unused_successor_block_argument_does_not_keep_edge_arg_live() { let pipeline = parse(DEAD_EDGE_ARG_PROGRAM); - let (stage, region) = main_region(&pipeline); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let entry_params = block_params(&pipeline, region, 0); + let entry_params = block_params(&pipeline, cfg, 0); let (live, dead, cond) = (entry_params[0], entry_params[1], entry_params[2]); - let then_param = block_params(&pipeline, region, 1)[0]; - let else_param = block_params(&pipeline, region, 2)[0]; + let then_param = block_params(&pipeline, cfg, 1)[0]; + let else_param = block_params(&pipeline, cfg, 2)[0]; // `^else` returns `%live` directly (a dominated cross-block use), never // touching its own parameter — so the `%dead` edge argument stays dead. @@ -213,21 +210,21 @@ specialize @test fn @main(i64, i64) -> i64 { #[test] fn terminator_operands_become_demanded() { let pipeline = parse(RET_PARAM_PROGRAM); - let (stage, region) = main_region(&pipeline); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let x = block_params(&pipeline, region, 0)[0]; + let x = block_params(&pipeline, cfg, 0)[0]; assert!(result.is_demanded(x)); } #[test] fn demanded_result_marks_operands_demanded() { let pipeline = parse(DEMANDED_RESULT_PROGRAM); - let (stage, region) = main_region(&pipeline); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let params = block_params(&pipeline, region, 0); - let sum = find_arith(&pipeline, region, |op| match op { + let params = block_params(&pipeline, cfg, 0); + let sum = find_arith(&pipeline, cfg, |op| match op { Arith::Add { result, .. } => Some(SSAValue::from(*result)), _ => None, }); @@ -239,11 +236,11 @@ fn demanded_result_marks_operands_demanded() { #[test] fn dead_result_leaves_operands_dead() { let pipeline = parse(DEAD_RESULT_PROGRAM); - let (stage, region) = main_region(&pipeline); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let params = block_params(&pipeline, region, 0); - let sum = find_arith(&pipeline, region, |op| match op { + let params = block_params(&pipeline, cfg, 0); + let sum = find_arith(&pipeline, cfg, |op| match op { Arith::Add { result, .. } => Some(SSAValue::from(*result)), _ => None, }); @@ -264,29 +261,26 @@ fn dead_result_leaves_operands_dead() { use kirin_liveness::{LiveSet, analyze_dense}; -/// The `index`-th block of `region`. +/// The `index`-th block of `cfg`. fn nth_block( pipeline: &Pipeline>, - region: kirin_ir::Region, + cfg: kirin_ir::Cfg, index: usize, ) -> kirin_ir::Block { let stage_id = pipeline.stage_by_name("test").expect("stage @test exists"); let stage = pipeline.stage(stage_id).expect("stage info"); - region - .blocks(stage) - .nth(index) - .expect("block index in range") + cfg.blocks(stage).nth(index).expect("block index in range") } /// The statement whose definition matches `select`. fn find_stmt( pipeline: &Pipeline>, - region: kirin_ir::Region, + cfg: kirin_ir::Cfg, select: impl Fn(&ArithFunctionLanguage) -> bool, ) -> kirin_ir::Statement { let stage_id = pipeline.stage_by_name("test").expect("stage @test exists"); let stage = pipeline.stage(stage_id).expect("stage info"); - for block in region.blocks(stage) { + for block in cfg.blocks(stage) { for stmt in block.statements(stage) { if select(stmt.definition(stage)) { return stmt; @@ -298,7 +292,7 @@ fn find_stmt( return terminator; } } - panic!("no matching statement in region"); + panic!("no matching statement in cfg"); } fn live_set(values: &[SSAValue]) -> LiveSet { @@ -308,16 +302,16 @@ fn live_set(values: &[SSAValue]) -> LiveSet { #[test] fn classic_liveness_boundary_sets() { let pipeline = parse(PROGRAM); - let (stage, region) = main_region(&pipeline); - let result = analyze_dense(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let result = analyze_dense(&pipeline, stage, cfg).expect("analysis succeeds"); - let entry_params = block_params(&pipeline, region, 0); + let entry_params = block_params(&pipeline, cfg, 0); let (x, cond) = (entry_params[0], entry_params[1]); - let then_param = block_params(&pipeline, region, 1)[0]; - let else_param = block_params(&pipeline, region, 2)[0]; - let entry = nth_block(&pipeline, region, 0); - let then_block = nth_block(&pipeline, region, 1); - let else_block = nth_block(&pipeline, region, 2); + let then_param = block_params(&pipeline, cfg, 1)[0]; + let else_param = block_params(&pipeline, cfg, 2)[0]; + let entry = nth_block(&pipeline, cfg, 0); + let then_block = nth_block(&pipeline, cfg, 1); + let else_block = nth_block(&pipeline, cfg, 2); // live_in(entry): %x (used by add and both edges) and %cond (branch use). assert_eq!(result.live_in(entry), Some(&live_set(&[x, cond]))); @@ -334,12 +328,12 @@ fn classic_liveness_boundary_sets() { #[test] fn classic_per_point_sets_gen_dead_uses() { let pipeline = parse(DEAD_RESULT_PROGRAM); - let (stage, region) = main_region(&pipeline); - let result = analyze_dense(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let result = analyze_dense(&pipeline, stage, cfg).expect("analysis succeeds"); - let params = block_params(&pipeline, region, 0); + let params = block_params(&pipeline, cfg, 0); let (a, b) = (params[0], params[1]); - let add = find_stmt(&pipeline, region, |definition| { + let add = find_stmt(&pipeline, cfg, |definition| { matches!(definition, ArithFunctionLanguage::Arith(Arith::Add { .. })) }); @@ -353,13 +347,13 @@ fn classic_per_point_sets_gen_dead_uses() { #[test] fn strong_per_point_sets_are_classic_intersect_demanded() { let pipeline = parse(DEAD_RESULT_PROGRAM); - let (stage, region) = main_region(&pipeline); - let dense = analyze_dense(&pipeline, stage, region).expect("dense analysis succeeds"); - let demand = analyze_demand(&pipeline, stage, region).expect("demand analysis succeeds"); + let (stage, cfg) = main_cfg(&pipeline); + let dense = analyze_dense(&pipeline, stage, cfg).expect("dense analysis succeeds"); + let demand = analyze_demand(&pipeline, stage, cfg).expect("demand analysis succeeds"); - let params = block_params(&pipeline, region, 0); + let params = block_params(&pipeline, cfg, 0); let (a, b) = (params[0], params[1]); - let add = find_stmt(&pipeline, region, |definition| { + let add = find_stmt(&pipeline, cfg, |definition| { matches!(definition, ArithFunctionLanguage::Arith(Arith::Add { .. })) }); diff --git a/crates/kirin-prettyless/src/document/builder.rs b/crates/kirin-prettyless/src/document/builder.rs index fc212fb3c9..c540351278 100644 --- a/crates/kirin-prettyless/src/document/builder.rs +++ b/crates/kirin-prettyless/src/document/builder.rs @@ -14,7 +14,7 @@ use crate::{ArenaDoc, Config, PrettyPrint}; /// - **Arena methods** (via `Deref`): `text()`, `nil()`, /// `line_()`, etc. for building document fragments. /// - **IR printing methods**: `print_statement()`, `print_block()`, -/// `print_region()`, etc. for rendering structured IR nodes. +/// `print_cfg()`, etc. for rendering structured IR nodes. /// /// For most use cases, prefer [`PrettyPrintExt::render`] or /// [`PrettyPrintExt::sprint`] which construct and use a `Document` internally. diff --git a/crates/kirin-prettyless/src/document/ir_render.rs b/crates/kirin-prettyless/src/document/ir_render.rs index 99b76ccc5f..5eb1f85357 100644 --- a/crates/kirin-prettyless/src/document/ir_render.rs +++ b/crates/kirin-prettyless/src/document/ir_render.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use kirin_ir::{ - Block, DiGraph, Dialect, GetInfo, GlobalSymbol, Id, Port, Region, SSAInfo, SSAValue, Signature, + Block, Cfg, DiGraph, Dialect, GetInfo, GlobalSymbol, Id, Port, SSAInfo, SSAValue, Signature, SpecializedFunction, StagedFunction, Statement, Symbol, UnGraph, }; use petgraph::visit::IntoNodeReferences; @@ -153,10 +153,10 @@ where header + self.text(" {") + self.block_indent(inner) + self.line_() + self.text("}") } - /// Pretty print a region with its blocks. - pub fn print_region(&'a self, region: &Region) -> ArenaDoc<'a> { + /// Pretty print a CFG with its blocks. + pub fn print_cfg(&'a self, cfg: &Cfg) -> ArenaDoc<'a> { let mut inner = self.nil(); - for block in region.blocks(self.stage) { + for block in cfg.blocks(self.stage) { inner += self.print_block(&block); inner += self.line_(); } @@ -458,10 +458,10 @@ where inner } - /// Print a Region body only: blocks without outer braces. - pub fn print_region_body_only(&'a self, region: &Region) -> ArenaDoc<'a> { + /// Print a Cfg body only: blocks without outer braces. + pub fn print_cfg_body_only(&'a self, cfg: &Cfg) -> ArenaDoc<'a> { let mut inner = self.nil(); - for block in region.blocks(self.stage) { + for block in cfg.blocks(self.stage) { inner += self.print_block(&block); inner += self.line_(); } diff --git a/crates/kirin-prettyless/src/tests/edge_cases.rs b/crates/kirin-prettyless/src/tests/edge_cases.rs index 8c31c868e1..b9be75fc66 100644 --- a/crates/kirin-prettyless/src/tests/edge_cases.rs +++ b/crates/kirin-prettyless/src/tests/edge_cases.rs @@ -108,10 +108,10 @@ fn test_print_block_multiple_unnamed_args() { insta::assert_snapshot!(buf); } -// --- Region with multiple blocks --- +// --- Cfg with multiple blocks --- #[test] -fn test_print_region_multiple_blocks() { +fn test_print_cfg_multiple_blocks() { let mut stage: BuilderStageInfo = BuilderStageInfo::default(); let a = SimpleLanguage::op_constant(&mut stage, 1i64); @@ -126,8 +126,8 @@ fn test_print_region_multiple_blocks() { let ret3 = SimpleLanguage::op_return(&mut stage, c.result); let block3 = stage.block().stmt(c).terminator(ret3).new(); - let region = stage - .region() + let cfg = stage + .cfg() .add_block(block1) .add_block(block2) .add_block(block3) @@ -135,7 +135,7 @@ fn test_print_region_multiple_blocks() { let stage = stage.finalize().unwrap(); let doc = Document::new(Default::default(), &stage); - let arena_doc = doc.print_region(®ion); + let arena_doc = doc.print_cfg(&cfg); let mut buf = String::new(); arena_doc.render_fmt(120, &mut buf).unwrap(); insta::assert_snapshot!(buf); @@ -309,7 +309,7 @@ fn test_staged_function_unnamed() { let a = SimpleLanguage::op_constant(ctx, 0i64); let ret = SimpleLanguage::op_return(ctx, a.result); let block = ctx.block().stmt(a).terminator(ret).new(); - let body = ctx.region().add_block(block).new(); + let body = ctx.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(ctx, body); ctx.specialize().staged_func(sf).body(fdef).new().unwrap(); }); @@ -336,7 +336,7 @@ fn test_staged_function_no_params() { let a = SimpleLanguage::op_constant(&mut stage, 0i64); let ret = SimpleLanguage::op_return(&mut stage, a.result); let block = stage.block().stmt(a).terminator(ret).new(); - let body = stage.region().add_block(block).new(); + let body = stage.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(&mut stage, body); let _ = stage .specialize() @@ -390,7 +390,7 @@ fn test_pipeline_render_builder_write_to() { let a = SimpleLanguage::op_constant(ctx, 5i64); let ret = SimpleLanguage::op_return(ctx, a.result); let block = ctx.block().stmt(a).terminator(ret).new(); - let body = ctx.region().add_block(block).new(); + let body = ctx.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(ctx, body); ctx.specialize().staged_func(sf).body(fdef).new().unwrap(); }); @@ -426,7 +426,7 @@ fn test_function_render_builder_write_to() { let a = SimpleLanguage::op_constant(ctx, 99i64); let ret = SimpleLanguage::op_return(ctx, a.result); let block = ctx.block().stmt(a).terminator(ret).new(); - let body = ctx.region().add_block(block).new(); + let body = ctx.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(ctx, body); ctx.specialize().staged_func(sf).body(fdef).new().unwrap(); }); @@ -457,7 +457,7 @@ fn test_render_very_narrow_width() { let a = SimpleLanguage::op_constant(&mut stage, 1i64); let ret = SimpleLanguage::op_return(&mut stage, a.result); let block = stage.block().stmt(a).terminator(ret).new(); - let body = stage.region().add_block(block).new(); + let body = stage.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(&mut stage, body); let _ = stage .specialize() diff --git a/crates/kirin-prettyless/src/tests/impls.rs b/crates/kirin-prettyless/src/tests/impls.rs index 6a545ceaa8..dd0550ed4a 100644 --- a/crates/kirin-prettyless/src/tests/impls.rs +++ b/crates/kirin-prettyless/src/tests/impls.rs @@ -371,20 +371,20 @@ fn test_print_block_with_named_args() { } // ============================================================================ -// print_region tests +// print_cfg tests // ============================================================================ #[test] -fn test_print_region_empty() { +fn test_print_cfg_empty() { let mut gs: InternTable = InternTable::default(); let test_sym = gs.intern("test".to_string()); let mut stage: BuilderStageInfo = BuilderStageInfo::default(); let _ = stage.staged_function().name(test_sym).new().unwrap(); - let region = stage.region().new(); + let cfg = stage.cfg().new(); let stage = stage.finalize().unwrap(); let doc = Document::new(Default::default(), &stage); - let arena_doc = doc.print_region(®ion); + let arena_doc = doc.print_cfg(&cfg); let mut buf = String::new(); arena_doc.render_fmt(80, &mut buf).unwrap(); insta::assert_snapshot!(buf); @@ -427,7 +427,7 @@ fn test_render_builder_config() { .stmt(c) .terminator(ret) .new(); - let body = stage.region().add_block(block).new(); + let body = stage.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(&mut stage, body); let f = stage .specialize() diff --git a/crates/kirin-prettyless/src/tests/mod.rs b/crates/kirin-prettyless/src/tests/mod.rs index 692a826115..8a5ccca40b 100644 --- a/crates/kirin-prettyless/src/tests/mod.rs +++ b/crates/kirin-prettyless/src/tests/mod.rs @@ -21,7 +21,7 @@ impl PrettyPrint for SimpleLanguage { } SimpleLanguage::Constant(value, _res) => doc.text(format!("constant {}", value)), SimpleLanguage::Return(retval) => doc.text("return ") + retval.pretty_print(doc), - SimpleLanguage::Function(region, _) => doc.print_region(region), + SimpleLanguage::Function(cfg, _) => doc.print_cfg(cfg), } } } @@ -71,7 +71,7 @@ fn create_test_function() -> ( .terminator(ret) .new(); - let body = stage.region().add_block(block_a).add_block(block_b).new(); + let body = stage.cfg().add_block(block_a).add_block(block_b).new(); let fdef = SimpleLanguage::op_function(&mut stage, body); let f = stage .specialize() diff --git a/crates/kirin-prettyless/src/tests/pipeline.rs b/crates/kirin-prettyless/src/tests/pipeline.rs index 8ba5a27c46..04636aa454 100644 --- a/crates/kirin-prettyless/src/tests/pipeline.rs +++ b/crates/kirin-prettyless/src/tests/pipeline.rs @@ -21,7 +21,7 @@ fn test_pipeline_function_print() { let a = SimpleLanguage::op_constant(ctx0, 42i64); let ret = SimpleLanguage::op_return(ctx0, a.result); let block = ctx0.block().stmt(a).terminator(ret).new(); - let body = ctx0.region().add_block(block).new(); + let body = ctx0.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(ctx0, body); ctx0.specialize().staged_func(sf0).body(fdef).new().unwrap(); }); @@ -46,7 +46,7 @@ fn test_pipeline_function_print() { let c = SimpleLanguage::op_add(ctx1, a.result, b.result); let ret = SimpleLanguage::op_return(ctx1, c.result); let block = ctx1.block().stmt(a).stmt(b).stmt(c).terminator(ret).new(); - let body = ctx1.region().add_block(block).new(); + let body = ctx1.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(ctx1, body); ctx1.specialize().staged_func(sf1).body(fdef).new().unwrap(); }); @@ -78,7 +78,7 @@ fn test_pipeline_unnamed_stage() { let a = SimpleLanguage::op_constant(ctx, 7i64); let ret = SimpleLanguage::op_return(ctx, a.result); let block = ctx.block().stmt(a).terminator(ret).new(); - let body = ctx.region().add_block(block).new(); + let body = ctx.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(ctx, body); ctx.specialize().staged_func(sf).body(fdef).new().unwrap(); }); diff --git a/crates/kirin-prettyless/src/tests/sprint_with_globals.rs b/crates/kirin-prettyless/src/tests/sprint_with_globals.rs index 6bd7edb6bb..800af41212 100644 --- a/crates/kirin-prettyless/src/tests/sprint_with_globals.rs +++ b/crates/kirin-prettyless/src/tests/sprint_with_globals.rs @@ -14,7 +14,7 @@ fn test_sprint_with_globals() { let a = SimpleLanguage::op_constant(&mut stage, 42i64); let ret = SimpleLanguage::op_return(&mut stage, a.result); let block = stage.block().stmt(a).terminator(ret).new(); - let body = stage.region().add_block(block).new(); + let body = stage.cfg().add_block(block).new(); let fdef = SimpleLanguage::op_function(&mut stage, body); let _ = stage .specialize() diff --git a/crates/kirin-prettyless/src/traits.rs b/crates/kirin-prettyless/src/traits.rs index 528f463c89..432b585a2f 100644 --- a/crates/kirin-prettyless/src/traits.rs +++ b/crates/kirin-prettyless/src/traits.rs @@ -16,10 +16,10 @@ use prettyless::DocAllocator; /// should produce output that roundtrips through the parser. /// /// The bounds on `L` (`PrettyPrint` and `Type: Display`) are required because: -/// - `L: PrettyPrint` is needed to print nested Block/Region structures +/// - `L: PrettyPrint` is needed to print nested Block/Cfg structures /// - `Type: Display` is needed to print type annotations (`:type` format) /// -/// For IR nodes that require context (like `Statement`, `Block`, `Region`), use +/// For IR nodes that require context (like `Statement`, `Block`, `Cfg`), use /// the convenience methods provided on `Document` instead. /// /// # Example diff --git a/crates/kirin-scf/src/interpreter.rs b/crates/kirin-scf/src/interpreter.rs index 8de0f1acef..e9fc9994bb 100644 --- a/crates/kirin-scf/src/interpreter.rs +++ b/crates/kirin-scf/src/interpreter.rs @@ -108,7 +108,7 @@ where // Demand converges value-by-value on the sparse backward engine's worklist, // so structured bodies need no walk and loops need no frame fixpoint: this // rule re-runs whenever a result or a body block parameter it feeds rises -// (the owning statement is the body's *feeder* in the region topology). +// (the owning statement is the body's *feeder* in the cfg topology). /// Backward demand for `scf.if`: the condition is an unconditional control /// root (consistent with `cf.cond_br`); a body's yield slot is demanded iff diff --git a/crates/kirin-scf/src/lib.rs b/crates/kirin-scf/src/lib.rs index 5e8aeea08f..4f1c52c9f5 100644 --- a/crates/kirin-scf/src/lib.rs +++ b/crates/kirin-scf/src/lib.rs @@ -3,7 +3,7 @@ //! This dialect provides high-level control flow operations that model //! structured programming constructs. Unlike `kirin-cf` which uses //! unstructured branches, `kirin-scf` operations have lexically scoped -//! regions with guaranteed single-entry semantics. +//! cfgs with guaranteed single-entry semantics. //! //! # Operations //! @@ -13,9 +13,9 @@ //! | `for %iv in %lo..%hi step %s iter_args(..) do {..} [-> types]` | Counted loop with multi-accumulator support | //! | `yield [%v1, %v2, ..]` | Terminates an SCF body block, yielding 0-to-N values to the parent | //! -//! # Block vs Region +//! # Block vs Cfg //! -//! All body fields use `Block` (not `Region`) because MLIR's `scf.if` and +//! All body fields use `Block` (not `Cfg`) because MLIR's `scf.if` and //! `scf.for` have the `SingleBlock` + `SingleBlockImplicitTerminator` //! traits. A `yield` terminates each body block. //! diff --git a/crates/kirin-scf/src/tests.rs b/crates/kirin-scf/src/tests.rs index b981b2fd47..e87ca9678e 100644 --- a/crates/kirin-scf/src/tests.rs +++ b/crates/kirin-scf/src/tests.rs @@ -1,5 +1,5 @@ use kirin::ir::{ - HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, + HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsSpeculatable, IsTerminator, TestSSAValue, }; use kirin_test_types::UnitType; @@ -90,8 +90,8 @@ fn yield_no_blocks() { } #[test] -fn yield_no_regions() { - assert_eq!(make_yield().regions().count(), 0); +fn yield_no_cfgs() { + assert_eq!(make_yield().cfgs().count(), 0); } // --- Clone + PartialEq for Yield --- diff --git a/crates/kirin-test-languages/src/arith_function_language.rs b/crates/kirin-test-languages/src/arith_function_language.rs index 487e3aa342..898da14da0 100644 --- a/crates/kirin-test-languages/src/arith_function_language.rs +++ b/crates/kirin-test-languages/src/arith_function_language.rs @@ -1,7 +1,7 @@ use kirin_arith::{Arith, ArithType}; use kirin_cf::ControlFlow; use kirin_function::Return; -use kirin_ir::{Dialect, Region, Signature}; +use kirin_ir::{Cfg, Dialect, Signature}; /// Test language: Function + Arith + ControlFlow + Return. /// Used for arith pipeline roundtrips and as bare (no-namespace) language. @@ -17,7 +17,7 @@ pub enum ArithFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { - body: Region, + body: Cfg, sig: Signature, }, #[wraps] diff --git a/crates/kirin-test-languages/src/bitwise_function_language.rs b/crates/kirin-test-languages/src/bitwise_function_language.rs index 9f82f925c1..affc926eec 100644 --- a/crates/kirin-test-languages/src/bitwise_function_language.rs +++ b/crates/kirin-test-languages/src/bitwise_function_language.rs @@ -2,7 +2,7 @@ use kirin_arith::ArithType; use kirin_bitwise::Bitwise; use kirin_cf::ControlFlow; use kirin_function::Return; -use kirin_ir::{Dialect, Region, Signature}; +use kirin_ir::{Cfg, Dialect, Signature}; /// Test language: Function + Bitwise + ControlFlow + Return. /// Used for bitwise pipeline roundtrip tests. @@ -18,7 +18,7 @@ pub enum BitwiseFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { - body: Region, + body: Cfg, sig: Signature, }, #[wraps] diff --git a/crates/kirin-test-languages/src/callable_language.rs b/crates/kirin-test-languages/src/callable_language.rs index 6196ae001b..12e3021bdc 100644 --- a/crates/kirin-test-languages/src/callable_language.rs +++ b/crates/kirin-test-languages/src/callable_language.rs @@ -1,6 +1,6 @@ use kirin_arith::ArithType; use kirin_function::{Bind, Call, Return}; -use kirin_ir::{Dialect, Region, Signature}; +use kirin_ir::{Cfg, Dialect, Signature}; /// Test language: Function + Bind + Call + Return. /// Used for function call/bind roundtrip tests. @@ -16,7 +16,7 @@ pub enum CallableLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { - body: Region, + body: Cfg, sig: Signature, }, #[wraps] diff --git a/crates/kirin-test-languages/src/namespaced_language.rs b/crates/kirin-test-languages/src/namespaced_language.rs index 137ecf8030..a4cdbe1f59 100644 --- a/crates/kirin-test-languages/src/namespaced_language.rs +++ b/crates/kirin-test-languages/src/namespaced_language.rs @@ -1,7 +1,7 @@ use kirin_arith::{Arith, ArithType}; use kirin_cf::ControlFlow; use kirin_function::Return; -use kirin_ir::{Dialect, Region, Signature}; +use kirin_ir::{Cfg, Dialect, Signature}; /// Test language with namespace prefixes on wraps variants. /// Arith ops become `arith.add`, ControlFlow becomes `cf.br`, Return becomes `func.ret`. @@ -17,7 +17,7 @@ pub enum NamespacedLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { - body: Region, + body: Cfg, sig: Signature, }, #[wraps] diff --git a/crates/kirin-test-languages/src/simple_language.rs b/crates/kirin-test-languages/src/simple_language.rs index ea1142623c..e84c723fdc 100644 --- a/crates/kirin-test-languages/src/simple_language.rs +++ b/crates/kirin-test-languages/src/simple_language.rs @@ -1,5 +1,5 @@ use crate::{SimpleType, Value}; -use kirin_ir::{Dialect, Region, ResultValue, SSAValue}; +use kirin_ir::{Cfg, Dialect, ResultValue, SSAValue}; #[derive(Clone, Debug, PartialEq, Dialect)] #[cfg_attr(feature = "parser", derive(kirin_chumsky::HasParser))] @@ -35,5 +35,5 @@ pub enum SimpleLanguage { any(feature = "parser", feature = "pretty"), chumsky(format = "$function {0}") )] - Function(Region, #[kirin(type = SimpleType::F64)] ResultValue), + Function(Cfg, #[kirin(type = SimpleType::F64)] ResultValue), } diff --git a/crates/kirin-tuple/src/tests.rs b/crates/kirin-tuple/src/tests.rs index 4eca91299f..e93186300d 100644 --- a/crates/kirin-tuple/src/tests.rs +++ b/crates/kirin-tuple/src/tests.rs @@ -1,5 +1,5 @@ use kirin::ir::{ - HasArguments, HasBlocks, HasRegions, HasResults, HasSuccessors, IsConstant, IsPure, + HasArguments, HasBlocks, HasCfgs, HasResults, HasSuccessors, IsConstant, IsPure, IsSpeculatable, IsTerminator, TestSSAValue, }; use kirin_test_types::UnitType; @@ -73,8 +73,8 @@ fn new_tuple_no_blocks() { } #[test] -fn new_tuple_no_regions() { - assert_eq!(make_new_tuple().regions().count(), 0); +fn new_tuple_no_cfgs() { + assert_eq!(make_new_tuple().cfgs().count(), 0); } // --- Unpack: not a terminator --- @@ -127,8 +127,8 @@ fn unpack_no_blocks() { } #[test] -fn unpack_no_regions() { - assert_eq!(make_unpack().regions().count(), 0); +fn unpack_no_cfgs() { + assert_eq!(make_unpack().cfgs().count(), 0); } // --- Clone + PartialEq --- diff --git a/docs/design/formalism/state-environment-model.md b/docs/design/formalism/state-environment-model.md index 31efc6ee52..02ef0bd976 100644 --- a/docs/design/formalism/state-environment-model.md +++ b/docs/design/formalism/state-environment-model.md @@ -106,7 +106,7 @@ This is performed by engine/frame protocol, not by dialect statements. Structured control (`scf.if`, `scf.for`) is represented by `Scope`: -- `body: ScopeBody` (`Block`, `Region`, or `Immediate`) +- `body: ScopeBody` (`Block`, `Cfg`, or `Immediate`) - `args: Product` (entry arguments) - `results: Product` (landing slots) - `hook: Option>>` diff --git a/docs/design/formalism/syntax.md b/docs/design/formalism/syntax.md index 25501a9342..c6a811bcf7 100644 --- a/docs/design/formalism/syntax.md +++ b/docs/design/formalism/syntax.md @@ -7,8 +7,8 @@ just enough formal shorthand to compose with Parts II-IV. ## Reading Recipe -- **Formal read:** Treat this as the grammar-level domain of `s` (statement), owners (`Region`/`Block`), and SSA carriers consumed by the judgment. -- **API read:** Verify mappings in `crates/kirin-ir/src/{pipeline.rs,stage/info.rs,language.rs,node/{function/*,region.rs,block.rs,stmt.rs,ssa.rs},product.rs}` and stage/language composition in `example/toy-lang/src/{language.rs,stage.rs}`. +- **Formal read:** Treat this as the grammar-level domain of `s` (statement), owners (`Cfg`/`Block`), and SSA carriers consumed by the judgment. +- **API read:** Verify mappings in `crates/kirin-ir/src/{pipeline.rs,stage/info.rs,language.rs,node/{function/*,cfg.rs,block.rs,stmt.rs,ssa.rs},product.rs}` and stage/language composition in `example/toy-lang/src/{language.rs,stage.rs}`. The formal names below map directly to concrete Rust IR/runtime types. @@ -20,7 +20,7 @@ The formal names below map directly to concrete Rust IR/runtime types. | `Function` | `FunctionInfo` / `Function` | [`crates/kirin-ir/src/node/function/generic.rs`](../../../crates/kirin-ir/src/node/function/generic.rs) | | `StagedFunction` | `StagedFunctionInfo` / `StagedFunction` | [`crates/kirin-ir/src/node/function/staged.rs`](../../../crates/kirin-ir/src/node/function/staged.rs) | | `SpecializedFunction` | `SpecializedFunctionInfo` / `SpecializedFunction` | [`crates/kirin-ir/src/node/function/specialized.rs`](../../../crates/kirin-ir/src/node/function/specialized.rs) | -| `Region` | `RegionInfo` / `Region` | [`crates/kirin-ir/src/node/region.rs`](../../../crates/kirin-ir/src/node/region.rs) | +| `Cfg` | `CfgInfo` / `Cfg` | [`crates/kirin-ir/src/node/cfg.rs`](../../../crates/kirin-ir/src/node/cfg.rs) | | `Block` | `BlockInfo` / `Block` / `Successor` | [`crates/kirin-ir/src/node/block.rs`](../../../crates/kirin-ir/src/node/block.rs) | | `Statement` | `StatementInfo` / `Statement` | [`crates/kirin-ir/src/node/stmt.rs`](../../../crates/kirin-ir/src/node/stmt.rs) | | `SSAValue` | `SSAValue`, `ResultValue`, `BlockArgument` | [`crates/kirin-ir/src/node/ssa.rs`](../../../crates/kirin-ir/src/node/ssa.rs) | @@ -34,7 +34,7 @@ Kirin syntax for interpreter purposes is SSA IR over staged pipelines: - `StageInfo` is per-stage storage for one language `L`. - `L: Dialect` is the stage language (often an enum wrapping multiple dialects). - `Function -> StagedFunction -> SpecializedFunction` is the callable hierarchy. -- `Region -> Block -> Statement` is executable structure. +- `Cfg -> Block -> Statement` is executable structure. - `SSAValue` connects operands/results/block arguments across statements. The interpreter executes this graph-like SSA structure, not an expression tree. @@ -50,8 +50,8 @@ Function ::= FunctionInfo + staged variants StagedFunction ::= stage-specific callable variant Specialized ::= concrete specialization with body Statement -Statement ::= dialect definition + operands + results + nested blocks/regions/successors -Region ::= Block* +Statement ::= dialect definition + operands + results + nested blocks/cfgs/successors +Cfg ::= Block* Block ::= BlockArgument* ; Statement* ; optional terminator cache SSAValue ::= ResultValue | BlockArgument | Port ``` diff --git a/docs/design/graph-body-rust-interface.md b/docs/design/graph-body-rust-interface.md index 16ff501db6..a6f4908d65 100644 --- a/docs/design/graph-body-rust-interface.md +++ b/docs/design/graph-body-rust-interface.md @@ -184,7 +184,7 @@ pub enum FieldCategory { Result, Block, Successor, - Region, + Cfg, DiGraph, // new UnGraph, // new Symbol, @@ -204,7 +204,7 @@ pub enum FieldData { Field classification in `parse_field` adds type-name checks for `"DiGraph"` and `"UnGraph"`, supporting `Single`, `Vec`, and `Option` collection wrapping. -`#[derive(Dialect)]` auto-generates `HasDigraphs`/`HasUngraphs` impls for all dialects. Dialects with no `DiGraph`/`UnGraph` fields get empty-iterator impls (same pattern as `HasBlocks`/`HasRegions`). +`#[derive(Dialect)]` auto-generates `HasDigraphs`/`HasUngraphs` impls for all dialects. Dialects with no `DiGraph`/`UnGraph` fields get empty-iterator impls (same pattern as `HasBlocks`/`HasCfgs`). ### #[kirin(edge)] Attribute diff --git a/docs/design/graph-ir-node.md b/docs/design/graph-ir-node.md index 4fa9d45e82..7682b97d97 100644 --- a/docs/design/graph-ir-node.md +++ b/docs/design/graph-ir-node.md @@ -1,6 +1,6 @@ # Native Graph IR Node — Text Format and Semantics Design -This design introduces two new IR body kinds — `digraph` and `ungraph` — alongside Block and Region. A graph body uses standard statement syntax where SSAValues represent edges. The leading keyword (`^`, `digraph`, `ungraph`) selects the backing storage: Block (linked list), petgraph DiGraph, or petgraph UnGraph. +This design introduces two new IR body kinds — `digraph` and `ungraph` — alongside Block and CFG (`Cfg`). A graph body uses standard statement syntax where SSAValues represent edges. The leading keyword (`^`, `digraph`, `ungraph`) selects the backing storage: Block (linked list), petgraph DiGraph, or petgraph UnGraph. - For **directed graphs**, the text format follows MLIR graph region semantics (relaxed dominance, SSA def-use = directed edges). - For **undirected graphs**, `edge`-prefixed statements introduce edge SSAValues, and statements that share edge references are connected. diff --git a/docs/design/hybrid_ir_visualization.dot b/docs/design/hybrid_ir_visualization.dot index 8a70dff221..43700045b0 100644 --- a/docs/design/hybrid_ir_visualization.dot +++ b/docs/design/hybrid_ir_visualization.dot @@ -57,8 +57,8 @@ digraph kirin { color="#1a73e8" fontsize=12 - subgraph cluster_region { - label=<Region> + subgraph cluster_cfg { + label=<CFG> style="dashed,filled" fillcolor="#ecf0f8" color="#7baaf7" diff --git a/docs/design/hybrid_ir_visualization.svg b/docs/design/hybrid_ir_visualization.svg index 8e66dd1f69..5beffb0f2a 100644 --- a/docs/design/hybrid_ir_visualization.svg +++ b/docs/design/hybrid_ir_visualization.svg @@ -46,9 +46,9 @@ (i64) → i64 -cluster_region +cluster_cfg -Region +CFG cluster_entry diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index e3479bf7a6..74d98673a8 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -232,7 +232,7 @@ Like `Interpretable`, it receives the engine `interp` directly (function entry i forward-only, so there is no `Semantics` parameter). Statements that define function bodies (e.g. `kirin_function::Function`) -return the `FunctionBody { region, args }` to enter on invocation (the +return the `FunctionBody { cfg, args }` to enter on invocation (the function-call entry descriptor — not a structured-control abstraction). On language enums it is derived; `#[callable]` marks the variants that forward, all others report `NotCallable`. @@ -295,7 +295,7 @@ A generic **frame-stack driver**: it pops the top frame, calls `Frame::step`, and applies the returned `FrameEffect` (`Continue` / `Push` / `Done` / `Complete`) — it owns *no* traversal logic itself. Traversal lives in the frames. The default total frame type `StandardFrame` wraps the standard -`BodyFrame` (walks a function-body region CFG, or a single body block that +`BodyFrame` (walks a function-body CFG, or a single body block that completes on `Yield` — `Jump` retargets it, `Return` completes it) and `CallFrame` (dispatch a callee, await its `Return`). The dialect-produced `SparseForwardEffect` is consumed by `BodyFrame`, which maps it to a `FrameEffect` @@ -332,7 +332,7 @@ keying and merge; the interprocedural protocol (summary tables, caller recording) stays atomic in the engine. Three nested fixpoints, expressed as frames: -- **CFG**: each function body region is a block worklist; block parameters +- **CFG**: each function-body CFG is a block worklist; block parameters join across incoming edges and widen after `widen_after` visits — `cf` back-edge loops converge. - **Pushed loop frames**: a dialect loop frame (e.g. `scf.for`'s @@ -365,7 +365,7 @@ Two mechanisms keep engines generic over stage enums: matching `Interpretable`/`FunctionEntry` rule. - `StageQuery` — a bound bundle over kirin-ir's `StageDispatch`/`StageAction` machinery for language-independent IR facts (block parameters, statement - order, region entry, specialization lookup, symbol resolution). Satisfied + order, CFG entry, specialization lookup, symbol resolution). Satisfied automatically by any stage enum; used by engines and linkers internally. ## Custom traversal and policies diff --git a/example/simple.rs b/example/simple.rs index abde1e7547..d635f59d2f 100644 --- a/example/simple.rs +++ b/example/simple.rs @@ -10,20 +10,20 @@ use kirin_function::{Bind, Call, Return}; /// Higher-level language: structured control flow (`if`) and lexical /// lambdas that capture variables from the enclosing scope. /// -/// Block/Region-containing dialect types (SCF, Lambda) are inlined here +/// Block/Cfg-containing dialect types (SCF, Lambda) are inlined here /// to demonstrate the format-string parser path for inline variants. /// These types can also be composed via `#[wraps]` — see `toy-lang`. #[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint)] #[kirin(builders, type = ArithType)] enum HighLevel { #[chumsky(format = "{body}")] - Function { body: Region }, + Function { body: Cfg }, #[chumsky(format = "$lambda {name} captures({captures}) {body} -> {res:type}")] Lambda { name: Symbol, captures: Vec, - body: Region, + body: Cfg, #[kirin(type = ArithType::placeholder())] res: ResultValue, }, @@ -49,7 +49,7 @@ enum HighLevel { #[kirin(builders, type = ArithType)] enum LowLevel { #[chumsky(format = "{body}")] - Function { body: Region }, + Function { body: Cfg }, #[wraps] Arith(Arith), diff --git a/example/toy-lang/src/interpreter/mod.rs b/example/toy-lang/src/interpreter/mod.rs index 6fb55a7fa3..0638f0bf25 100644 --- a/example/toy-lang/src/interpreter/mod.rs +++ b/example/toy-lang/src/interpreter/mod.rs @@ -14,7 +14,7 @@ mod tests; pub use error::ToyError; pub use frame::{ToyAbstractFrame, ToyDenseBackwardFrame, ToyFrame}; -use kirin::prelude::{CompileStage, GetInfo, Pipeline, Region, UniqueLiveSpecializationError}; +use kirin::prelude::{Cfg, CompileStage, GetInfo, Pipeline, UniqueLiveSpecializationError}; use kirin_constprop::{ConstPropContext, ConstPropValue}; use kirin_function::{Lexical, Lifted}; use kirin_interpreter::InterpreterError; @@ -113,12 +113,12 @@ pub fn analyze_constprop( expect_single(analysis.analyze_by_name(stage_name, function_name, args.iter().cloned())?) } -/// The body region of `function_name`'s specialization at `stage_name`. -fn function_region( +/// The body cfg of `function_name`'s specialization at `stage_name`. +fn function_cfg( pipeline: &Pipeline, stage_name: &str, function_name: &str, -) -> Result<(CompileStage, Region), InterpreterError> { +) -> Result<(CompileStage, Cfg), InterpreterError> { let stage_id = pipeline .stage_by_name(stage_name) .ok_or_else(|| InterpreterError::MissingStageName(stage_name.into()))?; @@ -129,7 +129,7 @@ fn function_region( .stage(stage_id) .ok_or(InterpreterError::MissingStage(stage_id))?; - let region = match stage { + let cfg = match stage { Stage::Source(info) => { let staged_info = staged .get_info(info) @@ -151,8 +151,8 @@ fn function_region( .ok_or(InterpreterError::Custom("specialized function has no body"))?; match spec_info.body().definition(info) { HighLevel::Lexical(Lexical::Function(function)) => { - use kirin::prelude::HasRegionBody; - *function.region() + use kirin::prelude::HasCfgBody; + *function.cfg() } _ => return Err(InterpreterError::Custom("expected a function body")), } @@ -178,14 +178,14 @@ fn function_region( .ok_or(InterpreterError::Custom("specialized function has no body"))?; match spec_info.body().definition(info) { LowLevel::Lifted(Lifted::Function(function)) => { - use kirin::prelude::HasRegionBody; - *function.region() + use kirin::prelude::HasCfgBody; + *function.cfg() } _ => return Err(InterpreterError::Custom("expected a function body")), } } }; - Ok((stage_id, region)) + Ok((stage_id, cfg)) } /// Run both liveness analyses over `function_name`'s body at `stage_name`: @@ -196,10 +196,10 @@ pub fn analyze_liveness( stage_name: &str, function_name: &str, ) -> Result<(DemandResult, DenseLivenessResult), InterpreterError> { - let (stage, region) = function_region(pipeline, stage_name, function_name)?; - let demand = kirin_liveness::analyze_demand(pipeline, stage, region)?; + let (stage, cfg) = function_cfg(pipeline, stage_name, function_name)?; + let demand = kirin_liveness::analyze_demand(pipeline, stage, cfg)?; let mut engine: ToyDenseLiveness<'_> = ToyDenseLiveness::new(pipeline); - engine.analyze(stage, region)?; - let dense = DenseLivenessResult::from_engine(&mut engine, stage, region)?; + engine.analyze(stage, cfg)?; + let dense = DenseLivenessResult::from_engine(&mut engine, stage, cfg)?; Ok((demand, dense)) } diff --git a/example/toy-lang/src/interpreter/tests.rs b/example/toy-lang/src/interpreter/tests.rs index 95f8d846cb..3869fd65d0 100644 --- a/example/toy-lang/src/interpreter/tests.rs +++ b/example/toy-lang/src/interpreter/tests.rs @@ -262,10 +262,10 @@ fn build_cross_stage_specialized_pipeline() -> Pipeline { .stmt(call) .terminator(ret) .new(); - let region = builder.region().add_block(block).new(); + let cfg = builder.cfg().add_block(block).new(); let body = Function::::new( builder, - region, + cfg, Signature::new(vec![ArithType::I64], ArithType::I64, ()), ); builder @@ -897,8 +897,7 @@ mod advanced { mod demand { use kirin::prelude::{ - CompileStage, GetInfo, HasRegionBody, HasResults, ParsePipelineText, Pipeline, Region, - SSAValue, + Cfg, CompileStage, GetInfo, HasCfgBody, HasResults, ParsePipelineText, Pipeline, SSAValue, }; use kirin_arith::{Arith, ArithValue}; use kirin_function::Lexical; @@ -913,8 +912,8 @@ mod demand { pipeline } - /// The source stage id, its info, and the body region of `name`. - pub(super) fn source_region(pipeline: &Pipeline, name: &str) -> (CompileStage, Region) { + /// The source stage id, its info, and the body cfg of `name`. + pub(super) fn source_cfg(pipeline: &Pipeline, name: &str) -> (CompileStage, Cfg) { let stage_id = pipeline.stage_by_name("source").expect("source stage"); let Stage::Source(info) = pipeline.stage(stage_id).expect("stage info") else { panic!("source stage holds HighLevel"); @@ -924,20 +923,20 @@ mod demand { .expect("staged function"); let sf_info = sf.get_info(info).expect("staged function info"); let body = *sf_info.specializations()[0].body(); - let region = match body.definition(info) { - HighLevel::Lexical(Lexical::Function(function)) => *function.region(), + let cfg = match body.definition(info) { + HighLevel::Lexical(Lexical::Function(function)) => *function.cfg(), other => panic!("expected a function body, got {other:?}"), }; - (stage_id, region) + (stage_id, cfg) } - /// The parameters of the region's entry block. - pub(super) fn entry_params(pipeline: &Pipeline, region: Region) -> Vec { + /// The parameters of the CFG's entry block. + pub(super) fn entry_params(pipeline: &Pipeline, cfg: Cfg) -> Vec { let stage_id = pipeline.stage_by_name("source").expect("source stage"); let Stage::Source(info) = pipeline.stage(stage_id).expect("stage info") else { panic!("source stage holds HighLevel"); }; - let block = region.blocks(info).next().expect("entry block"); + let block = cfg.blocks(info).next().expect("entry block"); block .expect_info(info) .arguments @@ -948,18 +947,18 @@ mod demand { } /// Find something by matching statement definitions anywhere in the - /// region (including scf bodies, via the topology's nested-block + /// cfg (including scf bodies, via the topology's nested-block /// enumeration). pub(super) fn find_value( pipeline: &Pipeline, - region: Region, + cfg: Cfg, select: impl Fn(&HighLevel) -> Option, ) -> R { let stage_id = pipeline.stage_by_name("source").expect("source stage"); let Stage::Source(info) = pipeline.stage(stage_id).expect("stage info") else { panic!("source stage holds HighLevel"); }; - let topology = kirin_interpreter::region_topology(info, ®ion); + let topology = kirin_interpreter::cfg_topology(info, &cfg); for block in &topology.blocks { for &stmt in &block.stmts { if let Some(value) = select(stmt.definition(info)) { @@ -967,16 +966,12 @@ mod demand { } } } - panic!("no matching statement in region"); + panic!("no matching statement in cfg"); } /// The result of the `constant -> i64` statement. - pub(super) fn constant_result( - pipeline: &Pipeline, - region: Region, - value: i64, - ) -> SSAValue { - find_value(pipeline, region, |definition| match definition { + pub(super) fn constant_result(pipeline: &Pipeline, cfg: Cfg, value: i64) -> SSAValue { + find_value(pipeline, cfg, |definition| match definition { HighLevel::Constant(constant) if constant.value == ArithValue::I64(value) => { Some(constant.result.into()) } @@ -1008,11 +1003,11 @@ specialize @source fn @if_body(i64) -> i64 { #[test] fn scf_if_body_demand_follows_result_demand() { let pipeline = parse(IF_BODY_DEMAND); - let (stage, region) = source_region(&pipeline, "if_body"); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = source_cfg(&pipeline, "if_body"); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let cond = entry_params(&pipeline, region)[0]; - let if_result = find_value(&pipeline, region, |definition| match definition { + let cond = entry_params(&pipeline, cfg)[0]; + let if_result = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Structured(_) => definition.results().next().map(|r| SSAValue::from(*r)), _ => None, }); @@ -1020,15 +1015,15 @@ specialize @source fn @if_body(i64) -> i64 { assert!(result.is_demanded(if_result), "ret demands the if result"); assert!(result.is_demanded(cond), "condition is a control root"); assert!( - result.is_demanded(constant_result(&pipeline, region, 1)), + result.is_demanded(constant_result(&pipeline, cfg, 1)), "then-arm yield operand feeds the demanded result" ); assert!( - result.is_demanded(constant_result(&pipeline, region, 2)), + result.is_demanded(constant_result(&pipeline, cfg, 2)), "else-arm yield operand feeds the demanded result" ); assert!( - !result.is_demanded(constant_result(&pipeline, region, 9)), + !result.is_demanded(constant_result(&pipeline, cfg, 9)), "a dead constant inside a body stays dead" ); } @@ -1057,20 +1052,20 @@ specialize @source fn @if_dead(i64) -> i64 { #[test] fn scf_if_dead_result_keeps_only_condition() { let pipeline = parse(IF_DEAD_RESULT); - let (stage, region) = source_region(&pipeline, "if_dead"); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = source_cfg(&pipeline, "if_dead"); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let cond = entry_params(&pipeline, region)[0]; - let if_result = find_value(&pipeline, region, |definition| match definition { + let cond = entry_params(&pipeline, cfg)[0]; + let if_result = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Structured(_) => definition.results().next().map(|r| SSAValue::from(*r)), _ => None, }); assert!(result.is_demanded(cond), "condition is a control root"); - assert!(result.is_demanded(constant_result(&pipeline, region, 0))); + assert!(result.is_demanded(constant_result(&pipeline, cfg, 0))); assert!(!result.is_demanded(if_result)); - assert!(!result.is_demanded(constant_result(&pipeline, region, 1))); - assert!(!result.is_demanded(constant_result(&pipeline, region, 2))); + assert!(!result.is_demanded(constant_result(&pipeline, cfg, 1))); + assert!(!result.is_demanded(constant_result(&pipeline, cfg, 2))); } pub(super) const FOR_CARRIED_DEMAND: &str = r#" @@ -1097,12 +1092,12 @@ specialize @source fn @loop_sum(i64, i64, i64) -> i64 { #[test] fn scf_for_loop_carried_demand_converges() { let pipeline = parse(FOR_CARRIED_DEMAND); - let (stage, region) = source_region(&pipeline, "loop_sum"); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = source_cfg(&pipeline, "loop_sum"); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let params = entry_params(&pipeline, region); + let params = entry_params(&pipeline, cfg); let (lo, hi, step) = (params[0], params[1], params[2]); - let next = find_value(&pipeline, region, |definition| match definition { + let next = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Arith(Arith::Add { result, .. }) => Some(SSAValue::from(*result)), _ => None, }); @@ -1112,7 +1107,7 @@ specialize @source fn @loop_sum(i64, i64, i64) -> i64 { let Stage::Source(info) = pipeline.stage(stage_id).expect("stage info") else { panic!("source stage holds HighLevel"); }; - let body = find_value(&pipeline, region, |definition| match definition { + let body = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Structured(kirin_scf::StructuredControlFlow::For(op)) => Some(op.body()), _ => None, }); @@ -1127,11 +1122,11 @@ specialize @source fn @loop_sum(i64, i64, i64) -> i64 { }; assert!( - result.is_demanded(constant_result(&pipeline, region, 0)), + result.is_demanded(constant_result(&pipeline, cfg, 0)), "init" ); assert!( - result.is_demanded(constant_result(&pipeline, region, 1)), + result.is_demanded(constant_result(&pipeline, cfg, 1)), "one" ); assert!(result.is_demanded(next), "yield slot"); @@ -1165,11 +1160,11 @@ specialize @source fn @loop_dead(i64, i64, i64) -> i64 { #[test] fn scf_for_dead_result_keeps_only_bounds() { let pipeline = parse(FOR_DEAD_RESULT); - let (stage, region) = source_region(&pipeline, "loop_dead"); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = source_cfg(&pipeline, "loop_dead"); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let params = entry_params(&pipeline, region); - let next = find_value(&pipeline, region, |definition| match definition { + let params = entry_params(&pipeline, cfg); + let next = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Arith(Arith::Add { result, .. }) => Some(SSAValue::from(*result)), _ => None, }); @@ -1177,13 +1172,13 @@ specialize @source fn @loop_dead(i64, i64, i64) -> i64 { assert!(result.is_demanded(params[0]), "bounds are control roots"); assert!(result.is_demanded(params[1])); assert!(result.is_demanded(params[2])); - assert!(result.is_demanded(constant_result(&pipeline, region, 7))); + assert!(result.is_demanded(constant_result(&pipeline, cfg, 7))); assert!( - !result.is_demanded(constant_result(&pipeline, region, 0)), + !result.is_demanded(constant_result(&pipeline, cfg, 0)), "init dead" ); assert!( - !result.is_demanded(constant_result(&pipeline, region, 1)), + !result.is_demanded(constant_result(&pipeline, cfg, 1)), "body interior dead" ); assert!(!result.is_demanded(next), "yield slot dead"); @@ -1215,24 +1210,24 @@ specialize @source fn @main(i64, i64) -> i64 { #[test] fn call_arguments_are_demand_roots() { let pipeline = parse(CALL_PURITY); - let (stage, region) = source_region(&pipeline, "main"); - let result = analyze_demand(&pipeline, stage, region).expect("analysis succeeds"); + let (stage, cfg) = source_cfg(&pipeline, "main"); + let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); - let params = entry_params(&pipeline, region); + let params = entry_params(&pipeline, cfg); let (x, y) = (params[0], params[1]); - let unused = find_value(&pipeline, region, |definition| match definition { + let unused = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Lexical(Lexical::Call(_)) => { definition.results().next().map(|r| SSAValue::from(*r)) } _ => None, }); - let deadsum = find_value(&pipeline, region, |definition| match definition { + let deadsum = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Arith(Arith::Add { result, .. }) => Some(SSAValue::from(*result)), _ => None, }); assert!(result.is_demanded(x), "call args are roots (impure)"); - assert!(result.is_demanded(constant_result(&pipeline, region, 0))); + assert!(result.is_demanded(constant_result(&pipeline, cfg, 0))); assert!(!result.is_demanded(unused), "the call result is unused"); assert!(!result.is_demanded(y), "pure add with dead result"); assert!(!result.is_demanded(deadsum)); @@ -1246,12 +1241,12 @@ specialize @source fn @main(i64, i64) -> i64 { // =========================================================================== mod dense { - use kirin::prelude::{CompileStage, Pipeline, Region, SSAValue, Statement}; + use kirin::prelude::{Cfg, CompileStage, Pipeline, SSAValue, Statement}; use kirin_arith::{Arith, ArithValue}; use kirin_liveness::{DenseLivenessResult, LiveSet, analyze_demand}; use super::demand::{FOR_CARRIED_DEMAND, IF_DEAD_RESULT}; - use super::demand::{constant_result, entry_params, find_value, parse, source_region}; + use super::demand::{constant_result, entry_params, find_value, parse, source_cfg}; use crate::interpreter::ToyDenseLiveness; use crate::language::HighLevel; use crate::stage::Stage; @@ -1261,26 +1256,25 @@ mod dense { fn analyze_dense_toy( pipeline: &Pipeline, stage: CompileStage, - region: Region, + cfg: Cfg, ) -> DenseLivenessResult { let mut engine: ToyDenseLiveness<'_> = ToyDenseLiveness::new(pipeline); - engine.analyze(stage, region).expect("analysis succeeds"); - DenseLivenessResult::from_engine(&mut engine, stage, region) - .expect("reconstruction succeeds") + engine.analyze(stage, cfg).expect("analysis succeeds"); + DenseLivenessResult::from_engine(&mut engine, stage, cfg).expect("reconstruction succeeds") } /// The statement whose definition matches `select` (anywhere in the - /// region, including scf bodies). + /// cfg, including scf bodies). fn find_statement( pipeline: &Pipeline, - region: Region, + cfg: Cfg, select: impl Fn(&HighLevel) -> bool, ) -> Statement { let stage_id = pipeline.stage_by_name("source").expect("source stage"); let Stage::Source(info) = pipeline.stage(stage_id).expect("stage info") else { panic!("source stage holds HighLevel"); }; - let topology = kirin_interpreter::region_topology(info, ®ion); + let topology = kirin_interpreter::cfg_topology(info, &cfg); for block in &topology.blocks { for &stmt in &block.stmts { if select(stmt.definition(info)) { @@ -1288,7 +1282,7 @@ mod dense { } } } - panic!("no matching statement in region"); + panic!("no matching statement in cfg"); } fn live_set(values: &[SSAValue]) -> LiveSet { @@ -1301,13 +1295,13 @@ mod dense { #[test] fn dense_per_point_inside_scf_if_arm() { let pipeline = parse(IF_DEAD_RESULT); - let (stage, region) = source_region(&pipeline, "if_dead"); - let dense = analyze_dense_toy(&pipeline, stage, region); - let demand = analyze_demand(&pipeline, stage, region).expect("demand succeeds"); + let (stage, cfg) = source_cfg(&pipeline, "if_dead"); + let dense = analyze_dense_toy(&pipeline, stage, cfg); + let demand = analyze_demand(&pipeline, stage, cfg).expect("demand succeeds"); - let cond = entry_params(&pipeline, region)[0]; - let a = constant_result(&pipeline, region, 1); - let a_const = find_statement(&pipeline, region, |definition| match definition { + let cond = entry_params(&pipeline, cfg)[0]; + let a = constant_result(&pipeline, cfg, 1); + let a_const = find_statement(&pipeline, cfg, |definition| match definition { HighLevel::Constant(constant) => constant.value == ArithValue::I64(1), _ => None::<()>.is_some(), }); @@ -1320,7 +1314,7 @@ mod dense { // The if's own points: its dead result is live after it (classic // records what the walk saw: nothing uses it, so it is NOT live), and // before it only the condition survives the arm join. - let if_stmt = find_statement(&pipeline, region, |definition| { + let if_stmt = find_statement(&pipeline, cfg, |definition| { matches!(definition, HighLevel::Structured(_)) }); assert_eq!(dense.live_before(if_stmt), Some(&live_set(&[cond]))); @@ -1340,26 +1334,26 @@ mod dense { #[test] fn dense_loop_carried_fixpoint() { let pipeline = parse(FOR_CARRIED_DEMAND); - let (stage, region) = source_region(&pipeline, "loop_sum"); - let dense = analyze_dense_toy(&pipeline, stage, region); + let (stage, cfg) = source_cfg(&pipeline, "loop_sum"); + let dense = analyze_dense_toy(&pipeline, stage, cfg); - let params = entry_params(&pipeline, region); + let params = entry_params(&pipeline, cfg); let (lo, hi, step) = (params[0], params[1], params[2]); - let init = constant_result(&pipeline, region, 0); - let one = constant_result(&pipeline, region, 1); - let (next, acc) = find_value(&pipeline, region, |definition| match definition { + let init = constant_result(&pipeline, cfg, 0); + let one = constant_result(&pipeline, cfg, 1); + let (next, acc) = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Arith(Arith::Add { lhs, result, .. }) => { Some((SSAValue::from(*result), *lhs)) } _ => None, }); - let for_stmt = find_statement(&pipeline, region, |definition| { + let for_stmt = find_statement(&pipeline, cfg, |definition| { matches!(definition, HighLevel::Structured(_)) }); - let add_stmt = find_statement(&pipeline, region, |definition| { + let add_stmt = find_statement(&pipeline, cfg, |definition| { matches!(definition, HighLevel::Arith(Arith::Add { .. })) }); - let sum = find_value(&pipeline, region, |definition| match definition { + let sum = find_value(&pipeline, cfg, |definition| match definition { HighLevel::Structured(_) => { use kirin::prelude::HasResults; definition.results().next().map(|r| SSAValue::from(*r)) diff --git a/src/dialects/scf.rs b/src/dialects/scf.rs index 0bb5091b5e..a96452b879 100644 --- a/src/dialects/scf.rs +++ b/src/dialects/scf.rs @@ -12,7 +12,7 @@ use crate::ir::{Block, HasArguments, ResultValue, SSAValue}; IsPure, IsTerminator, IsConstant, - HasRegions, + HasCfgs, HasSuccessors, )] pub enum SCFInstruction { diff --git a/tests/roundtrip/composite.rs b/tests/roundtrip/composite.rs index 1f2f9c8477..4eb48ad629 100644 --- a/tests/roundtrip/composite.rs +++ b/tests/roundtrip/composite.rs @@ -152,7 +152,7 @@ fn test_roundtrip_return() { assert_eq!(output.trim(), input); } -/// Test roundtrip for a full function with region containing multiple blocks and statements. +/// Test roundtrip for a full function with CFG containing multiple blocks and statements. #[test] fn test_roundtrip_function() { let mut stage: BuilderStageInfo = BuilderStageInfo::default(); @@ -199,7 +199,7 @@ fn test_roundtrip_function() { assert_eq!(output.trim_end(), input); } -/// Test roundtrip for a function with multiple blocks in the region. +/// Test roundtrip for a function with multiple blocks in the CFG. #[test] fn test_roundtrip_function_multiple_blocks() { let mut stage: BuilderStageInfo = BuilderStageInfo::default(); diff --git a/tests/roundtrip/constant.rs b/tests/roundtrip/constant.rs index 6f8897ab7c..348a9a4957 100644 --- a/tests/roundtrip/constant.rs +++ b/tests/roundtrip/constant.rs @@ -11,7 +11,7 @@ use kirin_test_utils::roundtrip; enum ConstantLanguage { #[chumsky(format = "fn {:name}{sig} {body}")] Function { - body: Region, + body: Cfg, sig: Signature, }, #[kirin(constant, pure)] diff --git a/tests/roundtrip/digraph.rs b/tests/roundtrip/digraph.rs index 2f6f95d62b..06080a7013 100644 --- a/tests/roundtrip/digraph.rs +++ b/tests/roundtrip/digraph.rs @@ -228,13 +228,13 @@ specialize @test fn @foo(f64) -> f64 (%x: f64) { %r = add %x, %x; ret %r; } ); } -// --- Use Case 6: Region body-only projection pipeline test --- +// --- Use Case 6: Cfg body-only projection pipeline test --- -/// A dialect using Region body-only projection. +/// A dialect using Cfg body-only projection. #[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint)] #[kirin(builders, type = SimpleType, crate = kirin::ir)] #[chumsky(crate = kirin::parsers)] -enum RegionProjectedLang { +enum CfgProjectedLang { #[chumsky(format = "$add {0}, {1}")] Add( SSAValue, @@ -247,14 +247,14 @@ enum RegionProjectedLang { /// Function body: `fn {:name}{sig} {{ {body:body} }}` #[chumsky(format = "fn {:name}{sig} {{ {body:body} }}")] FuncBody { - body: Region, + body: Cfg, sig: Signature, }, } #[test] -fn test_region_projected_pipeline_roundtrip() { - let mut pipeline = make_test_pipeline::(); +fn test_cfg_projected_pipeline_roundtrip() { + let mut pipeline = make_test_pipeline::(); let input = r#" stage @test fn @foo(f64) -> f64; @@ -262,19 +262,19 @@ specialize @test fn @foo(f64) -> f64 { ^entry(%x: f64) { %r = add %x, %x; ret %r "#; pipeline .parse(input) - .expect("should parse region projection format"); + .expect("should parse cfg projection format"); let printed = pipeline.sprint(); - let mut pipeline2 = make_test_pipeline::(); + let mut pipeline2 = make_test_pipeline::(); pipeline2 .parse(printed.trim()) - .expect("should reparse region projection format"); + .expect("should reparse cfg projection format"); let printed2 = pipeline2.sprint(); assert_eq!( printed.trim(), printed2.trim(), - "region projection pipeline roundtrip should be stable" + "cfg projection pipeline roundtrip should be stable" ); } diff --git a/tests/roundtrip/function.rs b/tests/roundtrip/function.rs index 3a7f676a5a..6a5aa388a1 100644 --- a/tests/roundtrip/function.rs +++ b/tests/roundtrip/function.rs @@ -14,7 +14,7 @@ use kirin_test_utils::roundtrip; enum SplitSigLanguage { #[chumsky(format = "fn {:name}({sig:inputs}) -> {sig:return} {body}")] Function { - body: Region, + body: Cfg, sig: Signature, }, #[wraps] @@ -124,7 +124,7 @@ specialize @A fn @main(i32) -> i32 { // --- Tests from lambda_print.rs --- -// Lambda (Region-containing) works with #[wraps] delegation. +// Lambda (Cfg-containing) works with #[wraps] delegation. #[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint)] #[kirin(builders, type = SimpleType, crate = kirin::ir)] #[chumsky(crate = kirin::parsers)] diff --git a/tests/roundtrip/scf.rs b/tests/roundtrip/scf.rs index 36f61a25b7..891e9db077 100644 --- a/tests/roundtrip/scf.rs +++ b/tests/roundtrip/scf.rs @@ -11,7 +11,7 @@ use kirin_test_utils::roundtrip; enum ScfLanguage { #[chumsky(format = "fn {:name}{sig} {body}")] Function { - body: Region, + body: Cfg, sig: Signature, }, #[wraps] diff --git a/tests/roundtrip/tuple.rs b/tests/roundtrip/tuple.rs index a7273f2b42..fd690ef7ad 100644 --- a/tests/roundtrip/tuple.rs +++ b/tests/roundtrip/tuple.rs @@ -11,7 +11,7 @@ use kirin_tuple::Tuple; enum TupleLanguage { #[chumsky(format = "fn {:name}{sig} {body}")] Function { - body: Region, + body: Cfg, sig: Signature, }, #[wraps] diff --git a/tests/simple.rs b/tests/simple.rs index 24dd2474a8..44a9de9917 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -44,7 +44,7 @@ fn test_block() { .terminator(ret) .new(); - let body = stage.region().add_block(block_a).add_block(block_b).new(); + let body = stage.cfg().add_block(block_a).add_block(block_b).new(); let fdef = SimpleLanguage::op_function(&mut stage, body); let f = stage .specialize() From 12689502f69a9e2dae9bca7bc8b355b300e455f9 Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Mon, 13 Jul 2026 16:50:54 -0400 Subject: [PATCH 3/6] Add acceptance tests for mixed language interpreter bodies This commit introduces a new test file `body_kinds.rs` that contains acceptance tests for the generic interpreter bodies. The tests cover various scenarios including: - A Cfg-bodied `main` calling a DiGraph-bodied function. - A statement inside a Cfg block that owns a DiGraph body. - A linear callable with a flat instruction list. - Ensuring that graph nodes run in topological order. These tests aim to validate the interaction between Cfg-SSA code and DiGraph computational graphs, ensuring correct execution and error handling. --- Cargo.lock | 2 + Cargo.toml | 3 + .../src/function_entry.rs | 2 +- .../src/interp_dispatch.rs | 2 +- ..._function_entry_for_callable_variants.snap | 2 +- crates/kirin-function/src/interpreter.rs | 12 +- crates/kirin-interpreter/Cargo.toml | 1 + crates/kirin-interpreter/src/core/dispatch.rs | 10 +- crates/kirin-interpreter/src/core/effect.rs | 68 ++++- crates/kirin-interpreter/src/core/error.rs | 6 + crates/kirin-interpreter/src/core/frame.rs | 14 +- crates/kirin-interpreter/src/core/mod.rs | 4 +- crates/kirin-interpreter/src/core/query.rs | 90 +++++- .../src/engines/concrete/frames.rs | 289 +++++++++++++++++- .../src/engines/concrete/interp.rs | 44 ++- .../src/engines/concrete/mod.rs | 4 +- .../src/engines/dense_backward/interp.rs | 17 +- .../src/engines/sparse_backward/interp.rs | 88 ++++-- .../src/engines/sparse_forward/interp.rs | 36 ++- crates/kirin-interpreter/src/facts/mod.rs | 5 +- .../kirin-interpreter/src/facts/topology.rs | 229 +++++++++++--- crates/kirin-interpreter/src/lib.rs | 24 +- crates/kirin-liveness/src/lib.rs | 20 +- crates/kirin-liveness/src/result.rs | 17 +- crates/kirin-test-languages/Cargo.toml | 1 + .../src/arith_function_language.rs | 6 +- .../src/graph_function_language.rs | 149 +++++++++ crates/kirin-test-languages/src/lib.rs | 4 + docs/design/interpreter/index.md | 4 +- example/toy-lang/src/interpreter/frame.rs | 11 +- example/toy-lang/src/interpreter/tests.rs | 11 +- tests/body_kinds.rs | 158 ++++++++++ 32 files changed, 1158 insertions(+), 175 deletions(-) create mode 100644 crates/kirin-test-languages/src/graph_function_language.rs create mode 100644 tests/body_kinds.rs diff --git a/Cargo.lock b/Cargo.lock index e4e149d90c..ede9dbd3e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,6 +711,7 @@ dependencies = [ "kirin-cmp", "kirin-constant", "kirin-function", + "kirin-interpreter", "kirin-ir", "kirin-lexer", "kirin-prettyless", @@ -884,6 +885,7 @@ version = "0.1.0" dependencies = [ "kirin-derive-interpreter", "kirin-ir", + "petgraph", "smallvec", "thiserror 2.0.18", ] diff --git a/Cargo.toml b/Cargo.toml index e17ca838c1..bf7ed375f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,8 +117,11 @@ kirin-cmp = { workspace = true } kirin-constant = { workspace = true } kirin-function = { workspace = true } kirin-scf = { workspace = true } +kirin-interpreter = { workspace = true } kirin-test-languages = { workspace = true, features = [ "arith-function-language", + "graph-function-language", + "interpreter", "bitwise-function-language", "callable-language", "namespaced-language", diff --git a/crates/kirin-derive-interpreter/src/function_entry.rs b/crates/kirin-derive-interpreter/src/function_entry.rs index 94f11abcae..b1ad6e3bea 100644 --- a/crates/kirin-derive-interpreter/src/function_entry.rs +++ b/crates/kirin-derive-interpreter/src/function_entry.rs @@ -114,7 +114,7 @@ fn emit_function_entry( args: #ir_crate::Product<<__EntryI as #interp_crate::Interp>::Value>, interp: &mut __EntryI, ) -> Result< - #interp_crate::FunctionBody<<__EntryI as #interp_crate::Interp>::Value>, + #interp_crate::CallableBody<<__EntryI as #interp_crate::Interp>::Value>, <__EntryI as #interp_crate::Interp>::Error, > { #body diff --git a/crates/kirin-derive-interpreter/src/interp_dispatch.rs b/crates/kirin-derive-interpreter/src/interp_dispatch.rs index 8a2149615e..7eb02631da 100644 --- a/crates/kirin-derive-interpreter/src/interp_dispatch.rs +++ b/crates/kirin-derive-interpreter/src/interp_dispatch.rs @@ -116,7 +116,7 @@ pub fn generate(input: &DeriveInput) -> Result { args: #ir_crate::Product<<__InterpI as #interp_crate::Interp>::Value>, interp: &mut __InterpI, ) -> Result< - #interp_crate::FunctionBody<<__InterpI as #interp_crate::Interp>::Value>, + #interp_crate::CallableBody<<__InterpI as #interp_crate::Interp>::Value>, <__InterpI as #interp_crate::Interp>::Error, > { match self { diff --git a/crates/kirin-derive-interpreter/src/snapshots/kirin_derive_interpreter__function_entry__tests__function_entry_for_callable_variants.snap b/crates/kirin-derive-interpreter/src/snapshots/kirin_derive_interpreter__function_entry__tests__function_entry_for_callable_variants.snap index 1da0bb6b7e..8524f2e6d5 100644 --- a/crates/kirin-derive-interpreter/src/snapshots/kirin_derive_interpreter__function_entry__tests__function_entry_for_callable_variants.snap +++ b/crates/kirin-derive-interpreter/src/snapshots/kirin_derive_interpreter__function_entry__tests__function_entry_for_callable_variants.snap @@ -14,7 +14,7 @@ where args: ::kirin::ir::Product<<__EntryI as ::kirin_interpreter::Interp>::Value>, interp: &mut __EntryI, ) -> Result< - ::kirin_interpreter::FunctionBody<<__EntryI as ::kirin_interpreter::Interp>::Value>, + ::kirin_interpreter::CallableBody<<__EntryI as ::kirin_interpreter::Interp>::Value>, <__EntryI as ::kirin_interpreter::Interp>::Error, > { match self { diff --git a/crates/kirin-function/src/interpreter.rs b/crates/kirin-function/src/interpreter.rs index 05029a515f..fbfd715dd9 100644 --- a/crates/kirin-function/src/interpreter.rs +++ b/crates/kirin-function/src/interpreter.rs @@ -1,7 +1,7 @@ use kirin::prelude::{CompileTimeValue, HasBottom, HasCfgBody, Product, SSAValue}; use kirin_interpreter::dialect::{ - CallEffect, Callee, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, - ForwardEval, FunctionBody, FunctionEntry, Interp, Interpretable, InterpreterError, + CallEffect, CallableBody, Callee, ClassicLiveness, ClassicLivenessInterp, DemandInterp, + DenseBackwardEffect, ForwardEval, FunctionEntry, Interp, Interpretable, InterpreterError, SparseForwardEffect, SparseForwardInterp, StrongDemand, }; @@ -98,8 +98,8 @@ where &self, args: Product, _interp: &mut I, - ) -> Result, I::Error> { - Ok(FunctionBody::new(*self.cfg()).args(args)) + ) -> Result, I::Error> { + Ok(CallableBody::new(*self.cfg()).args(args)) } } @@ -112,8 +112,8 @@ where &self, args: Product, _interp: &mut I, - ) -> Result, I::Error> { - Ok(FunctionBody::new(*self.cfg()).args(args)) + ) -> Result, I::Error> { + Ok(CallableBody::new(*self.cfg()).args(args)) } } diff --git a/crates/kirin-interpreter/Cargo.toml b/crates/kirin-interpreter/Cargo.toml index 6ad2250608..5fca996fca 100644 --- a/crates/kirin-interpreter/Cargo.toml +++ b/crates/kirin-interpreter/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] kirin-ir = { workspace = true } +petgraph = { workspace = true } kirin-derive-interpreter = { workspace = true, optional = true } smallvec = { workspace = true } thiserror = { workspace = true } diff --git a/crates/kirin-interpreter/src/core/dispatch.rs b/crates/kirin-interpreter/src/core/dispatch.rs index 64cd2e7d5e..19f6428f87 100644 --- a/crates/kirin-interpreter/src/core/dispatch.rs +++ b/crates/kirin-interpreter/src/core/dispatch.rs @@ -1,6 +1,6 @@ use kirin_ir::{Dialect, Product, StageInfo, StageMeta, Statement}; -use crate::{FunctionBody, Interp}; +use crate::{CallableBody, Interp}; /// Statement semantics. The single trait dialect authors implement. /// @@ -18,7 +18,7 @@ pub trait Interpretable: Dialect { /// Function-entry semantics for callable statements. /// /// Implemented by statements that define function bodies (e.g. -/// `kirin_function::Function`); describes the [`FunctionBody`] an engine enters +/// `kirin_function::Function`); describes the [`CallableBody`] an engine enters /// when the function is invoked. Derived on language enums with /// `#[derive(FunctionEntry)]` where `#[callable]` marks the variants that wrap /// callable statements. @@ -27,7 +27,7 @@ pub trait FunctionEntry: Dialect { &self, args: Product, interp: &mut I, - ) -> Result, I::Error>; + ) -> Result, I::Error>; } /// Monomorphic statement dispatch over a stage enum. @@ -54,7 +54,7 @@ pub trait InterpDispatch: StageMeta { body: Statement, args: Product, interp: &mut I, - ) -> Result, I::Error>; + ) -> Result, I::Error>; } impl InterpDispatch for StageInfo @@ -76,7 +76,7 @@ where body: Statement, args: Product, interp: &mut I, - ) -> Result, I::Error> { + ) -> Result, I::Error> { let definition = body.definition(self).clone(); definition.function_entry(args, interp) } diff --git a/crates/kirin-interpreter/src/core/effect.rs b/crates/kirin-interpreter/src/core/effect.rs index fa33570af4..58be464716 100644 --- a/crates/kirin-interpreter/src/core/effect.rs +++ b/crates/kirin-interpreter/src/core/effect.rs @@ -1,8 +1,47 @@ use kirin_ir::{ - Block, Cfg, CompileStage, Function, Product, SSAValue, SpecializedFunction, StagedFunction, - Symbol, + Block, Cfg, CompileStage, DiGraph, Function, Product, SSAValue, SpecializedFunction, + StagedFunction, Symbol, UnGraph, }; +/// A traversal descriptor: which body was the engine handed? +/// +/// Interpreter vocabulary, not an IR concept — dialect ops keep their precise +/// field types (`Block`, `Cfg`, `DiGraph`, `UnGraph`); a `Body` appears only at +/// the moment a body is handed to the interpreter (callable entry, topology +/// queries, analysis scopes). Bodies carry no semantics of their own: the +/// statement that owns a body defines what entering and exiting it means. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Body { + Block(Block), + Cfg(Cfg), + DiGraph(DiGraph), + UnGraph(UnGraph), +} + +impl From for Body { + fn from(block: Block) -> Self { + Self::Block(block) + } +} + +impl From for Body { + fn from(cfg: Cfg) -> Self { + Self::Cfg(cfg) + } +} + +impl From for Body { + fn from(graph: DiGraph) -> Self { + Self::DiGraph(graph) + } +} + +impl From for Body { + fn from(graph: UnGraph) -> Self { + Self::UnGraph(graph) + } +} + /// The closed forward control algebra a statement produces. /// /// Atomic statements read operands, write results, and return [`SparseForwardEffect::Next`]. @@ -86,27 +125,32 @@ pub enum Callee { Specialized(SpecializedFunction), } -/// The body a callable statement enters when invoked: a CFG plus the -/// entry arguments bound to its entry block. +/// The body a callable statement enters when invoked, plus the entry +/// arguments bound to its boundary (block parameters / graph ports). /// /// This is the function-call entry descriptor — the call mechanism, not a /// structured-control abstraction. A [`FunctionEntry`](crate::FunctionEntry) -/// rule returns one; the engine builds the body frame that walks the CFG. -pub struct FunctionBody { - pub cfg: Cfg, +/// rule returns one; the engine picks the walker that matches the body kind. +/// Any body kind may be callable — the statement declaring itself callable +/// defines the semantics; the framework supplies default walkers for `Cfg`, +/// `Block` (linear functions), and `DiGraph`, and rejects `UnGraph` with +/// [`InterpreterError::NoDefaultWalker`](crate::InterpreterError) unless a +/// dialect supplies its own walk. +pub struct CallableBody { + pub body: Body, pub args: Product, } -impl FunctionBody { - /// A function body over `cfg`, with no entry arguments yet. - pub fn new(cfg: Cfg) -> Self { +impl CallableBody { + /// A callable body, with no entry arguments yet. + pub fn new(body: impl Into) -> Self { Self { - cfg, + body: body.into(), args: Product::new(), } } - /// Entry arguments bound to the CFG entry block's parameters. + /// Entry arguments bound to the body's boundary parameters. pub fn args(mut self, args: impl IntoIterator) -> Self { self.args = args.into_iter().collect(); self diff --git a/crates/kirin-interpreter/src/core/error.rs b/crates/kirin-interpreter/src/core/error.rs index 5211e87d26..f1e7605eef 100644 --- a/crates/kirin-interpreter/src/core/error.rs +++ b/crates/kirin-interpreter/src/core/error.rs @@ -41,6 +41,12 @@ pub enum InterpreterError { MissingCallTarget(Symbol), #[error("cfg has no entry block")] EmptyCfg, + #[error("body {0:?} has no default walker in this engine")] + NoDefaultWalker(crate::Body), + #[error("digraph {0:?} has a cycle; the default walker only runs DAGs")] + GraphHasCycle(kirin_ir::DiGraph), + #[error("CFG control flow (jump/branch) inside a structured or linear body")] + CfgControlFlowInStructuredBody, #[error("block {0:?} fell through without a terminator effect")] BlockFellThrough(Block), #[error("function body fell through without returning")] diff --git a/crates/kirin-interpreter/src/core/frame.rs b/crates/kirin-interpreter/src/core/frame.rs index 688eefe8fb..59e0e241f2 100644 --- a/crates/kirin-interpreter/src/core/frame.rs +++ b/crates/kirin-interpreter/src/core/frame.rs @@ -10,7 +10,7 @@ use std::hash::Hash; use kirin_ir::{Block, Cfg, CompileStage, Product, SSAValue, Statement}; use crate::{ - CallEffect, Callee, Env, EnvIndex, FunctionBody, FunctionTarget, Interp, InterpreterError, + CallEffect, CallableBody, Callee, Env, EnvIndex, FunctionTarget, Interp, InterpreterError, }; /// Structural effect a [`Frame`] returns to the engine driver loop. @@ -117,14 +117,14 @@ pub trait ForwardFrameDriver: Env { statement: Statement, index: EnvIndex, ) -> Result; - /// Build the [`FunctionBody`] a callable statement enters on invocation. + /// Build the [`CallableBody`] a callable statement enters on invocation. fn enter_function( &mut self, stage: CompileStage, body: Statement, args: Product, index: EnvIndex, - ) -> Result, Self::Error>; + ) -> Result, Self::Error>; fn block_params(&self, stage: CompileStage, block: Block) -> Result, Self::Error>; @@ -141,6 +141,14 @@ pub trait ForwardFrameDriver: Env { ) -> Result, Self::Error>; fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, Self::Error>; + /// The default walk plan of a digraph body (ports, toposorted nodes, + /// yields). Errors on cyclic digraphs. + fn digraph_walk_plan( + &self, + stage: CompileStage, + graph: kirin_ir::DiGraph, + ) -> Result; + /// Bind a block's parameters to incoming actuals in `env` (arity-checked). fn bind_block_args( &mut self, diff --git a/crates/kirin-interpreter/src/core/mod.rs b/crates/kirin-interpreter/src/core/mod.rs index cdcd5f9b0b..1b57be2345 100644 --- a/crates/kirin-interpreter/src/core/mod.rs +++ b/crates/kirin-interpreter/src/core/mod.rs @@ -14,7 +14,7 @@ pub(crate) mod query; pub(crate) mod value; pub use dispatch::{FunctionEntry, InterpDispatch, Interpretable}; -pub use effect::{CallEffect, Callee, Edge, FunctionBody, SparseForwardEffect}; +pub use effect::{Body, CallEffect, CallableBody, Callee, Edge, SparseForwardEffect}; pub use env::{EnvIndex, EnvStackStore, Store}; pub use error::InterpreterError; pub use frame::{ @@ -22,5 +22,5 @@ pub use frame::{ }; pub use interp::{AbstractInterpreter, Env, Interp, InterpLocation, SparseForwardInterp}; pub use linker::{CrossStageLinker, FunctionTarget, Linker, SameStageLinker}; -pub use query::StageQuery; +pub use query::{GraphWalkPlan, StageQuery}; pub use value::{BranchCondition, HasProductValue, expect_single}; diff --git a/crates/kirin-interpreter/src/core/query.rs b/crates/kirin-interpreter/src/core/query.rs index d871a2c0ca..e95b229475 100644 --- a/crates/kirin-interpreter/src/core/query.rs +++ b/crates/kirin-interpreter/src/core/query.rs @@ -13,8 +13,9 @@ use kirin_ir::{ UniqueLiveSpecializationError, }; +use crate::Body; use crate::InterpreterError; -use crate::facts::topology::{self, CfgTopology}; +use crate::facts::topology::{self, BodyTopology}; /// Block parameters as SSA values. pub struct BlockParams(pub Block); @@ -115,6 +116,52 @@ where } } +/// Everything the default digraph walker needs: the boundary ports, the +/// node statements in topological order, and the graph's yields. +#[derive(Clone, Debug)] +pub struct GraphWalkPlan { + pub ports: Vec, + pub schedule: Vec, + pub yields: Vec, +} + +/// The walk plan of a digraph body (ports, toposorted nodes, yields). +/// +/// Fails with [`InterpreterError::GraphHasCycle`] on cyclic digraphs: they +/// are structurally legal IR but have no single-pass execution order. +pub struct DiGraphWalkQuery(pub kirin_ir::DiGraph); + +impl StageAction for DiGraphWalkQuery +where + S: StageMeta + HasStageInfo, + L: Dialect, +{ + type Output = GraphWalkPlan; + type Error = InterpreterError; + + fn run( + &mut self, + _stage: CompileStage, + info: &StageInfo, + ) -> Result { + let graph_info = self + .0 + .get_info(info) + .ok_or(InterpreterError::GraphHasCycle(self.0))?; + let order = petgraph::algo::toposort(graph_info.graph(), None) + .map_err(|_| InterpreterError::GraphHasCycle(self.0))?; + let schedule = order + .into_iter() + .map(|node| graph_info.graph()[node]) + .collect(); + Ok(GraphWalkPlan { + ports: graph_info.ports().to_vec(), + schedule, + yields: graph_info.yields().to_vec(), + }) + } +} + /// The unique live specialization of a staged function. pub struct UniqueSpecialization(pub StagedFunction); @@ -230,17 +277,22 @@ where } } -/// The topology of a CFG: blocks (including nested structured bodies), -/// statements per block, CFG successors, and block feeders. -pub struct CfgTopologyQuery(pub Cfg); +/// The topology of a body: blocks and graph parts (including nested +/// structured bodies), statements per part, CFG successors, block feeders, +/// and graph-port boundaries. +pub struct BodyTopologyQuery(pub Body); -impl StageAction for CfgTopologyQuery +impl StageAction for BodyTopologyQuery where S: StageMeta + HasStageInfo, L: Dialect, - for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a>, + for<'a> L: HasSuccessors<'a> + + HasBlocks<'a> + + HasCfgs<'a> + + kirin_ir::HasDigraphs<'a> + + kirin_ir::HasUngraphs<'a>, { - type Output = CfgTopology; + type Output = BodyTopology; type Error = InterpreterError; fn run( @@ -248,7 +300,7 @@ where _stage: CompileStage, info: &StageInfo, ) -> Result { - Ok(topology::cfg_topology(info, &self.0)) + Ok(topology::body_topology(info, self.0)) } } @@ -291,7 +343,8 @@ pub trait StageQuery: + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch, InterpreterError> - + SupportsStageDispatch + + SupportsStageDispatch + + SupportsStageDispatch { } @@ -309,7 +362,8 @@ impl StageQuery for S where + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch, InterpreterError> - + SupportsStageDispatch + + SupportsStageDispatch + + SupportsStageDispatch { } @@ -402,10 +456,18 @@ pub(crate) fn terminator_arguments( dispatch(pipeline, stage, TerminatorArguments(block)) } -pub(crate) fn cfg_topology( +pub(crate) fn digraph_walk_plan( pipeline: &Pipeline, stage: CompileStage, - cfg: Cfg, -) -> Result { - dispatch(pipeline, stage, CfgTopologyQuery(cfg)) + graph: kirin_ir::DiGraph, +) -> Result { + dispatch(pipeline, stage, DiGraphWalkQuery(graph)) +} + +pub(crate) fn body_topology( + pipeline: &Pipeline, + stage: CompileStage, + body: Body, +) -> Result { + dispatch(pipeline, stage, BodyTopologyQuery(body)) } diff --git a/crates/kirin-interpreter/src/engines/concrete/frames.rs b/crates/kirin-interpreter/src/engines/concrete/frames.rs index 1972bd4b46..cdde9416e4 100644 --- a/crates/kirin-interpreter/src/engines/concrete/frames.rs +++ b/crates/kirin-interpreter/src/engines/concrete/frames.rs @@ -16,7 +16,7 @@ use kirin_ir::{Block, Cfg, CompileStage, Product, SSAValue, Statement}; use crate::{ - CallEffect, Callee, EnvIndex, Frame, FrameDriver, FrameEffect, InterpreterError, + Body, CallEffect, Callee, EnvIndex, Frame, FrameDriver, FrameEffect, InterpreterError, SparseForwardEffect, SparseForwardInterp, }; @@ -42,15 +42,28 @@ pub enum Completion { pub trait FrameBuild: Sized { fn from_body(frame: BodyFrame) -> Self; fn from_call(frame: CallFrame) -> Self; + fn from_digraph(frame: DiGraphFrame) -> Self; } /// Traversal of one body: a function-body CFG (multi-block, with jumps) /// or a single body block (scf-style, terminated by a yield). +/// How a [`BodyFrame`] treats its current block's control flow. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BlockMode { + /// A block belonging to a CFG: `Jump`/`Branch` move between blocks. + CfgBlock, + /// A structured (scf-style) or linear-function body: a single block whose + /// exit is `Yield` (structured) or `Return` (linear function); + /// `Jump`/`Branch` are an error. + StructuredBody, +} + pub struct BodyFrame { stage: CompileStage, index: EnvIndex, owns_env: bool, function_boundary: bool, + mode: BlockMode, block: Block, cursor: Option, /// Entry arguments not yet bound. A body frame built by a dialect frame is @@ -83,7 +96,41 @@ where let entry = interp .cfg_entry(stage, cfg)? .ok_or_else(|| E::from(InterpreterError::EmptyCfg))?; - Self::start(interp, stage, index, entry, args, true, true) + Self::start( + interp, + stage, + index, + entry, + args, + true, + true, + BlockMode::CfgBlock, + ) + } + + /// Walk a linear (single-`Block`) function body: bind `args` to the + /// block's parameters. Owns the activation and is the return boundary; + /// `Jump`/`Branch`/`Yield` are errors — the exit convention is `Return`. + pub fn linear_function( + interp: &mut I, + stage: CompileStage, + index: EnvIndex, + block: Block, + args: Product, + ) -> Result + where + I: FrameDriver, + { + Self::start( + interp, + stage, + index, + block, + args, + true, + true, + BlockMode::StructuredBody, + ) } /// A single body block (scf-style), to bind `args` to its parameters on the @@ -95,6 +142,7 @@ where index, owns_env: false, function_boundary: false, + mode: BlockMode::StructuredBody, block, cursor: None, pending: Some(args), @@ -111,6 +159,7 @@ where args: Product, owns_env: bool, function_boundary: bool, + mode: BlockMode, ) -> Result where I: FrameDriver, @@ -122,6 +171,7 @@ where index, owns_env, function_boundary, + mode, block, cursor, pending: None, @@ -156,12 +206,20 @@ where match interp.run_statement(self.stage, statement, self.index)? { SparseForwardEffect::Next => Ok(FrameEffect::Continue(F::from_body(self))), SparseForwardEffect::Jump(edge) => { + if self.mode == BlockMode::StructuredBody { + return Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)); + } interp.bind_block_args(self.stage, self.index, edge.target, &edge.args)?; self.cursor = interp.first_statement(self.stage, edge.target)?; self.block = edge.target; Ok(FrameEffect::Continue(F::from_body(self))) } - SparseForwardEffect::Branch(_) => Err(E::from(InterpreterError::IndeterminateBranch)), + SparseForwardEffect::Branch(_) => { + if self.mode == BlockMode::StructuredBody { + return Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)); + } + Err(E::from(InterpreterError::IndeterminateBranch)) + } SparseForwardEffect::Push { frame, results } => { self.resume_slots = Some(results); Ok(FrameEffect::Push { @@ -289,13 +347,37 @@ where let target = interp.resolve_call(resolve_stage, &callee)?; let index = interp.alloc_env(); let body = interp.enter_function(target.stage, target.body, args, index)?; - let frame = BodyFrame::function(interp, target.stage, index, body.cfg, body.args)?; + let child = match body.body { + Body::Cfg(cfg) => F::from_body(BodyFrame::function( + interp, + target.stage, + index, + cfg, + body.args, + )?), + Body::Block(block) => F::from_body(BodyFrame::linear_function( + interp, + target.stage, + index, + block, + body.args, + )?), + Body::DiGraph(graph) => F::from_digraph(DiGraphFrame::function( + target.stage, + index, + graph, + body.args, + )), + other @ Body::UnGraph(_) => { + return Err(I::Error::from(InterpreterError::NoDefaultWalker(other))); + } + }; Ok(FrameEffect::Push { parent: F::from_call(CallFrame::Awaiting { caller_env, results, }), - child: F::from_body(frame), + child, }) } CallFrame::Awaiting { .. } => Err(I::Error::from(InterpreterError::Custom( @@ -343,9 +425,200 @@ where /// The default total concrete frame enum: standard concrete traversal (no /// structured-control dialect frames). +/// Traversal of one digraph body: bind entry arguments to the graph's +/// boundary ports, run the node statements in topological order, and +/// complete with the graph's yielded values. +/// +/// Pure construction — the walk plan is fetched and the ports are bound on +/// the first `step`, so a dialect frame can build one without engine access +/// (the same lazy pattern as [`BodyFrame::block`]). CFG control flow +/// (`Jump`/`Branch`) and `Yield`/`Return` are errors inside a graph: a +/// digraph's outputs are its declared yields, not a statement effect. +pub struct DiGraphFrame { + stage: CompileStage, + index: EnvIndex, + owns_env: bool, + function_boundary: bool, + graph: kirin_ir::DiGraph, + /// Entry arguments not yet bound (bound on the first `step`). + pending: Option>, + /// Remaining schedule, in topological order; `None` until the first step. + schedule: Option>, + yields: Vec, + /// Result slots awaiting a pushed child frame's `Finished` completion. + resume_slots: Option>, + _marker: std::marker::PhantomData (V, E)>, +} + +impl DiGraphFrame +where + V: Clone, + E: From, +{ + /// Walk a digraph as a function body: owns the activation and is the + /// call's return boundary (completes `Returned` with the yields). + pub fn function( + stage: CompileStage, + index: EnvIndex, + graph: kirin_ir::DiGraph, + args: Product, + ) -> Self { + Self::with_boundary(stage, index, graph, args, true, true) + } + + /// Walk a digraph owned by a statement inside another body (pushed by a + /// dialect frame): borrows the caller's activation and completes + /// `Finished` with the yields. + pub fn nested( + stage: CompileStage, + index: EnvIndex, + graph: kirin_ir::DiGraph, + args: Product, + ) -> Self { + Self::with_boundary(stage, index, graph, args, false, false) + } + + fn with_boundary( + stage: CompileStage, + index: EnvIndex, + graph: kirin_ir::DiGraph, + args: Product, + owns_env: bool, + function_boundary: bool, + ) -> Self { + Self { + stage, + index, + owns_env, + function_boundary, + graph, + pending: Some(args), + schedule: None, + yields: Vec::new(), + resume_slots: None, + _marker: std::marker::PhantomData, + } + } + + /// Execute the next scheduled node and translate its + /// [`SparseForwardEffect`] into a [`FrameEffect`] over the total frame + /// type `F`. + pub fn step_into(mut self, interp: &mut I) -> Result>, E> + where + I: FrameDriver + SparseForwardInterp, + F: FrameBuild, + { + // First step: fetch the walk plan and bind the boundary ports. + if let Some(args) = self.pending.take() { + let plan = interp.digraph_walk_plan(self.stage, self.graph)?; + if plan.ports.len() != args.len() { + return Err(E::from(InterpreterError::ProductArityMismatch { + expected: plan.ports.len(), + actual: args.len(), + })); + } + for (port, value) in plan.ports.iter().copied().zip(args) { + interp.env_write(self.index, SSAValue::from(port), value)?; + } + self.schedule = Some(plan.schedule.into()); + self.yields = plan.yields; + return Ok(FrameEffect::Continue(F::from_digraph(self))); + } + + let Some(statement) = self.schedule.as_mut().and_then(|s| s.pop_front()) else { + return self.finish::(interp); + }; + + match interp.run_statement(self.stage, statement, self.index)? { + SparseForwardEffect::Next => Ok(FrameEffect::Continue(F::from_digraph(self))), + SparseForwardEffect::Push { frame, results } => { + self.resume_slots = Some(results); + Ok(FrameEffect::Push { + parent: F::from_digraph(self), + child: frame, + }) + } + SparseForwardEffect::Call(call) => { + let pending = CallFrame::pending(self.stage, self.index, call); + Ok(FrameEffect::Push { + parent: F::from_digraph(self), + child: F::from_call(pending), + }) + } + SparseForwardEffect::Jump(_) | SparseForwardEffect::Branch(_) => { + Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)) + } + SparseForwardEffect::Yield(_) => Err(E::from(InterpreterError::Custom( + "yield inside a digraph body (a digraph's outputs are its declared yields)", + ))), + SparseForwardEffect::Return(_) => Err(E::from(InterpreterError::Custom( + "return inside a digraph body", + ))), + } + } + + /// Schedule exhausted: read the yields from the environment and complete. + fn finish(self, interp: &mut I) -> Result>, E> + where + I: FrameDriver, + F: FrameBuild, + { + let values: Product = self + .yields + .iter() + .map(|&value| interp.env_read(self.index, value)) + .collect::>()?; + if self.function_boundary { + if self.owns_env { + interp.free_env(self.index)?; + } + Ok(FrameEffect::Complete(Completion::Returned(values))) + } else { + Ok(FrameEffect::Complete(Completion::Finished(values))) + } + } + + /// A child finished without a payload (e.g. a returned call whose results + /// are already written): resume the schedule. + pub fn resume_done_into(self) -> FrameEffect> + where + F: FrameBuild, + { + FrameEffect::Continue(F::from_digraph(self)) + } + + /// A child bubbled a completion: a pushed frame `Finished` (bind its + /// values into the awaiting result slots) or a callee `Returned`. + pub fn resume_into( + mut self, + completion: Completion, + interp: &mut I, + ) -> Result>, E> + where + I: FrameDriver, + F: FrameBuild, + { + match completion { + Completion::Finished(values) => { + let slots = self.resume_slots.take().ok_or_else(|| { + E::from(InterpreterError::Custom( + "digraph resume without result slots", + )) + })?; + interp.write_results(self.index, &slots, values)?; + Ok(FrameEffect::Continue(F::from_digraph(self))) + } + Completion::Returned(_) => Err(E::from(InterpreterError::Custom( + "return bubbled into a digraph body", + ))), + } + } +} + pub enum StandardFrame { Body(BodyFrame), Call(CallFrame), + DiGraph(DiGraphFrame), } impl FrameBuild for StandardFrame { @@ -355,6 +628,9 @@ impl FrameBuild for StandardFrame { fn from_call(frame: CallFrame) -> Self { StandardFrame::Call(frame) } + fn from_digraph(frame: DiGraphFrame) -> Self { + StandardFrame::DiGraph(frame) + } } impl Frame for StandardFrame @@ -369,6 +645,7 @@ where match self { StandardFrame::Body(frame) => frame.step_into::(interp), StandardFrame::Call(frame) => frame.step_into::(interp), + StandardFrame::DiGraph(frame) => frame.step_into::(interp), } } @@ -376,6 +653,7 @@ where match self { StandardFrame::Body(frame) => Ok(frame.resume_done_into::()), StandardFrame::Call(frame) => frame.resume_done_into::().map_err(I::Error::from), + StandardFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), } } @@ -387,6 +665,7 @@ where match self { StandardFrame::Body(frame) => frame.resume_into::(completion, interp), StandardFrame::Call(frame) => frame.resume_into::(completion, interp), + StandardFrame::DiGraph(frame) => frame.resume_into::(completion, interp), } } } diff --git a/crates/kirin-interpreter/src/engines/concrete/interp.rs b/crates/kirin-interpreter/src/engines/concrete/interp.rs index 6c7665ac06..42315bbd94 100644 --- a/crates/kirin-interpreter/src/engines/concrete/interp.rs +++ b/crates/kirin-interpreter/src/engines/concrete/interp.rs @@ -4,8 +4,8 @@ use kirin_ir::{Block, Cfg, CompileStage, Pipeline, Product, SSAValue, StageMeta, use crate::core::query; use crate::{ - BodyFrame, Callee, Completion, Env, EnvIndex, EnvStackStore, ForwardEval, Frame, FrameBuild, - FrameDriver, FunctionBody, FunctionTarget, Interp, InterpDispatch, InterpLocation, + Body, BodyFrame, CallableBody, Callee, Completion, Env, EnvIndex, EnvStackStore, ForwardEval, + Frame, FrameBuild, FrameDriver, FunctionTarget, Interp, InterpDispatch, InterpLocation, InterpreterError, Linker, SameStageLinker, SparseForwardEffect, StageQuery, StandardFrame, Store, drive_frames, }; @@ -159,7 +159,7 @@ where body: Statement, args: Product, index: EnvIndex, - ) -> Result, E> { + ) -> Result, E> { let pipeline = self.pipeline; let info = pipeline .stage(stage) @@ -194,6 +194,14 @@ where fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, E> { query::cfg_entry(self.pipeline, stage, cfg).map_err(E::from) } + + fn digraph_walk_plan( + &self, + stage: CompileStage, + graph: kirin_ir::DiGraph, + ) -> Result { + query::digraph_walk_plan(self.pipeline, stage, graph).map_err(E::from) + } } impl<'ir, S, V, E, Lk, F> ConcreteInterpreter<'ir, S, V, E, Lk, F> @@ -233,8 +241,34 @@ where let index = self.alloc_env(); let args: Product = args.into_iter().collect(); let body = self.enter_function(target.stage, target.body, args, index)?; - let frame = BodyFrame::function(self, target.stage, index, body.cfg, body.args)?; - self.frames.push(F::from_body(frame)); + let frame = match body.body { + Body::Cfg(cfg) => F::from_body(BodyFrame::function( + self, + target.stage, + index, + cfg, + body.args, + )?), + Body::Block(block) => F::from_body(BodyFrame::linear_function( + self, + target.stage, + index, + block, + body.args, + )?), + Body::DiGraph(graph) => { + F::from_digraph(crate::engines::concrete::DiGraphFrame::function( + target.stage, + index, + graph, + body.args, + )) + } + other @ Body::UnGraph(_) => { + return Err(E::from(InterpreterError::NoDefaultWalker(other))); + } + }; + self.frames.push(frame); self.run() } diff --git a/crates/kirin-interpreter/src/engines/concrete/mod.rs b/crates/kirin-interpreter/src/engines/concrete/mod.rs index 968305c9ff..ea1a276d8c 100644 --- a/crates/kirin-interpreter/src/engines/concrete/mod.rs +++ b/crates/kirin-interpreter/src/engines/concrete/mod.rs @@ -3,5 +3,7 @@ pub(crate) mod frames; pub(crate) mod interp; -pub use frames::{BodyFrame, CallFrame, Completion, FrameBuild, StandardFrame}; +pub use frames::{ + BlockMode, BodyFrame, CallFrame, Completion, DiGraphFrame, FrameBuild, StandardFrame, +}; pub use interp::ConcreteInterpreter; diff --git a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs index 823ff776bd..475a74fbcc 100644 --- a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs @@ -51,6 +51,7 @@ use kirin_ir::{ }; use super::frames::{DenseBlockFrame, DenseFrameBuild}; +use crate::Body; use crate::core::query; use crate::engines::sparse_backward::CfgScope; use crate::{ @@ -654,10 +655,11 @@ where pub fn block_summary( &self, stage: CompileStage, - cfg: Cfg, + body: impl Into, block: Block, ) -> Option<&BlockLiveness> { - self.driver.summary(&Scoped::new((stage, cfg), block)) + self.driver + .summary(&Scoped::new((stage, body.into()), block)) } /// The analyzed CFG's own top-level blocks (post-`analyze`). @@ -683,9 +685,10 @@ where /// Run the block-boundary fixpoint over `cfg` in `stage`: seed every /// CFG block (a backward analysis must visit them all) and drain the /// worklist; dependencies are discovered from the terminators' edges. - pub fn analyze(&mut self, stage: CompileStage, cfg: Cfg) -> Result<(), E> { - let scope = (stage, cfg); - let topology = query::cfg_topology(self.driver.inner().pipeline(), stage, cfg)?; + pub fn analyze(&mut self, stage: CompileStage, body: impl Into) -> Result<(), E> { + let body = body.into(); + let scope = (stage, body); + let topology = query::body_topology(self.driver.inner().pipeline(), stage, body)?; let owners: Vec> = topology .cfg_blocks() .map(|block| Scoped::new(scope, block.block)) @@ -708,9 +711,9 @@ where pub fn reconstruct_points( &mut self, stage: CompileStage, - cfg: Cfg, + body: impl Into, ) -> Result, E> { - let scope = (stage, cfg); + let scope = (stage, body.into()); self.driver.store_mut().recorder = Some(DensePointStore::new()); for block in self.cfg_blocks() { // The CfgOwner walk re-absorbs the converged successor summaries, diff --git a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs index e5851b7b97..8729f599f9 100644 --- a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs @@ -55,17 +55,20 @@ use kirin_ir::{ use crate::core::query; use crate::{ - AbstractInterpreter, CfgTopology, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, + AbstractInterpreter, Body, CfgTopology, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, InterpDispatch, InterpLocation, InterpreterError, OwnerSemantics, OwnerSummaryDeps, Scoped, SparseBackwardSemantic, SparseStore, StageQuery, StandardFixpointInterpreter, StrongDemand, Summary, SummaryEffect, }; -/// The scope a CFG-level backward analysis qualifies its facts with. +/// The scope a body-level backward analysis qualifies its facts with. /// /// Arena ids are per-stage, so the stage is part of the scope; analyzing two -/// cfgs in one engine keeps their facts distinct. -pub type CfgScope = (CompileStage, Cfg); +/// bodies in one engine keeps their facts distinct. +pub type BodyScope = (CompileStage, Body); + +/// Deprecated name for [`BodyScope`]; kept for one release. +pub type CfgScope = BodyScope; // =========================================================================== // Effect + dialect-facing trait @@ -476,9 +479,18 @@ where SSAKind::Result(statement, _) => vec![statement], SSAKind::BlockArgument(block, _) => interp.store().topology.feeders(block).to_vec(), SSAKind::Port(..) => { - return Err(E::from(InterpreterError::Custom( - "graph ports are not supported by sparse backward demand", - ))); + // A port is a boundary SSA value: the statement owning the + // graph translates demand across the boundary (its rule maps + // port/index to operands, captures, or results). + let boundary = interp.store().topology.port_boundary(owner.item); + match boundary { + Some(boundary) => vec![boundary.owner], + None => { + return Err(E::from(InterpreterError::Custom( + "graph port outside the analyzed body", + ))); + } + } } }; Ok(DemandFrame::new(stage, work)) @@ -542,16 +554,25 @@ where self.driver.inner().pipeline() } - /// The converged demand fact for `value` under the `(stage, cfg)` scope. - pub fn fact(&self, stage: CompileStage, cfg: Cfg, value: impl Into) -> Option<&V> { + /// The converged demand fact for `value` under the `(stage, body)` scope. + pub fn fact( + &self, + stage: CompileStage, + body: impl Into, + value: impl Into, + ) -> Option<&V> { self.driver - .summary(&Scoped::new((stage, cfg), value.into())) + .summary(&Scoped::new((stage, body.into()), value.into())) .map(|summary| &summary.0) } - /// All converged `(value, fact)` pairs under the `(stage, cfg)` scope. - pub fn facts(&self, stage: CompileStage, cfg: Cfg) -> impl Iterator { - let scope = (stage, cfg); + /// All converged `(value, fact)` pairs under the `(stage, body)` scope. + pub fn facts( + &self, + stage: CompileStage, + body: impl Into, + ) -> impl Iterator { + let scope = (stage, body.into()); self.driver .summaries() .iter() @@ -559,11 +580,11 @@ where .map(|(owner, summary)| (owner.item, &summary.0)) } - /// The converged facts under the `(stage, cfg)` scope as a + /// The converged facts under the `(stage, body)` scope as a /// [`SparseStore`] (the sparse per-SSA-value fact view; absent = bottom). - pub fn fact_store(&self, stage: CompileStage, cfg: Cfg) -> SparseStore { + pub fn fact_store(&self, stage: CompileStage, body: impl Into) -> SparseStore { let mut store = SparseStore::new(); - for (value, fact) in self.facts(stage, cfg) { + for (value, fact) in self.facts(stage, body) { store.set(value, fact.clone()); } store @@ -577,21 +598,19 @@ where E: From, Sem: SparseBackwardSemantic, { - /// Run the demand fixpoint over `cfg` in `stage`. + /// Run the demand fixpoint over `body` in `stage`. /// - /// **Prepass**: enumerate the CFG topology (blocks including structured - /// bodies, statements, feeders), then run every statement's rule once with - /// nothing demanded — impure statements and terminators contribute the - /// demand roots. **Propagation**: drain the value worklist; each risen - /// value dispatches the rules that translate its demand. - pub fn analyze(&mut self, stage: CompileStage, cfg: Cfg) -> Result<(), E> { - let scope = (stage, cfg); - let topology = query::cfg_topology(self.driver.inner().pipeline(), stage, cfg)?; - let statements: Vec = topology - .blocks - .iter() - .flat_map(|block| block.stmts.iter().copied()) - .collect(); + /// **Prepass**: enumerate the body topology (blocks and graph parts, + /// including structured bodies, statements, feeders, port boundaries), + /// then run every statement's rule once with nothing demanded — impure + /// statements and terminators contribute the demand roots. + /// **Propagation**: drain the value worklist; each risen value dispatches + /// the rules that translate its demand. + pub fn analyze(&mut self, stage: CompileStage, body: impl Into) -> Result<(), E> { + let body = body.into(); + let scope = (stage, body); + let topology = query::body_topology(self.driver.inner().pipeline(), stage, body)?; + let statements: Vec = topology.statements().collect(); *self.driver.store_mut() = BackwardAnalysisState { scope: Some(scope), topology, @@ -619,11 +638,16 @@ where } /// `true` iff `value` carries a non-bottom demand fact under the scope. - pub fn is_demanded(&self, stage: CompileStage, cfg: Cfg, value: impl Into) -> bool + pub fn is_demanded( + &self, + stage: CompileStage, + body: impl Into, + value: impl Into, + ) -> bool where V: HasBottom, { - self.fact(stage, cfg, value) + self.fact(stage, body, value) .is_some_and(|fact| *fact != V::bottom()) } } diff --git a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs index f97af1e723..3266c85bd2 100644 --- a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs @@ -40,8 +40,8 @@ use kirin_ir::{ use crate::core::query; use crate::{ AbstractBlockFrame, AbstractCompletion, AbstractFrameBuild, AbstractFrameDriver, - AbstractInterpreter, CallEffect, Callee, Env, EnvIndex, EnvStackStore, FixpointProfile, - ForwardEval, ForwardFrameDriver, ForwardSummaryDeps, Frame, FunctionBody, FunctionTarget, + AbstractInterpreter, Body, CallEffect, CallableBody, Callee, Env, EnvIndex, EnvStackStore, + FixpointProfile, ForwardEval, ForwardFrameDriver, ForwardSummaryDeps, Frame, FunctionTarget, Interp, InterpDispatch, InterpLocation, InterpreterError, Linker, OwnerSemantics, SameStageLinker, SparseForwardEffect, SparseForwardSemantic, StageQuery, StandardAbstractFrame, StandardFixpointInterpreter, Store, Summary, SummaryDependency, SummaryDependencyIndex, @@ -652,7 +652,7 @@ where body: Statement, args: Product, index: EnvIndex, - ) -> Result, E> { + ) -> Result, E> { let pipeline = self.pipeline; let info = pipeline .stage(stage) @@ -687,6 +687,14 @@ where fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, E> { query::cfg_entry(self.pipeline, stage, cfg).map_err(E::from) } + + fn digraph_walk_plan( + &self, + stage: CompileStage, + graph: kirin_ir::DiGraph, + ) -> Result { + query::digraph_walk_plan(self.pipeline, stage, graph).map_err(E::from) + } } // =========================================================================== @@ -729,7 +737,7 @@ where body: Statement, args: Product, index: EnvIndex, - ) -> Result, E> { + ) -> Result, E> { self.inner_mut().enter_function(stage, body, args, index) } @@ -753,6 +761,14 @@ where fn cfg_entry(&self, stage: CompileStage, cfg: Cfg) -> Result, E> { self.inner().cfg_entry(stage, cfg) } + + fn digraph_walk_plan( + &self, + stage: CompileStage, + graph: kirin_ir::DiGraph, + ) -> Result { + self.inner().digraph_walk_plan(stage, graph) + } } impl<'ir, S, V, E, Lk, P, F, Sem> AbstractFrameDriver for ForwardDriver<'ir, S, V, E, Lk, P, F, Sem> @@ -1051,9 +1067,15 @@ where .map(|function| function.entry.clone()) .expect("function summary present"); let body_info = self.enter_function(stage, body, entry_args, env)?; - let entry_block = self - .cfg_entry(stage, body_info.cfg)? - .ok_or_else(|| E::from(InterpreterError::EmptyCfg))?; + let entry_block = match body_info.body { + Body::Cfg(cfg) => self + .cfg_entry(stage, cfg)? + .ok_or_else(|| E::from(InterpreterError::EmptyCfg))?, + Body::Block(block) => block, + other @ (Body::DiGraph(_) | Body::UnGraph(_)) => { + return Err(E::from(InterpreterError::NoDefaultWalker(other))); + } + }; if let Some(function) = self .summary_mut(&Owner::Function(key.clone())) .and_then(|info| info.as_function_mut()) diff --git a/crates/kirin-interpreter/src/facts/mod.rs b/crates/kirin-interpreter/src/facts/mod.rs index abb02d8cca..b53c157918 100644 --- a/crates/kirin-interpreter/src/facts/mod.rs +++ b/crates/kirin-interpreter/src/facts/mod.rs @@ -8,4 +8,7 @@ pub(crate) mod topology; pub use anchor::{Change, DenseAnchor, LatticeAnchor, ProgramPoint, Scoped}; pub use store::{DenseBlockStore, DensePointStore, FactStore, ScopedSparseStore, SparseStore}; -pub use topology::{BlockTopology, CfgTopology, cfg_topology}; +pub use topology::{ + BlockTopology, BodyTopology, CfgTopology, GraphTopology, PortBoundary, body_topology, + cfg_topology, +}; diff --git a/crates/kirin-interpreter/src/facts/topology.rs b/crates/kirin-interpreter/src/facts/topology.rs index d56675da6f..2de8d670b4 100644 --- a/crates/kirin-interpreter/src/facts/topology.rs +++ b/crates/kirin-interpreter/src/facts/topology.rs @@ -1,18 +1,25 @@ -//! Dialect-neutral CFG topology enumeration. +//! Dialect-neutral body topology enumeration. //! -//! Backward analyses need the *shape* of a CFG: which blocks exist -//! (including blocks nested inside structured statements), each block's -//! statements, the CFG successor relation, and each block's *feeders* — the -//! statements whose rules can translate demand on that block's parameters -//! (terminators targeting it, statements owning it). This is topology only — -//! uses/defs/edge-argument *semantics* stay in dialect -//! [`Interpretable`](crate::Interpretable) rules; the enumeration consumes the -//! generic [`HasSuccessors`]/[`HasBlocks`]/[`HasCfgs`] contract every -//! dialect derives. +//! Backward analyses need the *shape* of a body: which blocks and graph +//! nodes exist (including bodies nested inside structured statements), each +//! block's statements, the CFG successor relation, each block's *feeders* — +//! the statements whose rules can translate demand on that block's +//! parameters (terminators targeting it, statements owning it) — and each +//! graph port's *boundary* (the statement owning the graph, and the port's +//! slot index). This is topology only — uses/defs/edge-argument *semantics* +//! stay in dialect [`Interpretable`](crate::Interpretable) rules; the +//! enumeration consumes the generic [`HasSuccessors`]/[`HasBlocks`]/ +//! [`HasCfgs`]/[`HasDigraphs`]/[`HasUngraphs`] contract every dialect +//! derives. use std::collections::{HashMap, HashSet}; -use kirin_ir::{Block, Cfg, Dialect, HasBlocks, HasCfgs, HasSuccessors, StageInfo, Statement}; +use kirin_ir::{ + Block, Cfg, DiGraph, Dialect, GetInfo, HasBlocks, HasCfgs, HasDigraphs, HasSuccessors, + HasUngraphs, Port, SSAValue, StageInfo, Statement, UnGraph, +}; + +use crate::Body; /// The shape of one block: its statements and CFG successors. #[derive(Clone, Debug)] @@ -23,19 +30,52 @@ pub struct BlockTopology { /// CFG successor blocks (targets of the block's terminator). pub successors: Vec, /// `true` for blocks nested inside a statement (structured bodies), - /// `false` for the analyzed CFG's own top-level blocks. + /// `false` for the analyzed body's own top-level blocks. pub nested: bool, } -/// The shape of a CFG: all blocks (the CFG's own top-level blocks and -/// structured bodies, recursively) plus the block-feeder index. +/// The shape of one graph body: its node statements, in declaration order. +/// +/// Order is enumeration order, not a schedule — execution scheduling is the +/// walker's job, and backward prepasses only need *all* statements. +#[derive(Clone, Debug)] +pub struct GraphTopology { + /// `Body::DiGraph(..)` or `Body::UnGraph(..)`. + pub graph: Body, + pub stmts: Vec, + /// `true` for graphs nested inside a statement, `false` for the analyzed + /// body itself. + pub nested: bool, +} + +/// Where a graph port sits on its owning statement's boundary. +/// +/// This is a **location**, not a value mapping: the owning statement's +/// dialect rule translates port/index into its operands, captures, or +/// results — for values (forward) and demand (backward) alike. +#[derive(Clone, Copy, Debug)] +pub struct PortBoundary { + /// The statement that owns the graph. + pub owner: Statement, + /// Which boundary slot this port occupies. + pub index: usize, +} + +/// The shape of a body: all blocks and graph parts (the analyzed body's own +/// plus structured bodies, recursively), the block-feeder index, and the +/// graph-port boundary index. #[derive(Clone, Debug, Default)] -pub struct CfgTopology { +pub struct BodyTopology { pub blocks: Vec, + pub graphs: Vec, feeders: HashMap>, + port_boundary: HashMap, } -impl CfgTopology { +/// Deprecated name for [`BodyTopology`]; kept for one release. +pub type CfgTopology = BodyTopology; + +impl BodyTopology { /// The statements whose rules can translate demand on `block`'s parameters: /// terminators with an edge into `block`, plus statements owning `block` /// as a structured body. @@ -43,35 +83,75 @@ impl CfgTopology { self.feeders.get(&block).map(Vec::as_slice).unwrap_or(&[]) } - /// The analyzed CFG's own top-level blocks (excluding nested bodies). + /// The analyzed body's own top-level blocks (excluding nested bodies). pub fn cfg_blocks(&self) -> impl Iterator { self.blocks.iter().filter(|block| !block.nested) } + + /// Where `port` sits on its owning statement's boundary, if the port + /// belongs to a graph enumerated by this topology. + pub fn port_boundary(&self, port: impl Into) -> Option<&PortBoundary> { + self.port_boundary.get(&port.into()) + } + + /// Every statement enumerated by this topology: block statements first, + /// then graph node statements. + pub fn statements(&self) -> impl Iterator + '_ { + self.blocks + .iter() + .flat_map(|block| block.stmts.iter().copied()) + .chain(self.graphs.iter().flat_map(|g| g.stmts.iter().copied())) + } } -/// Enumerate the topology of `cfg` in the finalized `stage`. -pub fn cfg_topology(stage: &StageInfo, cfg: &Cfg) -> CfgTopology +/// Enumerate the topology of `body` in the finalized `stage`. +pub fn body_topology(stage: &StageInfo, body: Body) -> BodyTopology where L: Dialect, - for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a>, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a> + HasDigraphs<'a> + HasUngraphs<'a>, { - let mut topology = CfgTopology::default(); + let mut topology = BodyTopology::default(); let mut visited = HashSet::new(); - for block in cfg.blocks(stage) { - collect_block(stage, block, false, &mut topology, &mut visited); + match body { + Body::Cfg(cfg) => { + for block in cfg.blocks(stage) { + collect_block(stage, block, false, &mut topology, &mut visited); + } + } + Body::Block(block) => { + collect_block(stage, block, false, &mut topology, &mut visited); + } + Body::DiGraph(graph) => { + collect_digraph(stage, graph, false, &mut topology, &mut visited); + } + Body::UnGraph(graph) => { + collect_ungraph(stage, graph, false, &mut topology, &mut visited); + } } topology } +/// Enumerate the topology of `cfg` in the finalized `stage`. +/// +/// Deprecated spelling of [`body_topology`] over a `Cfg`; kept for one +/// release. +pub fn cfg_topology(stage: &StageInfo, cfg: &Cfg) -> BodyTopology +where + L: Dialect, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a> + HasDigraphs<'a> + HasUngraphs<'a>, +{ + body_topology(stage, Body::Cfg(*cfg)) +} + fn collect_block( stage: &StageInfo, block: Block, nested: bool, - topology: &mut CfgTopology, + topology: &mut BodyTopology, visited: &mut HashSet, ) where L: Dialect, - for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a>, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a> + HasDigraphs<'a> + HasUngraphs<'a>, { if !visited.insert(block) { return; @@ -99,20 +179,97 @@ fn collect_block( nested, }); - // Structured bodies: the owning statement feeds each owned block. for &stmt in &stmts { - let definition = stmt.definition(stage); - let owned_blocks: Vec = definition.blocks().copied().collect(); - let owned_cfgs: Vec = definition.cfgs().copied().collect(); - for owned in owned_blocks { + collect_owned_bodies(stage, stmt, topology, visited); + } +} + +fn collect_digraph( + stage: &StageInfo, + graph: DiGraph, + nested: bool, + topology: &mut BodyTopology, + visited: &mut HashSet, +) where + L: Dialect, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a> + HasDigraphs<'a> + HasUngraphs<'a>, +{ + let info = graph.expect_info(stage); + let stmts: Vec = info.graph().node_weights().copied().collect(); + record_ports(info.parent(), info.ports(), topology); + topology.graphs.push(GraphTopology { + graph: Body::DiGraph(graph), + stmts: stmts.clone(), + nested, + }); + for stmt in stmts { + collect_owned_bodies(stage, stmt, topology, visited); + } +} + +fn collect_ungraph( + stage: &StageInfo, + graph: UnGraph, + nested: bool, + topology: &mut BodyTopology, + visited: &mut HashSet, +) where + L: Dialect, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a> + HasDigraphs<'a> + HasUngraphs<'a>, +{ + let info = graph.expect_info(stage); + let stmts: Vec = info.graph().node_weights().copied().collect(); + record_ports(info.parent(), info.ports(), topology); + topology.graphs.push(GraphTopology { + graph: Body::UnGraph(graph), + stmts: stmts.clone(), + nested, + }); + for stmt in stmts { + collect_owned_bodies(stage, stmt, topology, visited); + } +} + +/// Descend into every body owned by `stmt`: structured blocks/cfgs (the +/// owning statement feeds each owned block) and owned graphs (their ports' +/// boundary is recorded). +fn collect_owned_bodies( + stage: &StageInfo, + stmt: Statement, + topology: &mut BodyTopology, + visited: &mut HashSet, +) where + L: Dialect, + for<'a> L: HasSuccessors<'a> + HasBlocks<'a> + HasCfgs<'a> + HasDigraphs<'a> + HasUngraphs<'a>, +{ + let definition = stmt.definition(stage); + let owned_blocks: Vec = definition.blocks().copied().collect(); + let owned_cfgs: Vec = definition.cfgs().copied().collect(); + let owned_digraphs: Vec = definition.digraphs().copied().collect(); + let owned_ungraphs: Vec = definition.ungraphs().copied().collect(); + for owned in owned_blocks { + topology.feeders.entry(owned).or_default().push(stmt); + collect_block(stage, owned, true, topology, visited); + } + for owned_cfg in owned_cfgs { + for owned in owned_cfg.blocks(stage) { topology.feeders.entry(owned).or_default().push(stmt); collect_block(stage, owned, true, topology, visited); } - for owned_cfg in owned_cfgs { - for owned in owned_cfg.blocks(stage) { - topology.feeders.entry(owned).or_default().push(stmt); - collect_block(stage, owned, true, topology, visited); - } - } + } + for owned in owned_digraphs { + collect_digraph(stage, owned, true, topology, visited); + } + for owned in owned_ungraphs { + collect_ungraph(stage, owned, true, topology, visited); + } +} + +fn record_ports(owner: Option, ports: &[Port], topology: &mut BodyTopology) { + let Some(owner) = owner else { return }; + for (index, &port) in ports.iter().enumerate() { + topology + .port_boundary + .insert(SSAValue::from(port), PortBoundary { owner, index }); } } diff --git a/crates/kirin-interpreter/src/lib.rs b/crates/kirin-interpreter/src/lib.rs index 4f8055eea3..4b146f030d 100644 --- a/crates/kirin-interpreter/src/lib.rs +++ b/crates/kirin-interpreter/src/lib.rs @@ -66,9 +66,11 @@ mod semantics; // The shared chassis: engine trait + dialect dispatch, effect types, // activation storage, calling conventions, errors, and IR queries. -pub use self::core::{AbstractInterpreter, Env, Interp, InterpLocation, SparseForwardInterp}; +pub use self::core::{ + AbstractInterpreter, Env, GraphWalkPlan, Interp, InterpLocation, SparseForwardInterp, +}; +pub use self::core::{Body, CallEffect, CallableBody, Callee, Edge, SparseForwardEffect}; pub use self::core::{BranchCondition, HasProductValue, expect_single}; -pub use self::core::{CallEffect, Callee, Edge, FunctionBody, SparseForwardEffect}; pub use self::core::{CrossStageLinker, FunctionTarget, Linker, SameStageLinker}; pub use self::core::{EnvIndex, EnvStackStore, Store}; pub use self::core::{FunctionEntry, InterpDispatch, Interpretable}; @@ -84,7 +86,8 @@ pub use self::core::ForwardFrameDriver as FrameDriver; // Concrete execution engine + the concrete standard frames. pub use engines::concrete::{ - BodyFrame, CallFrame, Completion, ConcreteInterpreter, FrameBuild, StandardFrame, + BlockMode, BodyFrame, CallFrame, Completion, ConcreteInterpreter, DiGraphFrame, FrameBuild, + StandardFrame, }; // Sparse forward engine (`Sem = ForwardEval`) + the abstract standard frames. pub use engines::sparse_forward::{ @@ -110,8 +113,9 @@ pub use engines::dense_backward::{ // fact stores, and cfg topology enumeration. Anchor family is a property of // the solver shape; dispatch meaning lives in `semantics`. pub use facts::{ - BlockTopology, CfgTopology, Change, DenseAnchor, DenseBlockStore, DensePointStore, FactStore, - LatticeAnchor, ProgramPoint, Scoped, ScopedSparseStore, SparseStore, cfg_topology, + BlockTopology, BodyTopology, CfgTopology, Change, DenseAnchor, DenseBlockStore, + DensePointStore, FactStore, GraphTopology, LatticeAnchor, PortBoundary, ProgramPoint, Scoped, + ScopedSparseStore, SparseStore, body_topology, cfg_topology, }; // Semantic keys (*what* a rule means — the `Interpretable`/`Interp::Semantics` @@ -144,10 +148,10 @@ pub use kirin_derive_interpreter::{FunctionEntry, InterpDispatch, Interpretable} /// (`impl SemanticKey for MyKey { type Shape = ...; }`). pub mod dialect { pub use crate::{ - AnalysisShape, BranchCondition, CallEffect, Callee, ClassicLiveness, ClassicLivenessInterp, - DemandInterp, DenseBackwardEffect, DenseBackwardInterp, DenseBackwardShape, - DenseForwardShape, Edge, ForwardEval, FunctionBody, FunctionEntry, HasProductValue, Interp, - Interpretable, InterpreterError, PointFacts, SemanticKey, SparseBackwardEffect, + AnalysisShape, Body, BranchCondition, CallEffect, CallableBody, Callee, ClassicLiveness, + ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, DenseBackwardInterp, + DenseBackwardShape, DenseForwardShape, Edge, ForwardEval, FunctionEntry, HasProductValue, + Interp, Interpretable, InterpreterError, PointFacts, SemanticKey, SparseBackwardEffect, SparseBackwardInterp, SparseBackwardShape, SparseForwardEffect, SparseForwardInterp, SparseForwardShape, StrongDemand, SuccessorEdge, }; @@ -160,7 +164,7 @@ pub mod engine { AbstractFrameDriver, AbstractInterpreter, BodyFrame, CallContext, CallFrame, Callee, Completion, ConcreteInterpreter, ContextInsensitive, CrossStageLinker, DenseBackwardCompletion, DenseBackwardFrameDriver, DenseBackwardInterp, - DenseBackwardInterpreter, DenseBlockFrame, DenseFrameBuild, Env, + DenseBackwardInterpreter, DenseBlockFrame, DenseFrameBuild, DiGraphFrame, Env, ForwardDataflowFrameDriver, ForwardFrameDriver, Frame, FrameBuild, FrameDriver, FrameEffect, FrameEngine, FunctionTarget, Interp, InterpDispatch, InterpreterError, Linker, SameStageLinker, SparseBackwardInterp, SparseBackwardInterpreter, SparseForwardInterp, diff --git a/crates/kirin-liveness/src/lib.rs b/crates/kirin-liveness/src/lib.rs index 56f90893a2..bb54c9b65b 100644 --- a/crates/kirin-liveness/src/lib.rs +++ b/crates/kirin-liveness/src/lib.rs @@ -26,7 +26,7 @@ pub use live::{Live, LiveSet}; pub use result::{DemandResult, DenseLivenessResult}; use kirin_interpreter::{ - DenseBackwardInterpreter, DenseBackwardTransfer, InterpDispatch, InterpreterError, + Body, DenseBackwardInterpreter, DenseBackwardTransfer, InterpDispatch, InterpreterError, SparseBackwardDriver, SparseBackwardInterpreter, StageQuery, StandardDenseBackwardFrame, }; use kirin_ir::{Cfg, CompileStage, Pipeline, StageMeta}; @@ -41,30 +41,31 @@ pub type Demand<'ir, S, E = InterpreterError> = SparseBackwardInterpreter<'ir, S pub type DenseLiveness<'ir, S, E = InterpreterError, F = StandardDenseBackwardFrame> = DenseBackwardInterpreter<'ir, S, LiveSet, E, F>; -/// Run strong liveness (sparse backward demand) over `cfg` in `stage`. +/// Run strong liveness (sparse backward demand) over `body` in `stage`. pub fn analyze_demand<'ir, S>( pipeline: &'ir Pipeline, stage: CompileStage, - cfg: Cfg, + body: impl Into, ) -> Result where S: StageMeta + StageQuery + InterpDispatch>, { + let body = body.into(); let mut engine = Demand::::new(pipeline); - engine.analyze(stage, cfg)?; - Ok(DemandResult::from_engine(&engine, stage, cfg)) + engine.analyze(stage, body)?; + Ok(DemandResult::from_engine(&engine, stage, body)) } -/// Run classic per-point liveness (dense backward) over `cfg` in `stage`, +/// Run classic per-point liveness (dense backward) over `body` in `stage`, /// with the standard (structured-control-free) frames. Languages with scf /// compose [`DenseLiveness`] with their own frame type and build the result /// via [`DenseLivenessResult::from_engine`]. pub fn analyze_dense<'ir, S>( pipeline: &'ir Pipeline, stage: CompileStage, - cfg: Cfg, + body: impl Into, ) -> Result where S: StageMeta @@ -79,7 +80,8 @@ where >, >, { + let body = body.into(); let mut engine = DenseLiveness::::new(pipeline); - engine.analyze(stage, cfg)?; - DenseLivenessResult::from_engine(&mut engine, stage, cfg) + engine.analyze(stage, body)?; + DenseLivenessResult::from_engine(&mut engine, stage, body) } diff --git a/crates/kirin-liveness/src/result.rs b/crates/kirin-liveness/src/result.rs index b7e337608f..93528e2de5 100644 --- a/crates/kirin-liveness/src/result.rs +++ b/crates/kirin-liveness/src/result.rs @@ -2,9 +2,9 @@ //! per-point sets (classic liveness), plus their composition. use kirin_interpreter::{ - DenseBackwardCompletion, DenseBackwardDriver, DenseBackwardInterpreter, DenseBackwardTransfer, - DenseBlockStore, DenseFrameBuild, DensePointStore, Frame, InterpDispatch, InterpreterError, - ProgramPoint, SparseBackwardInterpreter, StageQuery, + Body, DenseBackwardCompletion, DenseBackwardDriver, DenseBackwardInterpreter, + DenseBackwardTransfer, DenseBlockStore, DenseFrameBuild, DensePointStore, Frame, + InterpDispatch, InterpreterError, ProgramPoint, SparseBackwardInterpreter, StageQuery, }; use kirin_ir::{Block, Cfg, CompileStage, Lattice, SSAValue, StageMeta, Statement}; @@ -21,10 +21,10 @@ impl DemandResult { pub(crate) fn from_engine( engine: &SparseBackwardInterpreter<'_, S, Live, InterpreterError>, stage: CompileStage, - cfg: Cfg, + body: impl Into, ) -> Self { // The engine's sparse fact view; the demand set is its live support. - let facts = engine.fact_store(stage, cfg); + let facts = engine.fact_store(stage, body); let demanded = facts .iter() .filter(|(_, fact)| fact.is_live()) @@ -64,7 +64,7 @@ impl DenseLivenessResult { pub fn from_engine<'ir, S, F>( engine: &mut DenseBackwardInterpreter<'ir, S, LiveSet, InterpreterError, F>, stage: CompileStage, - cfg: Cfg, + body: impl Into, ) -> Result where S: StageMeta @@ -75,14 +75,15 @@ impl DenseLivenessResult { Completion = DenseBackwardCompletion, > + DenseFrameBuild, { + let body = body.into(); let mut blocks = DenseBlockStore::new(); for block in engine.cfg_blocks() { - if let Some(summary) = engine.block_summary(stage, cfg, block) { + if let Some(summary) = engine.block_summary(stage, body, block) { blocks.set_entry(block, summary.live_in.clone()); blocks.set_exit(block, summary.live_out.clone()); } } - let points = engine.reconstruct_points(stage, cfg)?; + let points = engine.reconstruct_points(stage, body)?; Ok(Self { blocks, points }) } diff --git a/crates/kirin-test-languages/Cargo.toml b/crates/kirin-test-languages/Cargo.toml index e9c10660bd..6813b4942d 100644 --- a/crates/kirin-test-languages/Cargo.toml +++ b/crates/kirin-test-languages/Cargo.toml @@ -21,6 +21,7 @@ kirin-derive-chumsky = { workspace = true, optional = true } default = [] simple-language = ["kirin-ir/derive"] arith-function-language = ["kirin-ir/derive", "kirin-arith", "kirin-cf", "kirin-function", "parser", "pretty"] +graph-function-language = ["kirin-ir/derive", "kirin-arith", "kirin-cf", "kirin-constant", "kirin-function", "parser", "pretty"] bitwise-function-language = ["kirin-ir/derive", "kirin-arith", "kirin-bitwise", "kirin-cf", "kirin-function", "parser", "pretty"] callable-language = ["kirin-ir/derive", "kirin-arith", "kirin-function", "parser", "pretty"] namespaced-language = ["kirin-ir/derive", "kirin-arith", "kirin-cf", "kirin-function", "parser", "pretty"] diff --git a/crates/kirin-test-languages/src/arith_function_language.rs b/crates/kirin-test-languages/src/arith_function_language.rs index 898da14da0..b39ac27539 100644 --- a/crates/kirin-test-languages/src/arith_function_language.rs +++ b/crates/kirin-test-languages/src/arith_function_language.rs @@ -34,7 +34,7 @@ pub enum ArithFunctionLanguage { #[cfg(feature = "interpreter")] mod interpreter { use kirin_interpreter::dialect::{ - ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, FunctionBody, + CallableBody, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, FunctionEntry, Interp, Interpretable, InterpreterError, StrongDemand, }; use kirin_ir::{HasBottom, Product}; @@ -79,10 +79,10 @@ mod interpreter { &self, args: Product, interp: &mut I, - ) -> Result, I::Error> { + ) -> Result, I::Error> { match self { ArithFunctionLanguage::Function { body, .. } => { - Ok(FunctionBody::new(*body).args(args)) + Ok(CallableBody::new(*body).args(args)) } _ => Err(I::Error::from(InterpreterError::NotCallable( interp.statement(), diff --git a/crates/kirin-test-languages/src/graph_function_language.rs b/crates/kirin-test-languages/src/graph_function_language.rs new file mode 100644 index 0000000000..ffb8896f02 --- /dev/null +++ b/crates/kirin-test-languages/src/graph_function_language.rs @@ -0,0 +1,149 @@ +//! Mixed graph/SSA test language: regular SSA IR (arith + cf + function +//! calls over `Cfg` bodies) combined with `DiGraph` computational-graph +//! bodies — the acceptance shape from issue #667. Adds a linear (`Block`- +//! bodied) callable and an inline graph-owning statement so both interpreter +//! entry paths (call and `Push`) are exercised. + +use kirin_arith::{Arith, ArithType, ArithValue}; +use kirin_cf::ControlFlow; +use kirin_constant::Constant; +use kirin_function::{Call, Return}; +use kirin_ir::{Block, Cfg, DiGraph, Dialect, Placeholder as _, ResultValue, SSAValue, Signature}; + +#[derive(Debug, Clone, PartialEq, Dialect)] +#[cfg_attr(feature = "parser", derive(kirin_chumsky::HasParser))] +#[cfg_attr(feature = "pretty", derive(kirin_derive_chumsky::PrettyPrint))] +#[kirin(builders, type = ArithType, crate = kirin_ir)] +#[cfg_attr(feature = "parser", chumsky(crate = kirin_chumsky))] +#[cfg_attr(feature = "pretty", pretty(crate = kirin_prettyless))] +pub enum GraphFunctionLanguage { + /// Standard Cfg-bodied function. + #[cfg_attr( + any(feature = "parser", feature = "pretty"), + chumsky(format = "fn {:name}{sig} {body}") + )] + Function { + body: Cfg, + sig: Signature, + }, + /// DiGraph-bodied callable (a computational graph as a function body). + #[cfg_attr( + any(feature = "parser", feature = "pretty"), + chumsky(format = "fn {:name}{sig} {body}") + )] + GraphFunction { + body: DiGraph, + sig: Signature, + }, + /// Linear (single-`Block`) callable: a flat instruction list. + #[cfg_attr( + any(feature = "parser", feature = "pretty"), + chumsky(format = "fn {:name}{sig} {body}") + )] + LinearFunction { + body: Block, + sig: Signature, + }, + /// Inline graph evaluation: enters its owned digraph via a pushed frame. + #[cfg_attr( + any(feature = "parser", feature = "pretty"), + chumsky(format = "$graph_eval {lhs}, {rhs} {graph} -> {result:type}") + )] + GraphEval { + lhs: SSAValue, + rhs: SSAValue, + graph: DiGraph, + result: ResultValue, + }, + #[wraps] + Arith(Arith), + #[wraps] + Cf(ControlFlow), + #[wraps] + Constant(Constant), + #[wraps] + Call(Call), + #[wraps] + Return(Return), +} + +// Manual interpreter impls: the inline function/graph variants keep this +// enum off the `#[derive(Interpretable)]` wraps-delegation path. +#[cfg(feature = "interpreter")] +mod interpreter { + use kirin_arith::{ArithValue, CheckedDiv, CheckedRem, interpreter::DivisionByZero}; + use kirin_interpreter::BranchCondition; + use kirin_interpreter::{ + CallableBody, DiGraphFrame, ForwardEval, FrameBuild, FunctionEntry, Interp, Interpretable, + InterpreterError, SparseForwardEffect, SparseForwardInterp, + }; + use kirin_ir::{Product, SSAValue}; + + use super::GraphFunctionLanguage; + + impl Interpretable for GraphFunctionLanguage + where + I: SparseForwardInterp, + I::Frame: FrameBuild, + I::Value: std::ops::Add + + std::ops::Sub + + std::ops::Mul + + std::ops::Neg + + CheckedDiv + + CheckedRem + + BranchCondition + + TryFrom, + I::Error: From + From<>::Error>, + { + fn interpret(&self, interp: &mut I) -> Result { + match self { + GraphFunctionLanguage::Function { .. } + | GraphFunctionLanguage::GraphFunction { .. } + | GraphFunctionLanguage::LinearFunction { .. } => Ok(SparseForwardEffect::Next), + GraphFunctionLanguage::GraphEval { + lhs, + rhs, + graph, + result, + } => { + let args: Product = [interp.read(*lhs)?, interp.read(*rhs)?] + .into_iter() + .collect(); + let frame = DiGraphFrame::nested(interp.stage(), interp.index(), *graph, args); + Ok(SparseForwardEffect::Push { + frame: I::Frame::from_digraph(frame), + results: [SSAValue::from(*result)].into_iter().collect(), + }) + } + GraphFunctionLanguage::Arith(op) => op.interpret(interp), + GraphFunctionLanguage::Cf(op) => op.interpret(interp), + GraphFunctionLanguage::Constant(op) => op.interpret(interp), + GraphFunctionLanguage::Call(op) => op.interpret(interp), + GraphFunctionLanguage::Return(op) => op.interpret(interp), + } + } + } + + impl FunctionEntry for GraphFunctionLanguage { + fn function_entry( + &self, + args: Product, + interp: &mut I, + ) -> Result, I::Error> { + match self { + GraphFunctionLanguage::Function { body, .. } => { + Ok(CallableBody::new(*body).args(args)) + } + GraphFunctionLanguage::GraphFunction { body, .. } => { + Ok(CallableBody::new(*body).args(args)) + } + GraphFunctionLanguage::LinearFunction { body, .. } => { + Ok(CallableBody::new(*body).args(args)) + } + _ => Err(I::Error::from(InterpreterError::NotCallable( + interp.statement(), + ))), + } + } + } +} diff --git a/crates/kirin-test-languages/src/lib.rs b/crates/kirin-test-languages/src/lib.rs index 64c6851a39..b52ec3cd7e 100644 --- a/crates/kirin-test-languages/src/lib.rs +++ b/crates/kirin-test-languages/src/lib.rs @@ -7,6 +7,8 @@ mod arith_function_language; mod bitwise_function_language; #[cfg(feature = "callable-language")] mod callable_language; +#[cfg(feature = "graph-function-language")] +mod graph_function_language; #[cfg(feature = "namespaced-language")] mod namespaced_language; #[cfg(feature = "simple-language")] @@ -20,6 +22,8 @@ pub use arith_function_language::ArithFunctionLanguage; pub use bitwise_function_language::BitwiseFunctionLanguage; #[cfg(feature = "callable-language")] pub use callable_language::CallableLanguage; +#[cfg(feature = "graph-function-language")] +pub use graph_function_language::GraphFunctionLanguage; #[cfg(feature = "namespaced-language")] pub use namespaced_language::NamespacedLanguage; #[cfg(feature = "simple-language")] diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index 74d98673a8..7336cf6809 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -224,7 +224,7 @@ operations are implemented. ```rust pub trait FunctionEntry: Dialect { fn function_entry(&self, args: Product, interp: &mut I) - -> Result, I::Error>; + -> Result, I::Error>; } ``` @@ -232,7 +232,7 @@ Like `Interpretable`, it receives the engine `interp` directly (function entry i forward-only, so there is no `Semantics` parameter). Statements that define function bodies (e.g. `kirin_function::Function`) -return the `FunctionBody { cfg, args }` to enter on invocation (the +return the `CallableBody { body, args }` to enter on invocation (the function-call entry descriptor — not a structured-control abstraction). On language enums it is derived; `#[callable]` marks the variants that forward, all others report `NotCallable`. diff --git a/example/toy-lang/src/interpreter/frame.rs b/example/toy-lang/src/interpreter/frame.rs index 630d5741ef..276b491e5a 100644 --- a/example/toy-lang/src/interpreter/frame.rs +++ b/example/toy-lang/src/interpreter/frame.rs @@ -11,8 +11,8 @@ use std::hash::Hash; use kirin_interpreter::engine::{ AbstractBlockFrame, AbstractCallFrame, AbstractCompletion, AbstractFrameBuild, - AbstractFrameDriver, BodyFrame, CallFrame, Completion, Frame, FrameBuild, FrameDriver, - FrameEffect, InterpreterError, SparseForwardInterp, + AbstractFrameDriver, BodyFrame, CallFrame, Completion, DiGraphFrame, Frame, FrameBuild, + FrameDriver, FrameEffect, InterpreterError, SparseForwardInterp, }; use kirin_scf::{ AbstractScfForFrame, AbstractScfIfFrame, BuildAbstractScfFor, BuildAbstractScfIf, BuildScfFor, @@ -27,6 +27,7 @@ use kirin_scf::{ pub enum ToyFrame { Body(BodyFrame), Call(CallFrame), + DiGraph(DiGraphFrame), ScfIf(ScfIfFrame), ScfFor(ScfForFrame), } @@ -38,6 +39,9 @@ impl FrameBuild for ToyFrame { fn from_call(frame: CallFrame) -> Self { ToyFrame::Call(frame) } + fn from_digraph(frame: DiGraphFrame) -> Self { + ToyFrame::DiGraph(frame) + } } impl BuildScfIf for ToyFrame { @@ -64,6 +68,7 @@ where match self { ToyFrame::Body(frame) => frame.step_into::(interp), ToyFrame::Call(frame) => frame.step_into::(interp), + ToyFrame::DiGraph(frame) => frame.step_into::(interp), ToyFrame::ScfIf(frame) => frame.step_into::(interp), ToyFrame::ScfFor(frame) => frame.step_into::(interp), } @@ -73,6 +78,7 @@ where match self { ToyFrame::Body(frame) => Ok(frame.resume_done_into::()), ToyFrame::Call(frame) => frame.resume_done_into::().map_err(I::Error::from), + ToyFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), ToyFrame::ScfIf(frame) => frame.resume_done_into::(), ToyFrame::ScfFor(frame) => frame.resume_done_into::(), } @@ -86,6 +92,7 @@ where match self { ToyFrame::Body(frame) => frame.resume_into::(completion, interp), ToyFrame::Call(frame) => frame.resume_into::(completion, interp), + ToyFrame::DiGraph(frame) => frame.resume_into::(completion, interp), ToyFrame::ScfIf(frame) => frame.resume_into::(completion), ToyFrame::ScfFor(frame) => frame.resume_into::(completion, interp), } diff --git a/example/toy-lang/src/interpreter/tests.rs b/example/toy-lang/src/interpreter/tests.rs index 3869fd65d0..7057924243 100644 --- a/example/toy-lang/src/interpreter/tests.rs +++ b/example/toy-lang/src/interpreter/tests.rs @@ -567,8 +567,8 @@ mod advanced { use kirin_interpreter::engine::{ AbstractBlockFrame, AbstractCallFrame, AbstractCompletion, AbstractFrameBuild, AbstractFrameDriver, BodyFrame, CallContext, CallFrame, Completion, ConcreteInterpreter, - CrossStageLinker, Frame, FrameBuild, FrameDriver, FrameEffect, InterpreterError, - SparseForwardInterp, SparseForwardInterpreter, expect_single, + CrossStageLinker, DiGraphFrame, Frame, FrameBuild, FrameDriver, FrameEffect, + InterpreterError, SparseForwardInterp, SparseForwardInterpreter, expect_single, }; use kirin_scf::{ AbstractScfForFrame, AbstractScfIfFrame, BuildAbstractScfFor, BuildAbstractScfIf, @@ -600,6 +600,7 @@ mod advanced { enum TracingFrame { Body(BodyFrame), Call(CallFrame), + DiGraph(DiGraphFrame), ScfIf(ScfIfFrame), ScfFor(ScfForFrame), } @@ -611,6 +612,9 @@ mod advanced { fn from_call(frame: CallFrame) -> Self { TracingFrame::Call(frame) } + fn from_digraph(frame: DiGraphFrame) -> Self { + TracingFrame::DiGraph(frame) + } } impl BuildScfIf for TracingFrame { @@ -643,6 +647,7 @@ mod advanced { TRACE.with(|t| t.borrow_mut().calls += 1); frame.step_into::(interp) } + TracingFrame::DiGraph(frame) => frame.step_into::(interp), TracingFrame::ScfIf(frame) => frame.step_into::(interp), TracingFrame::ScfFor(frame) => frame.step_into::(interp), } @@ -657,6 +662,7 @@ mod advanced { TracingFrame::Call(frame) => { frame.resume_done_into::().map_err(I::Error::from) } + TracingFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), TracingFrame::ScfIf(frame) => frame.resume_done_into::(), TracingFrame::ScfFor(frame) => frame.resume_done_into::(), } @@ -670,6 +676,7 @@ mod advanced { match self { TracingFrame::Body(frame) => frame.resume_into::(completion, interp), TracingFrame::Call(frame) => frame.resume_into::(completion, interp), + TracingFrame::DiGraph(frame) => frame.resume_into::(completion, interp), TracingFrame::ScfIf(frame) => frame.resume_into::(completion), TracingFrame::ScfFor(frame) => frame.resume_into::(completion, interp), } diff --git a/tests/body_kinds.rs b/tests/body_kinds.rs new file mode 100644 index 0000000000..0b1e9d332a --- /dev/null +++ b/tests/body_kinds.rs @@ -0,0 +1,158 @@ +//! Acceptance tests for generic interpreter bodies (issue #667): a mixed +//! language where regular Cfg-SSA code and DiGraph computational graphs +//! call into each other, plus linear (Block-bodied) callables. + +use kirin::prelude::*; +use kirin_arith::{ArithConversionError, interpreter::DivisionByZero}; +use kirin_interpreter::{ + ConcreteInterpreter, InterpreterError, SameStageLinker, StandardFrame, expect_single, +}; +use kirin_test_languages::GraphFunctionLanguage; + +/// Total error for the test engine: the framework error plus the value +/// conversion/trap errors the mixed language's rules can raise. +#[derive(Debug)] +enum TestError { + Core(InterpreterError), + ArithConversion(ArithConversionError), + DivisionByZero, +} + +impl From for TestError { + fn from(error: InterpreterError) -> Self { + Self::Core(error) + } +} +impl From for TestError { + fn from(error: ArithConversionError) -> Self { + Self::ArithConversion(error) + } +} +impl From for TestError { + fn from(_: DivisionByZero) -> Self { + Self::DivisionByZero + } +} + +type L = StageInfo; +type Engine<'ir> = + ConcreteInterpreter<'ir, L, i64, TestError, SameStageLinker, StandardFrame>; + +fn parse(program: &str) -> Pipeline { + let mut pipeline: Pipeline = Pipeline::new(); + ParsePipelineText::parse(&mut pipeline, program).expect("program parses"); + pipeline +} + +fn run(pipeline: &Pipeline, function: &str, args: &[i64]) -> Result { + let mut interp: Engine<'_> = ConcreteInterpreter::new(pipeline).with_linker(SameStageLinker); + expect_single(interp.call_by_name("test", function, args.iter().copied())?) +} + +/// Entry path 1 (the call path): a Cfg-bodied `main` calls a DiGraph-bodied +/// callable; the engine builds a `DiGraphFrame`, runs the graph's arith +/// nodes in dependency order, and returns its yields. +#[test] +fn cfg_main_calls_digraph_function() { + let pipeline = parse( + r#" +stage @test fn @gadd(i64, i64) -> i64; +stage @test fn @main() -> i64; + +specialize @test fn @gadd(i64, i64) -> i64 digraph ^g0(%x: i64, %y: i64) { + %s = add %x, %y -> i64; + yield %s; +} + +specialize @test fn @main() -> i64 { + ^entry() { + %a = constant 2 -> i64; + %b = constant 3 -> i64; + %r = call.named @gadd(%a, %b) -> i64; + ret %r; + } +} +"#, + ); + assert_eq!(run(&pipeline, "main", &[]).unwrap(), 5); +} + +/// Entry path 2 (the dialect-frame path): a statement inside a Cfg block +/// owns a DiGraph body and enters it with `Push` — the same way `scf.if` +/// enters its Block arms. +#[test] +fn cfg_statement_pushes_digraph_body() { + let pipeline = parse( + r#" +stage @test fn @main() -> i64; + +specialize @test fn @main() -> i64 { + ^entry() { + %a = constant 20 -> i64; + %b = constant 22 -> i64; + %r = graph_eval %a, %b digraph ^g0(%x: i64, %y: i64) { + %s = add %x, %y -> i64; + yield %s; + } -> i64; + ret %r; + } +} +"#, + ); + assert_eq!(run(&pipeline, "main", &[]).unwrap(), 42); +} + +/// A linear (single-Block) callable: the flat-instruction-list function +/// shape (QOS-style compile targets). Exits with `Return`. +#[test] +fn linear_block_callable() { + let pipeline = parse( + r#" +stage @test fn @ladd(i64, i64) -> i64; +stage @test fn @main() -> i64; + +specialize @test fn @ladd(i64, i64) -> i64 ^body(%x: i64, %y: i64) { + %s = add %x, %y -> i64; + ret %s; +} + +specialize @test fn @main() -> i64 { + ^entry() { + %a = constant 40 -> i64; + %b = constant 2 -> i64; + %r = call.named @ladd(%a, %b) -> i64; + ret %r; + } +} +"#, + ); + assert_eq!(run(&pipeline, "main", &[]).unwrap(), 42); +} + +/// Graph nodes run in dependency order, not declaration order: the yield +/// depends on a node declared after its operand producer. +#[test] +fn digraph_runs_in_topological_order() { + let pipeline = parse( + r#" +stage @test fn @g(i64) -> i64; +stage @test fn @main() -> i64; + +specialize @test fn @g(i64) -> i64 digraph ^g0(%x: i64) { + %d = mul %c, %c -> i64; + %c = add %x, %x -> i64; + yield %d; +} + +specialize @test fn @main() -> i64 { + ^entry() { + %a = constant 3 -> i64; + %r = call.named @g(%a) -> i64; + ret %r; + } +} +"#, + ); + // (3 + 3)^2 = 36 — requires running `add` before `mul` despite text order. + assert_eq!(run(&pipeline, "main", &[]).unwrap(), 36); +} From 0a27ea643516093eabeb3ab97870eefc920b5bf4 Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Wed, 15 Jul 2026 15:13:39 -0400 Subject: [PATCH 4/6] Refactor interpreter frame structure and update toy language integration - Renamed `BodyFrame` to `BlockFrame` and adjusted related documentation to reflect the new terminology. - Enhanced the `ToyFrame` enum to include `BlockFrame` and `CfgFrame`, replacing `BodyFrame`. - Updated `FrameBuild` implementations in `ToyFrame` and `TracingFrame` to accommodate the new frame types. - Revised tests in `tests/body_kinds.rs` to ensure compatibility with the updated frame structure. - Added comprehensive tests for structured control flow (SCF) operations, including `scf.if` and `scf.for`, demonstrating their integration with the new frame types. - Implemented a custom callable-UnGraph policy to handle ungraph traversal, ensuring proper execution order and output handling. --- AGENTS.md | 6 +- Cargo.toml | 2 +- crates/kirin-interpreter/src/core/effect.rs | 14 +- crates/kirin-interpreter/src/core/error.rs | 4 +- .../src/engines/concrete/frames.rs | 671 ------------------ .../engines/concrete/frames/block_cursor.rs | 107 +++ .../engines/concrete/frames/block_frame.rs | 117 +++ .../src/engines/concrete/frames/call_frame.rs | 190 +++++ .../src/engines/concrete/frames/cfg_frame.rs | 140 ++++ .../engines/concrete/frames/digraph_frame.rs | 178 +++++ .../src/engines/concrete/frames/mod.rs | 63 ++ .../src/engines/concrete/frames/protocol.rs | 87 +++ .../engines/concrete/frames/standard_frame.rs | 69 ++ .../src/engines/concrete/interp.rs | 48 +- .../src/engines/concrete/mod.rs | 3 +- .../src/engines/sparse_forward/frames.rs | 2 +- crates/kirin-interpreter/src/lib.rs | 15 +- crates/kirin-scf/src/interpreter.rs | 49 +- .../src/graph_function_language.rs | 27 +- docs/design/interpreter/index.md | 68 +- example/toy-lang/src/interpreter/frame.rs | 32 +- example/toy-lang/src/interpreter/tests.rs | 45 +- tests/body_kinds.rs | 585 ++++++++++++++- 23 files changed, 1712 insertions(+), 810 deletions(-) delete mode 100644 crates/kirin-interpreter/src/engines/concrete/frames.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/block_cursor.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/block_frame.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/cfg_frame.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/digraph_frame.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/mod.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs create mode 100644 crates/kirin-interpreter/src/engines/concrete/frames/standard_frame.rs diff --git a/AGENTS.md b/AGENTS.md index e0523c3a4d..9dc43a1eb8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -149,15 +149,15 @@ For user-defined dialects not in this table, ask the user for domain context dur - **Dialects are engine-blind**: one `Interpretable` impl serves concrete execution and abstract interpretation; the value domain decides. Undecided conditions (`BranchCondition::is_truthy` / `ForLoopValue::loop_condition` returning `None`) are read in the rule and handed to the dialect's own frame, which rejects them under concrete execution and explores+joins under abstract. (`Branch` is the cf CFG analogue, driven by the engine's CFG frame.) Never write per-engine dialect impls — but a control dialect's *frame* may have distinct concrete/abstract forms, built per-engine through a dialect dispatch trait. -- **Ordinary vs control dialects (frame ownership)**: Ordinary dialects (arith, cmp, constant, bitwise, tuple, ordinary cf branch ops) implement statement-local semantics with the `SparseForwardInterp` helpers and **never see frames**. A dialect whose operations own *structured traversal* defines **dialect-owned frames** and pushes them with `SparseForwardEffect::Push`. The framework's `BodyFrame` / `AbstractBlockFrame` (single-block body walkers) are reusable **building blocks**, not framework-owned structured semantics — a dialect frame may build one to walk a chosen body, but the structured *decision* and result binding stay in the dialect frame. +- **Ordinary vs control dialects (frame ownership)**: Ordinary dialects (arith, cmp, constant, bitwise, tuple, ordinary cf branch ops) implement statement-local semantics with the `SparseForwardInterp` helpers and **never see frames**. A dialect whose operations own *structured traversal* defines **dialect-owned frames** and pushes them with `SparseForwardEffect::Push`. The framework's `BlockFrame` / `AbstractBlockFrame` (single-block body walkers) are reusable **building blocks**, not framework-owned structured semantics — a dialect frame may build one to walk a chosen body, but the structured *decision* and result binding stay in the dialect frame. - **SCF is the example**: `scf.if` → `kirin_scf::ScfIfFrame` (concrete) / `AbstractScfIfFrame` (abstract); `scf.for` → `ScfForFrame` / `AbstractScfForFrame`. Each is built per-engine through a dialect dispatch trait (`ScfIfDispatch`/`ScfForDispatch`) and returned as `SparseForwardEffect::Push`. The if frame owns picking the arm (concrete) or exploring both arms + joining (abstract); the for frame owns the loop-carried join/widen fixpoint. A language that uses SCF composes a total frame type embedding the standard frames plus `ScfIfFrame`/`ScfForFrame` (via `BuildScfIf`/`BuildScfFor` and the abstract equivalents); see `example/toy-lang`'s `ToyFrame`/`ToyAbstractFrame`. (Future structured dialects would follow the same pattern; only the existing SCF ops are implemented.) - **Calling conventions are linkers**: `Linker` resolves `Callee` to a `(stage, specialization, body)` target and is passed to engines by value (`.with_linker(..)`). `SameStageLinker` is the default; `CrossStageLinker` routes calls to whichever stage has a live specialization, which is all that cross-language execution *and* cross-language analysis require. Policy must be a component (field), never a trait impl on an engine type. -- **Engines run frames; traversal lives in frames**: both engines share one driver loop, `drive_frames` (`core/frame.rs`), over the direction-neutral `Frame` protocol — pop the top frame, `step`, apply the returned `FrameEffect`, owning no traversal logic. `FrameEngine` is the minimal anchor (just a total `Error`); every `Interp` is a `FrameEngine` by blanket impl, so frames are decoupled from the forward eval engine and stay reusable. `ConcreteInterpreter<'ir, S, V, E, Lk, F = StandardFrame>` uses the concrete standard frames (`engines/concrete/frames.rs`: `BodyFrame`/`CallFrame`, single-path). `SparseForwardInterpreter<'ir, S, V, E, Lk, P = ContextInsensitive, F = StandardAbstractFrame>` (the forward abstract engine, implementing the `AbstractInterpreter` trait) uses the forward abstract standard frames (`engines/sparse_forward/frames.rs`: `AbstractBlockFrame`/`AbstractCallFrame`) over the shared owner-summary fixpoint driver — block owners with widening, `Branch` exploration, and per-key interprocedural summaries (caller re-enqueueing incl. same-key self-recursion). The backward engines (`SparseBackwardInterpreter`, `DenseBackwardInterpreter`) implement the same `AbstractInterpreter` trait with their own fact stores/effects/frames, wrapping the same fixpoint driver around their own summary-free `*Transfer` inner `Interp`s. The default `StandardFrame`/`StandardAbstractFrame` are structured-control-free; a language with a structured dialect supplies a custom `F` embedding the standard frames (via `FrameBuild`/`AbstractFrameBuild`) plus that dialect's frames. Analysis crates are a lattice + a policy/frame choice + an engine type alias (see `kirin-constprop` for the forward shape and `kirin-liveness` for the two backward shapes). +- **Engines run frames; traversal lives in frames**: both engines share one driver loop, `drive_frames` (`core/frame.rs`), over the direction-neutral `Frame` protocol — pop the top frame, `step`, apply the returned `FrameEffect`, owning no traversal logic. `FrameEngine` is the minimal anchor (just a total `Error`); every `Interp` is a `FrameEngine` by blanket impl, so frames are decoupled from the forward eval engine and stay reusable. `ConcreteInterpreter<'ir, S, V, E, Lk, F = StandardFrame>` uses the concrete standard frames (`engines/concrete/frames/`: the representation walkers `BlockFrame`/`CfgFrame`/`DiGraphFrame` plus the `CallFrame` call boundary, single-path; `UnGraph` traversal has no framework default — a compiler supplies it via `FrameBuild::from_ungraph_entry`). `SparseForwardInterpreter<'ir, S, V, E, Lk, P = ContextInsensitive, F = StandardAbstractFrame>` (the forward abstract engine, implementing the `AbstractInterpreter` trait) uses the forward abstract standard frames (`engines/sparse_forward/frames.rs`: `AbstractBlockFrame`/`AbstractCallFrame`) over the shared owner-summary fixpoint driver — block owners with widening, `Branch` exploration, and per-key interprocedural summaries (caller re-enqueueing incl. same-key self-recursion). The backward engines (`SparseBackwardInterpreter`, `DenseBackwardInterpreter`) implement the same `AbstractInterpreter` trait with their own fact stores/effects/frames, wrapping the same fixpoint driver around their own summary-free `*Transfer` inner `Interp`s. The default `StandardFrame`/`StandardAbstractFrame` are structured-control-free; a language with a structured dialect supplies a custom `F` embedding the standard frames (via `FrameBuild`/`AbstractFrameBuild`) plus that dialect's frames. Analysis crates are a lattice + a policy/frame choice + an engine type alias (see `kirin-constprop` for the forward shape and `kirin-liveness` for the two backward shapes). -- **Customizing traversal**: `core/frame.rs` defines the shared `Frame`/`FrameEffect`/`drive_frames` protocol plus forward driver traits. Concrete custom frames embed `BodyFrame`/`CallFrame` via `FrameBuild`; forward abstract custom frames embed `Abstract*Frame`s via `AbstractFrameBuild`. Structured dialects may push dialect-owned frames with `SparseForwardEffect::Push`; ordinary dialects never name frame types. Abstract summary keying and join/widen policy stay in `CallContext`/`WideningStrategy`. +- **Customizing traversal**: `core/frame.rs` defines the shared `Frame`/`FrameEffect`/`drive_frames` protocol plus forward driver traits. Concrete custom frames embed `BlockFrame`/`CfgFrame`/`DiGraphFrame`/`CallFrame` via `FrameBuild` (whose `from_ungraph_entry` hook supplies a callable-UnGraph policy); forward abstract custom frames embed `Abstract*Frame`s via `AbstractFrameBuild`. Structured dialects may push dialect-owned frames with `SparseForwardEffect::Push`; ordinary dialects never name frame types. Abstract summary keying and join/widen policy stay in `CallContext`/`WideningStrategy`. - **Stage dispatch**: stage enums add `#[derive(InterpDispatch)]` next to `StageMeta`/`ParseDispatch`; single-language pipelines get a blanket impl. `InterpDispatch` is keyed on the engine alone; the dispatched key is always `I::Semantics`. The engine sets its current location then passes itself to dispatch, which forwards to the matching `Interpretable`/`FunctionEntry` rule. Engine-internal IR queries go through `StageQuery`. diff --git a/Cargo.toml b/Cargo.toml index bf7ed375f5..0788d53acc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ kirin-cmp = { workspace = true } kirin-constant = { workspace = true } kirin-function = { workspace = true } kirin-scf = { workspace = true } -kirin-interpreter = { workspace = true } +kirin-interpreter = { workspace = true, features = ["derive"] } kirin-test-languages = { workspace = true, features = [ "arith-function-language", "graph-function-language", diff --git a/crates/kirin-interpreter/src/core/effect.rs b/crates/kirin-interpreter/src/core/effect.rs index 58be464716..4176a2e1f0 100644 --- a/crates/kirin-interpreter/src/core/effect.rs +++ b/crates/kirin-interpreter/src/core/effect.rs @@ -130,12 +130,14 @@ pub enum Callee { /// /// This is the function-call entry descriptor — the call mechanism, not a /// structured-control abstraction. A [`FunctionEntry`](crate::FunctionEntry) -/// rule returns one; the engine picks the walker that matches the body kind. -/// Any body kind may be callable — the statement declaring itself callable -/// defines the semantics; the framework supplies default walkers for `Cfg`, -/// `Block` (linear functions), and `DiGraph`, and rejects `UnGraph` with -/// [`InterpreterError::NoDefaultWalker`](crate::InterpreterError) unless a -/// dialect supplies its own walk. +/// rule returns one; the call boundary picks the walker that matches the +/// body kind. Any body kind may be callable — the statement declaring itself +/// callable defines the semantics; the framework supplies default walkers +/// for `Cfg`, `Block`, and `DiGraph`, while `UnGraph` traversal is a +/// dialect/compiler-supplied policy (the concrete engine's +/// `FrameBuild::from_ungraph_entry` hook), rejected with +/// [`InterpreterError::NoDefaultWalker`](crate::InterpreterError) when no +/// policy is provided. pub struct CallableBody { pub body: Body, pub args: Product, diff --git a/crates/kirin-interpreter/src/core/error.rs b/crates/kirin-interpreter/src/core/error.rs index f1e7605eef..96f57dd120 100644 --- a/crates/kirin-interpreter/src/core/error.rs +++ b/crates/kirin-interpreter/src/core/error.rs @@ -45,12 +45,10 @@ pub enum InterpreterError { NoDefaultWalker(crate::Body), #[error("digraph {0:?} has a cycle; the default walker only runs DAGs")] GraphHasCycle(kirin_ir::DiGraph), - #[error("CFG control flow (jump/branch) inside a structured or linear body")] + #[error("CFG control flow (jump/branch) inside a single-block or graph body")] CfgControlFlowInStructuredBody, #[error("block {0:?} fell through without a terminator effect")] BlockFellThrough(Block), - #[error("function body fell through without returning")] - FunctionBodyFellThrough, #[error("yield outside of an enclosing scope at {0:?}")] UnexpectedYield(Statement), #[error("statement {0:?} is not callable")] diff --git a/crates/kirin-interpreter/src/engines/concrete/frames.rs b/crates/kirin-interpreter/src/engines/concrete/frames.rs deleted file mode 100644 index cdde9416e4..0000000000 --- a/crates/kirin-interpreter/src/engines/concrete/frames.rs +++ /dev/null @@ -1,671 +0,0 @@ -//! The **concrete** implementation of the shared [`frame`](crate::core::frame) -//! protocol. -//! -//! These are the default total frames for [`ConcreteInterpreter`](crate::ConcreteInterpreter): -//! [`BodyFrame`] (walks a function-body CFG or a single body block) and -//! [`CallFrame`] (call/return). They implement the shared [`Frame`] trait by -//! consuming the dialect [`SparseForwardEffect`] and driving a single deterministic -//! path. Structured-control dialects do not get a framework "scope": they push -//! a frame **they own** through [`SparseForwardEffect::Push`] (that frame may build a -//! [`BodyFrame`] to walk a chosen body — a reusable building block, not -//! framework-owned structured semantics). A language that combines such a -//! dialect defines its own total frame enum embedding [`BodyFrame`]/[`CallFrame`] -//! via [`FrameBuild`] plus its dialect frames. The forward abstract analogue -//! lives in [`sparse_forward::frames`](crate::engines::sparse_forward::frames). - -use kirin_ir::{Block, Cfg, CompileStage, Product, SSAValue, Statement}; - -use crate::{ - Body, CallEffect, Callee, EnvIndex, Frame, FrameDriver, FrameEffect, InterpreterError, - SparseForwardEffect, SparseForwardInterp, -}; - -/// Completion payloads produced by the standard concrete frames. -/// -/// `Returned` bubbles a function return across frames to the enclosing -/// [`CallFrame`]; `Finished` carries the values a pushed body frame yielded back -/// to whoever pushed it (written into that push's result slots). -pub enum Completion { - /// A function returned these values; bubbles to the enclosing - /// [`CallFrame`], or finishes the run at the root. - Returned(Product), - /// A pushed body frame yielded these values to its pusher. - Finished(Product), -} - -/// Construction trait letting any total frame enum embed the standard concrete -/// frames. -/// -/// The default [`StandardFrame`] implements it trivially; a language that adds -/// structured-control dialects implements it on its own enum to reuse -/// [`BodyFrame`]/[`CallFrame`] traversal while adding its own dialect frames. -pub trait FrameBuild: Sized { - fn from_body(frame: BodyFrame) -> Self; - fn from_call(frame: CallFrame) -> Self; - fn from_digraph(frame: DiGraphFrame) -> Self; -} - -/// Traversal of one body: a function-body CFG (multi-block, with jumps) -/// or a single body block (scf-style, terminated by a yield). -/// How a [`BodyFrame`] treats its current block's control flow. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum BlockMode { - /// A block belonging to a CFG: `Jump`/`Branch` move between blocks. - CfgBlock, - /// A structured (scf-style) or linear-function body: a single block whose - /// exit is `Yield` (structured) or `Return` (linear function); - /// `Jump`/`Branch` are an error. - StructuredBody, -} - -pub struct BodyFrame { - stage: CompileStage, - index: EnvIndex, - owns_env: bool, - function_boundary: bool, - mode: BlockMode, - block: Block, - cursor: Option, - /// Entry arguments not yet bound. A body frame built by a dialect frame is - /// constructed without engine access — it binds on its first `step`, so - /// building it requires no [`FrameDriver`] (a dialect frame builds these as - /// plain values, no engine capability or trait-resolution cycle). - pending: Option>, - /// Result slots awaiting a pushed body frame's `Finished` completion. - resume_slots: Option>, - _marker: std::marker::PhantomData (V, E)>, -} - -impl BodyFrame -where - V: Clone, - E: From, -{ - /// Walk a function body: start at the entry block of `cfg`, binding - /// `args` to its parameters. Owns the activation and is the return boundary. - pub fn function( - interp: &mut I, - stage: CompileStage, - index: EnvIndex, - cfg: Cfg, - args: Product, - ) -> Result - where - I: FrameDriver, - { - let entry = interp - .cfg_entry(stage, cfg)? - .ok_or_else(|| E::from(InterpreterError::EmptyCfg))?; - Self::start( - interp, - stage, - index, - entry, - args, - true, - true, - BlockMode::CfgBlock, - ) - } - - /// Walk a linear (single-`Block`) function body: bind `args` to the - /// block's parameters. Owns the activation and is the return boundary; - /// `Jump`/`Branch`/`Yield` are errors — the exit convention is `Return`. - pub fn linear_function( - interp: &mut I, - stage: CompileStage, - index: EnvIndex, - block: Block, - args: Product, - ) -> Result - where - I: FrameDriver, - { - Self::start( - interp, - stage, - index, - block, - args, - true, - true, - BlockMode::StructuredBody, - ) - } - - /// A single body block (scf-style), to bind `args` to its parameters on the - /// first step. Borrows the caller's activation and is not a return boundary. - /// Pure construction — needs no engine access. - pub fn block(stage: CompileStage, index: EnvIndex, block: Block, args: Product) -> Self { - Self { - stage, - index, - owns_env: false, - function_boundary: false, - mode: BlockMode::StructuredBody, - block, - cursor: None, - pending: Some(args), - resume_slots: None, - _marker: std::marker::PhantomData, - } - } - - fn start( - interp: &mut I, - stage: CompileStage, - index: EnvIndex, - block: Block, - args: Product, - owns_env: bool, - function_boundary: bool, - mode: BlockMode, - ) -> Result - where - I: FrameDriver, - { - interp.bind_block_args(stage, index, block, &args)?; - let cursor = interp.first_statement(stage, block)?; - Ok(Self { - stage, - index, - owns_env, - function_boundary, - mode, - block, - cursor, - pending: None, - resume_slots: None, - _marker: std::marker::PhantomData, - }) - } - - /// Execute the next statement and translate its [`SparseForwardEffect`] into a - /// [`FrameEffect`] over the total frame type `F`. - pub fn step_into(mut self, interp: &mut I) -> Result>, E> - where - I: FrameDriver + SparseForwardInterp, - F: FrameBuild, - { - // Bind entry arguments lazily on the first step (a dialect-built body - // frame carries them unbound). - if let Some(args) = self.pending.take() { - interp.bind_block_args(self.stage, self.index, self.block, &args)?; - self.cursor = interp.first_statement(self.stage, self.block)?; - return Ok(FrameEffect::Continue(F::from_body(self))); - } - let Some(statement) = self.cursor else { - return Err(E::from(if self.function_boundary { - InterpreterError::FunctionBodyFellThrough - } else { - InterpreterError::BlockFellThrough(self.block) - })); - }; - self.cursor = interp.next_statement(self.stage, self.block, statement)?; - - match interp.run_statement(self.stage, statement, self.index)? { - SparseForwardEffect::Next => Ok(FrameEffect::Continue(F::from_body(self))), - SparseForwardEffect::Jump(edge) => { - if self.mode == BlockMode::StructuredBody { - return Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)); - } - interp.bind_block_args(self.stage, self.index, edge.target, &edge.args)?; - self.cursor = interp.first_statement(self.stage, edge.target)?; - self.block = edge.target; - Ok(FrameEffect::Continue(F::from_body(self))) - } - SparseForwardEffect::Branch(_) => { - if self.mode == BlockMode::StructuredBody { - return Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)); - } - Err(E::from(InterpreterError::IndeterminateBranch)) - } - SparseForwardEffect::Push { frame, results } => { - self.resume_slots = Some(results); - Ok(FrameEffect::Push { - parent: F::from_body(self), - child: frame, - }) - } - SparseForwardEffect::Call(call) => { - let pending = CallFrame::pending(self.stage, self.index, call); - Ok(FrameEffect::Push { - parent: F::from_body(self), - child: F::from_call(pending), - }) - } - SparseForwardEffect::Yield(values) => { - if self.function_boundary { - return Err(E::from(InterpreterError::Custom( - "yield reached a function boundary", - ))); - } - Ok(FrameEffect::Complete(Completion::Finished(values))) - } - SparseForwardEffect::Return(values) => self.finish_return::(interp, values), - } - } - - /// A child finished without a payload (its results are already in the - /// shared index, e.g. a returned call): resume at the advanced cursor. - pub fn resume_done_into(self) -> FrameEffect> - where - F: FrameBuild, - { - FrameEffect::Continue(F::from_body(self)) - } - - /// A child bubbled a completion: a pushed body frame `Finished` (write its - /// values into the pending slots and continue) or a `Returned` (a return - /// happened in the child — keep bubbling). - pub fn resume_into( - mut self, - completion: Completion, - interp: &mut I, - ) -> Result>, E> - where - I: FrameDriver, - F: FrameBuild, - { - match completion { - Completion::Finished(values) => { - let slots = self.resume_slots.take().ok_or_else(|| { - E::from(InterpreterError::Custom("body resume without result slots")) - })?; - interp.write_results(self.index, &slots, values)?; - Ok(FrameEffect::Continue(F::from_body(self))) - } - Completion::Returned(values) => self.finish_return::(interp, values), - } - } - - /// Produce a `Returned` completion, freeing the activation record when this - /// frame is the owning function boundary. - fn finish_return( - self, - interp: &mut I, - values: Product, - ) -> Result>, E> - where - I: FrameDriver, - F: FrameBuild, - { - if self.function_boundary && self.owns_env { - interp.free_env(self.index)?; - } - Ok(FrameEffect::Complete(Completion::Returned(values))) - } -} - -/// Call/return bookkeeping: dispatch a function invocation, then await its -/// return and land the results in the caller's activation. -pub enum CallFrame { - /// Not yet dispatched: resolve the callee, enter its body. - Pending { - resolve_stage: CompileStage, - callee: Callee, - args: Product, - caller_env: EnvIndex, - results: Product, - }, - /// Dispatched: awaiting the callee's `Returned` completion. - Awaiting { - caller_env: EnvIndex, - results: Product, - }, -} - -impl CallFrame -where - V: Clone, -{ - /// Build a pending call frame from a [`CallEffect`]. - pub fn pending(scope_stage: CompileStage, caller_env: EnvIndex, call: CallEffect) -> Self { - CallFrame::Pending { - resolve_stage: call.stage.unwrap_or(scope_stage), - callee: call.callee, - args: call.args, - caller_env, - results: call.results, - } - } - - pub fn step_into(self, interp: &mut I) -> Result>, I::Error> - where - I: FrameDriver, - I::Error: From, - F: FrameBuild, - { - match self { - CallFrame::Pending { - resolve_stage, - callee, - args, - caller_env, - results, - } => { - let target = interp.resolve_call(resolve_stage, &callee)?; - let index = interp.alloc_env(); - let body = interp.enter_function(target.stage, target.body, args, index)?; - let child = match body.body { - Body::Cfg(cfg) => F::from_body(BodyFrame::function( - interp, - target.stage, - index, - cfg, - body.args, - )?), - Body::Block(block) => F::from_body(BodyFrame::linear_function( - interp, - target.stage, - index, - block, - body.args, - )?), - Body::DiGraph(graph) => F::from_digraph(DiGraphFrame::function( - target.stage, - index, - graph, - body.args, - )), - other @ Body::UnGraph(_) => { - return Err(I::Error::from(InterpreterError::NoDefaultWalker(other))); - } - }; - Ok(FrameEffect::Push { - parent: F::from_call(CallFrame::Awaiting { - caller_env, - results, - }), - child, - }) - } - CallFrame::Awaiting { .. } => Err(I::Error::from(InterpreterError::Custom( - "call frame stepped while awaiting a return", - ))), - } - } - - pub fn resume_done_into(self) -> Result>, InterpreterError> { - Err(InterpreterError::Custom( - "call frame resumed without a return", - )) - } - - pub fn resume_into( - self, - completion: Completion, - interp: &mut I, - ) -> Result>, I::Error> - where - I: FrameDriver, - I::Error: From, - F: FrameBuild, - { - match (self, completion) { - ( - CallFrame::Awaiting { - caller_env, - results, - }, - Completion::Returned(values), - ) => { - interp.write_results(caller_env, &results, values)?; - Ok(FrameEffect::Done) - } - (CallFrame::Awaiting { .. }, Completion::Finished(_)) => Err(I::Error::from( - InterpreterError::Custom("call frame resumed with a body completion"), - )), - (CallFrame::Pending { .. }, _) => Err(I::Error::from(InterpreterError::Custom( - "call frame resumed before dispatch", - ))), - } - } -} - -/// The default total concrete frame enum: standard concrete traversal (no -/// structured-control dialect frames). -/// Traversal of one digraph body: bind entry arguments to the graph's -/// boundary ports, run the node statements in topological order, and -/// complete with the graph's yielded values. -/// -/// Pure construction — the walk plan is fetched and the ports are bound on -/// the first `step`, so a dialect frame can build one without engine access -/// (the same lazy pattern as [`BodyFrame::block`]). CFG control flow -/// (`Jump`/`Branch`) and `Yield`/`Return` are errors inside a graph: a -/// digraph's outputs are its declared yields, not a statement effect. -pub struct DiGraphFrame { - stage: CompileStage, - index: EnvIndex, - owns_env: bool, - function_boundary: bool, - graph: kirin_ir::DiGraph, - /// Entry arguments not yet bound (bound on the first `step`). - pending: Option>, - /// Remaining schedule, in topological order; `None` until the first step. - schedule: Option>, - yields: Vec, - /// Result slots awaiting a pushed child frame's `Finished` completion. - resume_slots: Option>, - _marker: std::marker::PhantomData (V, E)>, -} - -impl DiGraphFrame -where - V: Clone, - E: From, -{ - /// Walk a digraph as a function body: owns the activation and is the - /// call's return boundary (completes `Returned` with the yields). - pub fn function( - stage: CompileStage, - index: EnvIndex, - graph: kirin_ir::DiGraph, - args: Product, - ) -> Self { - Self::with_boundary(stage, index, graph, args, true, true) - } - - /// Walk a digraph owned by a statement inside another body (pushed by a - /// dialect frame): borrows the caller's activation and completes - /// `Finished` with the yields. - pub fn nested( - stage: CompileStage, - index: EnvIndex, - graph: kirin_ir::DiGraph, - args: Product, - ) -> Self { - Self::with_boundary(stage, index, graph, args, false, false) - } - - fn with_boundary( - stage: CompileStage, - index: EnvIndex, - graph: kirin_ir::DiGraph, - args: Product, - owns_env: bool, - function_boundary: bool, - ) -> Self { - Self { - stage, - index, - owns_env, - function_boundary, - graph, - pending: Some(args), - schedule: None, - yields: Vec::new(), - resume_slots: None, - _marker: std::marker::PhantomData, - } - } - - /// Execute the next scheduled node and translate its - /// [`SparseForwardEffect`] into a [`FrameEffect`] over the total frame - /// type `F`. - pub fn step_into(mut self, interp: &mut I) -> Result>, E> - where - I: FrameDriver + SparseForwardInterp, - F: FrameBuild, - { - // First step: fetch the walk plan and bind the boundary ports. - if let Some(args) = self.pending.take() { - let plan = interp.digraph_walk_plan(self.stage, self.graph)?; - if plan.ports.len() != args.len() { - return Err(E::from(InterpreterError::ProductArityMismatch { - expected: plan.ports.len(), - actual: args.len(), - })); - } - for (port, value) in plan.ports.iter().copied().zip(args) { - interp.env_write(self.index, SSAValue::from(port), value)?; - } - self.schedule = Some(plan.schedule.into()); - self.yields = plan.yields; - return Ok(FrameEffect::Continue(F::from_digraph(self))); - } - - let Some(statement) = self.schedule.as_mut().and_then(|s| s.pop_front()) else { - return self.finish::(interp); - }; - - match interp.run_statement(self.stage, statement, self.index)? { - SparseForwardEffect::Next => Ok(FrameEffect::Continue(F::from_digraph(self))), - SparseForwardEffect::Push { frame, results } => { - self.resume_slots = Some(results); - Ok(FrameEffect::Push { - parent: F::from_digraph(self), - child: frame, - }) - } - SparseForwardEffect::Call(call) => { - let pending = CallFrame::pending(self.stage, self.index, call); - Ok(FrameEffect::Push { - parent: F::from_digraph(self), - child: F::from_call(pending), - }) - } - SparseForwardEffect::Jump(_) | SparseForwardEffect::Branch(_) => { - Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)) - } - SparseForwardEffect::Yield(_) => Err(E::from(InterpreterError::Custom( - "yield inside a digraph body (a digraph's outputs are its declared yields)", - ))), - SparseForwardEffect::Return(_) => Err(E::from(InterpreterError::Custom( - "return inside a digraph body", - ))), - } - } - - /// Schedule exhausted: read the yields from the environment and complete. - fn finish(self, interp: &mut I) -> Result>, E> - where - I: FrameDriver, - F: FrameBuild, - { - let values: Product = self - .yields - .iter() - .map(|&value| interp.env_read(self.index, value)) - .collect::>()?; - if self.function_boundary { - if self.owns_env { - interp.free_env(self.index)?; - } - Ok(FrameEffect::Complete(Completion::Returned(values))) - } else { - Ok(FrameEffect::Complete(Completion::Finished(values))) - } - } - - /// A child finished without a payload (e.g. a returned call whose results - /// are already written): resume the schedule. - pub fn resume_done_into(self) -> FrameEffect> - where - F: FrameBuild, - { - FrameEffect::Continue(F::from_digraph(self)) - } - - /// A child bubbled a completion: a pushed frame `Finished` (bind its - /// values into the awaiting result slots) or a callee `Returned`. - pub fn resume_into( - mut self, - completion: Completion, - interp: &mut I, - ) -> Result>, E> - where - I: FrameDriver, - F: FrameBuild, - { - match completion { - Completion::Finished(values) => { - let slots = self.resume_slots.take().ok_or_else(|| { - E::from(InterpreterError::Custom( - "digraph resume without result slots", - )) - })?; - interp.write_results(self.index, &slots, values)?; - Ok(FrameEffect::Continue(F::from_digraph(self))) - } - Completion::Returned(_) => Err(E::from(InterpreterError::Custom( - "return bubbled into a digraph body", - ))), - } - } -} - -pub enum StandardFrame { - Body(BodyFrame), - Call(CallFrame), - DiGraph(DiGraphFrame), -} - -impl FrameBuild for StandardFrame { - fn from_body(frame: BodyFrame) -> Self { - StandardFrame::Body(frame) - } - fn from_call(frame: CallFrame) -> Self { - StandardFrame::Call(frame) - } - fn from_digraph(frame: DiGraphFrame) -> Self { - StandardFrame::DiGraph(frame) - } -} - -impl Frame for StandardFrame -where - I: FrameDriver + SparseForwardInterp>, - V: Clone, - E: From, -{ - type Completion = Completion; - - fn step(self, interp: &mut I) -> Result, I::Error> { - match self { - StandardFrame::Body(frame) => frame.step_into::(interp), - StandardFrame::Call(frame) => frame.step_into::(interp), - StandardFrame::DiGraph(frame) => frame.step_into::(interp), - } - } - - fn resume_done(self, _interp: &mut I) -> Result, I::Error> { - match self { - StandardFrame::Body(frame) => Ok(frame.resume_done_into::()), - StandardFrame::Call(frame) => frame.resume_done_into::().map_err(I::Error::from), - StandardFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), - } - } - - fn resume( - self, - completion: Self::Completion, - interp: &mut I, - ) -> Result, I::Error> { - match self { - StandardFrame::Body(frame) => frame.resume_into::(completion, interp), - StandardFrame::Call(frame) => frame.resume_into::(completion, interp), - StandardFrame::DiGraph(frame) => frame.resume_into::(completion, interp), - } - } -} diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/block_cursor.rs b/crates/kirin-interpreter/src/engines/concrete/frames/block_cursor.rs new file mode 100644 index 0000000000..b1d96c386a --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/block_cursor.rs @@ -0,0 +1,107 @@ +use kirin_ir::{Block, CompileStage, Product, SSAValue, Statement}; + +use crate::{EnvIndex, FrameDriver, InterpreterError}; + +/// Block-cursor mechanics shared by the block-shaped walkers +/// ([`BlockFrame`](super::BlockFrame) and [`CfgFrame`](super::CfgFrame)): +/// the current block, the statement cursor, lazily bound entry arguments, +/// and the result slots awaiting a pushed child's completion values. +/// +/// Traversal state only — no environment ownership, no invocation role. +pub(super) struct BlockCursor { + pub(super) stage: CompileStage, + pub(super) index: EnvIndex, + pub(super) block: Block, + cursor: Option, + /// Entry arguments not yet bound. A frame built by a dialect frame is + /// constructed without engine access — it binds on its first `step`, so + /// construction needs no [`FrameDriver`]. + pending: Option>, + /// Result slots awaiting a pushed child frame's completion values. + resume_slots: Option>, +} + +impl BlockCursor { + pub(super) fn new( + stage: CompileStage, + index: EnvIndex, + block: Block, + args: Product, + ) -> Self { + Self { + stage, + index, + block, + cursor: None, + pending: Some(args), + resume_slots: None, + } + } + + /// Bind pending entry arguments to the block's parameters and position + /// the cursor at its first statement. Returns `true` if binding happened + /// on this call (the frame should `Continue` and step again). + pub(super) fn bind_entry(&mut self, interp: &mut I) -> Result + where + I: FrameDriver, + { + match self.pending.take() { + Some(args) => { + interp.bind_block_args(self.stage, self.index, self.block, &args)?; + self.cursor = interp.first_statement(self.stage, self.block)?; + Ok(true) + } + None => Ok(false), + } + } + + /// Take the current statement, advancing the cursor past it. + pub(super) fn advance(&mut self, interp: &I) -> Result, I::Error> + where + I: FrameDriver, + { + let Some(statement) = self.cursor else { + return Ok(None); + }; + self.cursor = interp.next_statement(self.stage, self.block, statement)?; + Ok(Some(statement)) + } + + /// Move to `target` (a CFG jump): bind its parameters and reset the + /// cursor to its first statement. + pub(super) fn enter_block( + &mut self, + interp: &mut I, + target: Block, + args: &Product, + ) -> Result<(), I::Error> + where + I: FrameDriver, + { + interp.bind_block_args(self.stage, self.index, target, args)?; + self.cursor = interp.first_statement(self.stage, target)?; + self.block = target; + Ok(()) + } + + /// Stash the result slots of a `Push` until the child completes. + pub(super) fn expect_results(&mut self, results: Product) { + self.resume_slots = Some(results); + } + + /// Write a completed child's values into the stashed result slots. + pub(super) fn write_child_results( + &mut self, + interp: &mut I, + values: Product, + ) -> Result<(), I::Error> + where + I: FrameDriver, + I::Error: From, + { + let slots = self.resume_slots.take().ok_or_else(|| { + I::Error::from(InterpreterError::Custom("body resume without result slots")) + })?; + interp.write_results(self.index, &slots, values) + } +} diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/block_frame.rs b/crates/kirin-interpreter/src/engines/concrete/frames/block_frame.rs new file mode 100644 index 0000000000..9d1e815224 --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/block_frame.rs @@ -0,0 +1,117 @@ +use kirin_ir::{Block, CompileStage, Product}; + +use crate::{ + EnvIndex, FrameDriver, FrameEffect, InterpreterError, SparseForwardEffect, SparseForwardInterp, +}; + +use super::block_cursor::BlockCursor; +use super::{CallFrame, Completion, FrameBuild}; + +/// Representation walker for exactly one [`Block`]: bind its parameters, +/// run its statements in order, and surface the exit through the +/// [`Completion`] protocol — `Return` as +/// [`Returned`](Completion::Returned), `Yield` as +/// [`Yielded`](Completion::Yielded). +/// +/// Traversal mechanics only. A `BlockFrame` does not own an activation, is +/// not a call boundary, and does not know whether it is a callable function +/// body ([`CallFrame`] → `BlockFrame`) or a nested structured-operation body +/// (dialect frame → `BlockFrame`): the parent frame defines the role and +/// interprets the completion. CFG transitions (`Jump`/`Branch`) are rejected +/// — a single block owns no CFG edges; multi-block traversal is +/// [`CfgFrame`](super::CfgFrame)'s job. +pub struct BlockFrame { + cursor: BlockCursor, + _marker: std::marker::PhantomData E>, +} + +impl BlockFrame +where + V: Clone, + E: From, +{ + /// Walk `block`, binding `args` to its parameters on the first step. + /// Pure construction — needs no engine access, so a dialect frame can + /// build one as plain values. + pub fn new(stage: CompileStage, index: EnvIndex, block: Block, args: Product) -> Self { + Self { + cursor: BlockCursor::new(stage, index, block, args), + _marker: std::marker::PhantomData, + } + } + + /// Execute the next statement and translate its [`SparseForwardEffect`] + /// into a [`FrameEffect`] over the total frame type `F`. + pub fn step_into(mut self, interp: &mut I) -> Result>, E> + where + I: FrameDriver + SparseForwardInterp, + F: FrameBuild, + { + if self.cursor.bind_entry(interp)? { + return Ok(FrameEffect::Continue(F::from_block(self))); + } + let Some(statement) = self.cursor.advance(interp)? else { + return Err(E::from(InterpreterError::BlockFellThrough( + self.cursor.block, + ))); + }; + + match interp.run_statement(self.cursor.stage, statement, self.cursor.index)? { + SparseForwardEffect::Next => Ok(FrameEffect::Continue(F::from_block(self))), + SparseForwardEffect::Jump(_) | SparseForwardEffect::Branch(_) => { + Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)) + } + SparseForwardEffect::Push { frame, results } => { + self.cursor.expect_results(results); + Ok(FrameEffect::Push { + parent: F::from_block(self), + child: frame, + }) + } + SparseForwardEffect::Call(call) => { + let pending = CallFrame::pending(self.cursor.stage, self.cursor.index, call); + Ok(FrameEffect::Push { + parent: F::from_block(self), + child: F::from_call(pending), + }) + } + SparseForwardEffect::Yield(values) => { + Ok(FrameEffect::Complete(Completion::Yielded(values))) + } + SparseForwardEffect::Return(values) => { + Ok(FrameEffect::Complete(Completion::Returned(values))) + } + } + } + + /// A child finished without a payload (its results are already in the + /// shared activation, e.g. a returned call): resume at the advanced + /// cursor. + pub fn resume_done_into(self) -> FrameEffect> + where + F: FrameBuild, + { + FrameEffect::Continue(F::from_block(self)) + } + + /// A child bubbled a completion: a pushed frame's values land in the + /// push's result slots; a `Returned` keeps bubbling toward the nearest + /// [`CallFrame`] (this frame owns no activation to free). + pub fn resume_into( + mut self, + completion: Completion, + interp: &mut I, + ) -> Result>, E> + where + I: FrameDriver, + F: FrameBuild, + { + match completion { + Completion::Finished(values) | Completion::Yielded(values) => { + self.cursor.write_child_results(interp, values)?; + Ok(FrameEffect::Continue(F::from_block(self))) + } + Completion::Returned(values) => Ok(FrameEffect::Complete(Completion::Returned(values))), + } + } +} diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs b/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs new file mode 100644 index 0000000000..9ffea78240 --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs @@ -0,0 +1,190 @@ +use kirin_ir::{CompileStage, Product, SSAValue}; + +use crate::{Body, CallEffect, Callee, EnvIndex, FrameDriver, FrameEffect, InterpreterError}; + +use super::{BlockFrame, CfgFrame, Completion, DiGraphFrame, FrameBuild, UnGraphEntry}; + +/// The function-call boundary frame: interpreter runtime bookkeeping, not a +/// function dialect operation and not the callable itself. +/// +/// A `CallFrame` owns the whole activation lifecycle that representation +/// walkers deliberately don't: +/// +/// 1. resolve the callee through the engine's [`Linker`](crate::Linker); +/// 2. allocate the callee activation; +/// 3. ask [`FunctionEntry`](crate::FunctionEntry) for the callable body +/// descriptor ([`CallableBody`](crate::CallableBody)); +/// 4. select the entry frame for the closed [`Body`] variant — +/// `Cfg` → [`CfgFrame`], `Block` → [`BlockFrame`], +/// `DiGraph` → [`DiGraphFrame`], `UnGraph` → the dialect/compiler policy +/// ([`FrameBuild::from_ungraph_entry`]); +/// 5. suspend while the callee frame runs; +/// 6. validate the callee's completion kind ([`Returned`](Completion::Returned) +/// or a graph's natural [`Finished`](Completion::Finished) are returns; a +/// structured [`Yielded`](Completion::Yielded) is an error); +/// 7. free the callee activation exactly once; +/// 8. deliver the returned values — into the caller's result slots for a +/// nested call, or as the run's completion for a root call +/// ([`ConcreteInterpreter::call`](crate::ConcreteInterpreter::call) pushes +/// a [`CallFrame::root`], so root and nested calls share this one +/// boundary implementation). +pub struct CallFrame { + state: CallState, +} + +enum CallState { + /// Not yet dispatched: resolve the callee and enter its body. + Pending { + resolve_stage: CompileStage, + callee: Callee, + args: Product, + dest: CallDest, + }, + /// Dispatched: the callee frame is running. Holds the callee activation + /// so the boundary frees it exactly once on completion. + Awaiting { + callee_env: EnvIndex, + dest: CallDest, + }, +} + +/// Where a finished call delivers its returned values. +enum CallDest { + /// Write into result slots of the calling activation and resume the + /// caller. + Caller { + env: EnvIndex, + results: Product, + }, + /// A root call: complete the frame stack with the values. + Root, +} + +impl CallFrame +where + V: Clone, +{ + /// A call issued by a statement ([`SparseForwardEffect::Call`](crate::SparseForwardEffect::Call)): + /// returned values land in `call.results` of the caller's activation. + pub fn pending(scope_stage: CompileStage, caller_env: EnvIndex, call: CallEffect) -> Self { + CallFrame { + state: CallState::Pending { + resolve_stage: call.stage.unwrap_or(scope_stage), + callee: call.callee, + args: call.args, + dest: CallDest::Caller { + env: caller_env, + results: call.results, + }, + }, + } + } + + /// A root call (no calling activation): the returned values complete the + /// frame stack. + pub fn root(stage: CompileStage, callee: Callee, args: Product) -> Self { + CallFrame { + state: CallState::Pending { + resolve_stage: stage, + callee, + args, + dest: CallDest::Root, + }, + } + } + + pub fn step_into(self, interp: &mut I) -> Result>, I::Error> + where + I: FrameDriver, + I::Error: From, + F: FrameBuild, + { + match self.state { + CallState::Pending { + resolve_stage, + callee, + args, + dest, + } => { + let target = interp.resolve_call(resolve_stage, &callee)?; + let index = interp.alloc_env(); + let entry = interp.enter_function(target.stage, target.body, args, index)?; + // The closed `Body` enum is the framework's supported body + // vocabulary, so this match is intentionally exhaustive; + // only the `UnGraph` arm delegates to a language policy. + let child = match entry.body { + Body::Cfg(cfg) => { + F::from_cfg(CfgFrame::new(target.stage, index, cfg, entry.args)) + } + Body::Block(block) => { + F::from_block(BlockFrame::new(target.stage, index, block, entry.args)) + } + Body::DiGraph(graph) => { + F::from_digraph(DiGraphFrame::new(target.stage, index, graph, entry.args)) + } + Body::UnGraph(graph) => F::from_ungraph_entry(UnGraphEntry { + stage: target.stage, + index, + graph, + args: entry.args, + })?, + }; + Ok(FrameEffect::Push { + parent: F::from_call(CallFrame { + state: CallState::Awaiting { + callee_env: index, + dest, + }, + }), + child, + }) + } + CallState::Awaiting { .. } => Err(I::Error::from(InterpreterError::Custom( + "call frame stepped while awaiting a return", + ))), + } + } + + pub fn resume_done_into(self) -> Result>, InterpreterError> { + Err(InterpreterError::Custom( + "call frame resumed without a return", + )) + } + + /// The callee completed: validate the completion kind, free the callee + /// activation exactly once, and deliver the returned values. + pub fn resume_into( + self, + completion: Completion, + interp: &mut I, + ) -> Result>, I::Error> + where + I: FrameDriver, + I::Error: From, + F: FrameBuild, + { + let CallState::Awaiting { callee_env, dest } = self.state else { + return Err(I::Error::from(InterpreterError::Custom( + "call frame resumed before dispatch", + ))); + }; + let values = match completion { + // An explicit `Return`, or a graph body's natural completion + // (a callable DiGraph's outputs are the call's returned values). + Completion::Returned(values) | Completion::Finished(values) => values, + Completion::Yielded(_) => { + return Err(I::Error::from(InterpreterError::Custom( + "structured yield reached a function-call boundary (a callable body must exit with return)", + ))); + } + }; + interp.free_env(callee_env)?; + match dest { + CallDest::Caller { env, results } => { + interp.write_results(env, &results, values)?; + Ok(FrameEffect::Done) + } + CallDest::Root => Ok(FrameEffect::Complete(Completion::Returned(values))), + } + } +} diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/cfg_frame.rs b/crates/kirin-interpreter/src/engines/concrete/frames/cfg_frame.rs new file mode 100644 index 0000000000..8dc3ca9eba --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/cfg_frame.rs @@ -0,0 +1,140 @@ +use kirin_ir::{Cfg, CompileStage, Product}; + +use crate::{ + EnvIndex, FrameDriver, FrameEffect, InterpreterError, SparseForwardEffect, SparseForwardInterp, +}; + +use super::block_cursor::BlockCursor; +use super::{CallFrame, Completion, FrameBuild}; + +/// Representation walker for a [`Cfg`]: enter the entry block, run +/// statements, follow `Jump` edges between blocks (binding successor +/// arguments and resetting the cursor), and surface the exit through the +/// [`Completion`] protocol. +/// +/// Traversal mechanics only. A `CfgFrame` does not own an activation, is not +/// a call boundary, and does not decide whether a `Return` belongs to a +/// function call — it completes [`Returned`](Completion::Returned) and lets +/// the completion bubble to the nearest [`CallFrame`]. An undecided concrete +/// `Branch` is an error (single-path execution); exploring branch +/// alternatives is the abstract engine's business. +pub struct CfgFrame { + stage: CompileStage, + index: EnvIndex, + cfg: Cfg, + /// Entry arguments awaiting the first step (the entry block is resolved + /// lazily, so construction needs no engine access). + pending: Option>, + /// The active block cursor; `None` until the entry block is resolved. + cursor: Option>, + _marker: std::marker::PhantomData E>, +} + +impl CfgFrame +where + V: Clone, + E: From, +{ + /// Walk `cfg` from its entry block, binding `args` to the entry block's + /// parameters on the first step. Pure construction — needs no engine + /// access. + pub fn new(stage: CompileStage, index: EnvIndex, cfg: Cfg, args: Product) -> Self { + Self { + stage, + index, + cfg, + pending: Some(args), + cursor: None, + _marker: std::marker::PhantomData, + } + } + + /// Execute the next statement and translate its [`SparseForwardEffect`] + /// into a [`FrameEffect`] over the total frame type `F`. + pub fn step_into(mut self, interp: &mut I) -> Result>, E> + where + I: FrameDriver + SparseForwardInterp, + F: FrameBuild, + { + // First step: find the entry block and bind the entry arguments. + if let Some(args) = self.pending.take() { + let entry = interp + .cfg_entry(self.stage, self.cfg)? + .ok_or_else(|| E::from(InterpreterError::EmptyCfg))?; + let mut cursor = BlockCursor::new(self.stage, self.index, entry, args); + cursor.bind_entry(interp)?; + self.cursor = Some(cursor); + return Ok(FrameEffect::Continue(F::from_cfg(self))); + } + let cursor = self + .cursor + .as_mut() + .ok_or_else(|| E::from(InterpreterError::Custom("cfg frame stepped before entry")))?; + let Some(statement) = cursor.advance(interp)? else { + return Err(E::from(InterpreterError::BlockFellThrough(cursor.block))); + }; + + match interp.run_statement(self.stage, statement, self.index)? { + SparseForwardEffect::Next => Ok(FrameEffect::Continue(F::from_cfg(self))), + SparseForwardEffect::Jump(edge) => { + cursor.enter_block(interp, edge.target, &edge.args)?; + Ok(FrameEffect::Continue(F::from_cfg(self))) + } + SparseForwardEffect::Branch(_) => Err(E::from(InterpreterError::IndeterminateBranch)), + SparseForwardEffect::Push { frame, results } => { + cursor.expect_results(results); + Ok(FrameEffect::Push { + parent: F::from_cfg(self), + child: frame, + }) + } + SparseForwardEffect::Call(call) => { + let pending = CallFrame::pending(self.stage, self.index, call); + Ok(FrameEffect::Push { + parent: F::from_cfg(self), + child: F::from_call(pending), + }) + } + SparseForwardEffect::Yield(values) => { + Ok(FrameEffect::Complete(Completion::Yielded(values))) + } + SparseForwardEffect::Return(values) => { + Ok(FrameEffect::Complete(Completion::Returned(values))) + } + } + } + + /// A child finished without a payload (its results are already in the + /// shared activation, e.g. a returned call): resume at the advanced + /// cursor. + pub fn resume_done_into(self) -> FrameEffect> + where + F: FrameBuild, + { + FrameEffect::Continue(F::from_cfg(self)) + } + + /// A child bubbled a completion: a pushed frame's values land in the + /// push's result slots; a `Returned` keeps bubbling toward the nearest + /// [`CallFrame`] (this frame owns no activation to free). + pub fn resume_into( + mut self, + completion: Completion, + interp: &mut I, + ) -> Result>, E> + where + I: FrameDriver, + F: FrameBuild, + { + match completion { + Completion::Finished(values) | Completion::Yielded(values) => { + let cursor = self.cursor.as_mut().ok_or_else(|| { + E::from(InterpreterError::Custom("cfg frame resumed before entry")) + })?; + cursor.write_child_results(interp, values)?; + Ok(FrameEffect::Continue(F::from_cfg(self))) + } + Completion::Returned(values) => Ok(FrameEffect::Complete(Completion::Returned(values))), + } + } +} diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/digraph_frame.rs b/crates/kirin-interpreter/src/engines/concrete/frames/digraph_frame.rs new file mode 100644 index 0000000000..19f4335346 --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/digraph_frame.rs @@ -0,0 +1,178 @@ +use kirin_ir::{CompileStage, Product, SSAValue, Statement}; + +use crate::{ + EnvIndex, FrameDriver, FrameEffect, InterpreterError, SparseForwardEffect, SparseForwardInterp, +}; + +use super::{CallFrame, Completion, FrameBuild}; + +/// Representation-specific walker for a [`DiGraph`](kirin_ir::DiGraph) body: bind +/// entry arguments to the graph's boundary ports, run the node statements in +/// topological (dependency) order, and complete +/// [`Finished`](Completion::Finished) with the graph's declared yields. +/// +/// Traversal mechanics only. A `DiGraphFrame` does not own an activation and +/// does not know whether it is a callable graph-function body +/// ([`CallFrame`] → `DiGraphFrame`, where the yields become the call's +/// returned values) or a graph nested inside another body (dialect frame → +/// `DiGraphFrame`, where the yields return to the pushing operation): the +/// parent interprets the completion. +/// +/// The concrete execution policy requires a DAG: directed cycles are +/// rejected when the walk plan is built +/// ([`GraphHasCycle`](InterpreterError::GraphHasCycle)). This is a property +/// of this walker, not of the IR — a `DiGraph` may represent cycles. +/// +/// Pure construction — the walk plan is fetched and the ports are bound on +/// the first `step`, so a dialect frame can build one without engine access +/// (the same lazy pattern as [`BlockFrame`](super::BlockFrame)). CFG control +/// flow (`Jump`/`Branch`) and `Yield`/`Return` are errors inside a graph: a +/// digraph's outputs are its declared yields, not a statement effect. +pub struct DiGraphFrame { + stage: CompileStage, + index: EnvIndex, + graph: kirin_ir::DiGraph, + /// Entry arguments not yet bound (bound on the first `step`). + pending: Option>, + /// Remaining schedule, in topological order; `None` until the first step. + schedule: Option>, + yields: Vec, + /// Result slots awaiting a pushed child frame's completion values. + resume_slots: Option>, + _marker: std::marker::PhantomData (V, E)>, +} + +impl DiGraphFrame +where + V: Clone, + E: From, +{ + /// Walk `graph`, binding `args` to its boundary ports on the first step. + pub fn new( + stage: CompileStage, + index: EnvIndex, + graph: kirin_ir::DiGraph, + args: Product, + ) -> Self { + Self { + stage, + index, + graph, + pending: Some(args), + schedule: None, + yields: Vec::new(), + resume_slots: None, + _marker: std::marker::PhantomData, + } + } + + /// Execute the next scheduled node and translate its + /// [`SparseForwardEffect`] into a [`FrameEffect`] over the total frame + /// type `F`. + pub fn step_into(mut self, interp: &mut I) -> Result>, E> + where + I: FrameDriver + SparseForwardInterp, + F: FrameBuild, + { + // First step: fetch the walk plan and bind the boundary ports. + if let Some(args) = self.pending.take() { + let plan = interp.digraph_walk_plan(self.stage, self.graph)?; + if plan.ports.len() != args.len() { + return Err(E::from(InterpreterError::ProductArityMismatch { + expected: plan.ports.len(), + actual: args.len(), + })); + } + for (port, value) in plan.ports.iter().copied().zip(args) { + interp.env_write(self.index, SSAValue::from(port), value)?; + } + self.schedule = Some(plan.schedule.into()); + self.yields = plan.yields; + return Ok(FrameEffect::Continue(F::from_digraph(self))); + } + + let Some(statement) = self.schedule.as_mut().and_then(|s| s.pop_front()) else { + return self.finish::(interp); + }; + + match interp.run_statement(self.stage, statement, self.index)? { + SparseForwardEffect::Next => Ok(FrameEffect::Continue(F::from_digraph(self))), + SparseForwardEffect::Push { frame, results } => { + self.resume_slots = Some(results); + Ok(FrameEffect::Push { + parent: F::from_digraph(self), + child: frame, + }) + } + SparseForwardEffect::Call(call) => { + let pending = CallFrame::pending(self.stage, self.index, call); + Ok(FrameEffect::Push { + parent: F::from_digraph(self), + child: F::from_call(pending), + }) + } + SparseForwardEffect::Jump(_) | SparseForwardEffect::Branch(_) => { + Err(E::from(InterpreterError::CfgControlFlowInStructuredBody)) + } + SparseForwardEffect::Yield(_) => Err(E::from(InterpreterError::Custom( + "yield inside a digraph body (a digraph's outputs are its declared yields)", + ))), + SparseForwardEffect::Return(_) => Err(E::from(InterpreterError::Custom( + "return inside a digraph body", + ))), + } + } + + /// Schedule exhausted: read the declared yields from the activation and + /// complete `Finished` — the graph's natural completion. The parent + /// decides what the values mean (call returns or push results). + fn finish(self, interp: &mut I) -> Result>, E> + where + I: FrameDriver, + F: FrameBuild, + { + let values: Product = self + .yields + .iter() + .map(|&value| interp.env_read(self.index, value)) + .collect::>()?; + Ok(FrameEffect::Complete(Completion::Finished(values))) + } + + /// A child finished without a payload (e.g. a returned call whose results + /// are already written): resume the schedule. + pub fn resume_done_into(self) -> FrameEffect> + where + F: FrameBuild, + { + FrameEffect::Continue(F::from_digraph(self)) + } + + /// A child bubbled a completion: a pushed frame's values land in the + /// push's result slots. A `Returned` cannot bubble out of a graph node — + /// a digraph has no function-return convention. + pub fn resume_into( + mut self, + completion: Completion, + interp: &mut I, + ) -> Result>, E> + where + I: FrameDriver, + F: FrameBuild, + { + match completion { + Completion::Finished(values) | Completion::Yielded(values) => { + let slots = self.resume_slots.take().ok_or_else(|| { + E::from(InterpreterError::Custom( + "digraph resume without result slots", + )) + })?; + interp.write_results(self.index, &slots, values)?; + Ok(FrameEffect::Continue(F::from_digraph(self))) + } + Completion::Returned(_) => Err(E::from(InterpreterError::Custom( + "return bubbled into a digraph body", + ))), + } + } +} diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/mod.rs b/crates/kirin-interpreter/src/engines/concrete/frames/mod.rs new file mode 100644 index 0000000000..68c2b20e90 --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/mod.rs @@ -0,0 +1,63 @@ +//! The **concrete** implementation of the shared [`frame`](crate::core::frame) +//! protocol. +//! +//! Two independent axes organize these frames: +//! +//! - **Body representation** — the closed [`Body`](crate::Body) vocabulary +//! (`Cfg` / `Block` / `DiGraph` / `UnGraph`), an intentional IR design +//! decision. Each representation the framework can walk has one +//! representation frame implementing *traversal mechanics only*: +//! [`CfgFrame`] (multi-block, follows jumps), [`BlockFrame`] (one linear +//! block), and [`DiGraphFrame`] (dependency-ordered DAG walk). `UnGraph` +//! has **no** default walker — an undirected graph has no inherent +//! execution order, so traversal is a dialect/compiler-supplied policy +//! ([`FrameBuild::from_ungraph_entry`]). +//! +//! - **Entry context** — *why* a body is being walked: as a callable function +//! body (entered through [`CallFrame`], the call boundary that owns the +//! callee activation and return bookkeeping), as a nested structured +//! operation body (entered through a dialect frame such as `kirin-scf`'s +//! `ScfIfFrame`/`ScfForFrame`, pushed with +//! [`SparseForwardEffect::Push`]), or as an analysis owner (the abstract +//! engines' concern, not this module's). +//! +//! Representation frames never know their entry context: the same +//! [`BlockFrame`] walks a callable Block body and an `scf.if` arm; the parent +//! frame ([`CallFrame`] or the dialect frame) interprets the walker's +//! [`Completion`] and owns activation lifetime. Roles compose instead of +//! multiplying frame types: +//! +//! ```text +//! linear function = CallFrame → BlockFrame +//! CFG function = CallFrame → CfgFrame +//! graph function = CallFrame → DiGraphFrame +//! nested scf block = ScfIfFrame → BlockFrame +//! ``` +//! +//! These are the default total frames for +//! [`ConcreteInterpreter`](crate::ConcreteInterpreter) (bundled as +//! [`StandardFrame`]). Structured-control dialects do not get a framework +//! "scope": they push a frame **they own** through +//! [`SparseForwardEffect::Push`] (that frame may build a [`BlockFrame`] to +//! walk a chosen body — a reusable building block, not framework-owned +//! structured semantics). A language that combines such a dialect defines its +//! own total frame enum embedding these frames via [`FrameBuild`] plus its +//! dialect frames. The forward abstract analogue lives in +//! [`sparse_forward::frames`](crate::engines::sparse_forward::frames). +//! +//! [`SparseForwardEffect::Push`]: crate::SparseForwardEffect::Push + +mod block_cursor; +mod block_frame; +mod call_frame; +mod cfg_frame; +mod digraph_frame; +mod protocol; +mod standard_frame; + +pub use block_frame::BlockFrame; +pub use call_frame::CallFrame; +pub use cfg_frame::CfgFrame; +pub use digraph_frame::DiGraphFrame; +pub use protocol::{Completion, FrameBuild, UnGraphEntry}; +pub use standard_frame::StandardFrame; diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs b/crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs new file mode 100644 index 0000000000..cec4f7ead6 --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs @@ -0,0 +1,87 @@ +use kirin_ir::{CompileStage, Product, UnGraph}; + +use crate::{Body, EnvIndex, InterpreterError}; + +use super::{BlockFrame, CallFrame, CfgFrame, DiGraphFrame}; + +/// Completion payloads produced by the standard concrete frames. +/// +/// Representation walkers report *what happened*; the parent frame decides +/// what it means. The protocol distinguishes the three relevant exits: +/// +/// - [`Returned`](Completion::Returned): an explicit function `Return` was +/// executed. Every frame between the walker and the call boundary relays it +/// unchanged (dialect frames included), so it bubbles to the nearest +/// [`CallFrame`] — the only frame that frees the callee activation and +/// writes the caller's result slots. +/// - [`Yielded`](Completion::Yielded): a structured `Yield` terminated a +/// block body. The structured-operation frame that pushed the block (e.g. +/// `scf.if`/`scf.for`) consumes the carried values. A [`CallFrame`] +/// rejects it: a callable Block or Cfg must exit with `Return`, never a +/// structured yield. +/// - [`Finished`](Completion::Finished): the body ran to its natural end +/// with these values — a digraph's declared output yields, or a dialect +/// frame's finished sub-computation. A frame that pushed the child writes +/// them into the push's result slots; a [`CallFrame`] accepts them as the +/// call's returned values (a callable DiGraph's outputs are its returns). +pub enum Completion { + /// An explicit function `Return` with these values; bubbles to the + /// enclosing [`CallFrame`]. + Returned(Product), + /// A structured `Yield` with these carried values; consumed by the + /// dialect frame that pushed the block body. + Yielded(Product), + /// Natural completion of a body/sub-computation with these values; + /// delivered to whoever entered it (pusher or [`CallFrame`]). + Finished(Product), +} + +/// The entry context handed to a dialect/compiler-supplied callable-UnGraph +/// policy: the callee stage, the callee activation (owned by the awaiting +/// [`CallFrame`], never by the policy frame), the graph, and the entry +/// arguments for its boundary ports. +pub struct UnGraphEntry { + pub stage: CompileStage, + pub index: EnvIndex, + pub graph: UnGraph, + pub args: Product, +} + +/// Construction trait letting any total frame enum embed the standard +/// concrete frames. +/// +/// The default [`StandardFrame`](super::StandardFrame) implements it +/// trivially; a language that adds structured-control dialects implements it +/// on its own enum to reuse the representation walkers and [`CallFrame`] +/// while adding its own dialect frames. +/// +/// [`Body`](crate::Body) is a deliberately closed enum, so [`CallFrame`] +/// matches it exhaustively and maps each representation to its default +/// walker — except `UnGraph`, whose traversal is a policy this trait's +/// [`from_ungraph_entry`](Self::from_ungraph_entry) hook supplies. +pub trait FrameBuild: Sized { + fn from_block(frame: BlockFrame) -> Self; + fn from_cfg(frame: CfgFrame) -> Self; + fn from_call(frame: CallFrame) -> Self; + fn from_digraph(frame: DiGraphFrame) -> Self; + + /// Build the entry frame for a **callable** `UnGraph` body. + /// + /// There is no framework default: an undirected graph has no inherent + /// producer/consumer direction, control-flow successor, or topological + /// execution order — its semantics (graph rewriting, circuits, constraint + /// propagation, …) belong to the dialect/compiler. A language with such + /// semantics overrides this to construct its own policy frame; everyone + /// else inherits this rejection, so total frame enums carry no meaningless + /// UnGraph boilerplate. (Nested, uncallable UnGraph operations don't come + /// through here — a dialect frame enters them via + /// [`SparseForwardEffect::Push`](crate::SparseForwardEffect::Push).) + fn from_ungraph_entry(entry: UnGraphEntry) -> Result + where + E: From, + { + Err(E::from(InterpreterError::NoDefaultWalker(Body::UnGraph( + entry.graph, + )))) + } +} diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/standard_frame.rs b/crates/kirin-interpreter/src/engines/concrete/frames/standard_frame.rs new file mode 100644 index 0000000000..11931376a4 --- /dev/null +++ b/crates/kirin-interpreter/src/engines/concrete/frames/standard_frame.rs @@ -0,0 +1,69 @@ +use crate::{Frame, FrameDriver, FrameEffect, InterpreterError, SparseForwardInterp}; + +use super::{BlockFrame, CallFrame, CfgFrame, Completion, DiGraphFrame, FrameBuild}; + +/// The standard total concrete frame enum: the representation walkers plus +/// the call boundary, no structured-control dialect frames and no +/// callable-UnGraph policy (so a call into an `UnGraph` body reports +/// [`NoDefaultWalker`](InterpreterError::NoDefaultWalker)). +pub enum StandardFrame { + Block(BlockFrame), + Cfg(CfgFrame), + Call(CallFrame), + DiGraph(DiGraphFrame), +} + +impl FrameBuild for StandardFrame { + fn from_block(frame: BlockFrame) -> Self { + StandardFrame::Block(frame) + } + fn from_cfg(frame: CfgFrame) -> Self { + StandardFrame::Cfg(frame) + } + fn from_call(frame: CallFrame) -> Self { + StandardFrame::Call(frame) + } + fn from_digraph(frame: DiGraphFrame) -> Self { + StandardFrame::DiGraph(frame) + } +} + +impl Frame for StandardFrame +where + I: FrameDriver + SparseForwardInterp>, + V: Clone, + E: From, +{ + type Completion = Completion; + + fn step(self, interp: &mut I) -> Result, I::Error> { + match self { + StandardFrame::Block(frame) => frame.step_into::(interp), + StandardFrame::Cfg(frame) => frame.step_into::(interp), + StandardFrame::Call(frame) => frame.step_into::(interp), + StandardFrame::DiGraph(frame) => frame.step_into::(interp), + } + } + + fn resume_done(self, _interp: &mut I) -> Result, I::Error> { + match self { + StandardFrame::Block(frame) => Ok(frame.resume_done_into::()), + StandardFrame::Cfg(frame) => Ok(frame.resume_done_into::()), + StandardFrame::Call(frame) => frame.resume_done_into::().map_err(I::Error::from), + StandardFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), + } + } + + fn resume( + self, + completion: Self::Completion, + interp: &mut I, + ) -> Result, I::Error> { + match self { + StandardFrame::Block(frame) => frame.resume_into::(completion, interp), + StandardFrame::Cfg(frame) => frame.resume_into::(completion, interp), + StandardFrame::Call(frame) => frame.resume_into::(completion, interp), + StandardFrame::DiGraph(frame) => frame.resume_into::(completion, interp), + } + } +} diff --git a/crates/kirin-interpreter/src/engines/concrete/interp.rs b/crates/kirin-interpreter/src/engines/concrete/interp.rs index 42315bbd94..b9a7624d8a 100644 --- a/crates/kirin-interpreter/src/engines/concrete/interp.rs +++ b/crates/kirin-interpreter/src/engines/concrete/interp.rs @@ -4,8 +4,8 @@ use kirin_ir::{Block, Cfg, CompileStage, Pipeline, Product, SSAValue, StageMeta, use crate::core::query; use crate::{ - Body, BodyFrame, CallableBody, Callee, Completion, Env, EnvIndex, EnvStackStore, ForwardEval, - Frame, FrameBuild, FrameDriver, FunctionTarget, Interp, InterpDispatch, InterpLocation, + CallFrame, CallableBody, Callee, Completion, Env, EnvIndex, EnvStackStore, ForwardEval, Frame, + FrameBuild, FrameDriver, FunctionTarget, Interp, InterpDispatch, InterpLocation, InterpreterError, Linker, SameStageLinker, SparseForwardEffect, StageQuery, StandardFrame, Store, drive_frames, }; @@ -231,44 +231,20 @@ where } /// Execute a function to completion and return its return product. + /// + /// The root call is an ordinary [`CallFrame`]: the same call boundary + /// that nested `Call` effects go through owns callee resolution, the + /// callee activation, body-kind selection, and completion validation — + /// there is exactly one implementation of that behavior. pub fn call( &mut self, stage: CompileStage, callee: Callee, args: impl IntoIterator, ) -> Result, E> { - let target = self.resolve_call(stage, &callee)?; - let index = self.alloc_env(); let args: Product = args.into_iter().collect(); - let body = self.enter_function(target.stage, target.body, args, index)?; - let frame = match body.body { - Body::Cfg(cfg) => F::from_body(BodyFrame::function( - self, - target.stage, - index, - cfg, - body.args, - )?), - Body::Block(block) => F::from_body(BodyFrame::linear_function( - self, - target.stage, - index, - block, - body.args, - )?), - Body::DiGraph(graph) => { - F::from_digraph(crate::engines::concrete::DiGraphFrame::function( - target.stage, - index, - graph, - body.args, - )) - } - other @ Body::UnGraph(_) => { - return Err(E::from(InterpreterError::NoDefaultWalker(other))); - } - }; - self.frames.push(frame); + self.frames + .push(F::from_call(CallFrame::root(stage, callee, args))); self.run() } @@ -281,9 +257,9 @@ where self.frames = frames; match completion? { Completion::Returned(values) => Ok(values), - Completion::Finished(_) => Err(E::from(InterpreterError::Custom( - "body completion reached the frame-stack root", - ))), + Completion::Yielded(_) | Completion::Finished(_) => Err(E::from( + InterpreterError::Custom("body completion reached the frame-stack root"), + )), } } } diff --git a/crates/kirin-interpreter/src/engines/concrete/mod.rs b/crates/kirin-interpreter/src/engines/concrete/mod.rs index ea1a276d8c..bb77d8cdc4 100644 --- a/crates/kirin-interpreter/src/engines/concrete/mod.rs +++ b/crates/kirin-interpreter/src/engines/concrete/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod frames; pub(crate) mod interp; pub use frames::{ - BlockMode, BodyFrame, CallFrame, Completion, DiGraphFrame, FrameBuild, StandardFrame, + BlockFrame, CallFrame, CfgFrame, Completion, DiGraphFrame, FrameBuild, StandardFrame, + UnGraphEntry, }; pub use interp::ConcreteInterpreter; diff --git a/crates/kirin-interpreter/src/engines/sparse_forward/frames.rs b/crates/kirin-interpreter/src/engines/sparse_forward/frames.rs index d14c120c42..2283e255c3 100644 --- a/crates/kirin-interpreter/src/engines/sparse_forward/frames.rs +++ b/crates/kirin-interpreter/src/engines/sparse_forward/frames.rs @@ -75,7 +75,7 @@ pub struct AbstractBlockFrame { cursor: Option, mode: BlockMode, /// Entry arguments not yet bound — bound on the first step, so building the - /// frame needs no engine access (see [`BodyFrame`](crate::BodyFrame)). + /// frame needs no engine access (see [`BlockFrame`](crate::BlockFrame)). pending: Option>, resume_slots: Option>, _marker: PhantomData (E, K)>, diff --git a/crates/kirin-interpreter/src/lib.rs b/crates/kirin-interpreter/src/lib.rs index 4b146f030d..8820e7c0be 100644 --- a/crates/kirin-interpreter/src/lib.rs +++ b/crates/kirin-interpreter/src/lib.rs @@ -84,10 +84,13 @@ pub use self::core::{ pub use self::core::ForwardDataflowFrameDriver as AbstractFrameDriver; pub use self::core::ForwardFrameDriver as FrameDriver; -// Concrete execution engine + the concrete standard frames. +// Concrete execution engine + the concrete standard frames: the +// representation walkers (`BlockFrame`/`CfgFrame`/`DiGraphFrame` — `UnGraph` +// traversal is a dialect/compiler policy supplied through +// `FrameBuild::from_ungraph_entry`) and the `CallFrame` call boundary. pub use engines::concrete::{ - BlockMode, BodyFrame, CallFrame, Completion, ConcreteInterpreter, DiGraphFrame, FrameBuild, - StandardFrame, + BlockFrame, CallFrame, CfgFrame, Completion, ConcreteInterpreter, DiGraphFrame, FrameBuild, + StandardFrame, UnGraphEntry, }; // Sparse forward engine (`Sem = ForwardEval`) + the abstract standard frames. pub use engines::sparse_forward::{ @@ -161,14 +164,14 @@ pub mod dialect { pub mod engine { pub use crate::{ AbstractBlockFrame, AbstractCallFrame, AbstractCompletion, AbstractFrameBuild, - AbstractFrameDriver, AbstractInterpreter, BodyFrame, CallContext, CallFrame, Callee, - Completion, ConcreteInterpreter, ContextInsensitive, CrossStageLinker, + AbstractFrameDriver, AbstractInterpreter, BlockFrame, CallContext, CallFrame, Callee, + CfgFrame, Completion, ConcreteInterpreter, ContextInsensitive, CrossStageLinker, DenseBackwardCompletion, DenseBackwardFrameDriver, DenseBackwardInterp, DenseBackwardInterpreter, DenseBlockFrame, DenseFrameBuild, DiGraphFrame, Env, ForwardDataflowFrameDriver, ForwardFrameDriver, Frame, FrameBuild, FrameDriver, FrameEffect, FrameEngine, FunctionTarget, Interp, InterpDispatch, InterpreterError, Linker, SameStageLinker, SparseBackwardInterp, SparseBackwardInterpreter, SparseForwardInterp, SparseForwardInterpreter, StandardAbstractFrame, StandardDenseBackwardFrame, StandardFrame, - WideningStrategy, drive_frames, expect_single, + UnGraphEntry, WideningStrategy, drive_frames, expect_single, }; } diff --git a/crates/kirin-scf/src/interpreter.rs b/crates/kirin-scf/src/interpreter.rs index e9fc9994bb..779f660238 100644 --- a/crates/kirin-scf/src/interpreter.rs +++ b/crates/kirin-scf/src/interpreter.rs @@ -10,10 +10,13 @@ //! both arms and joins their results (abstract). //! - `scf.for` -> [`ScfForFrame`] / [`AbstractScfForFrame`], via [`ScfForDispatch`]. //! -//! Both reuse the framework's generic [`BodyFrame`]/[`AbstractBlockFrame`] to +//! Both reuse the framework's generic [`BlockFrame`]/[`AbstractBlockFrame`] to //! *walk* a chosen body block — those are reusable building blocks, not //! framework-owned structured semantics — but the structured *decision* and -//! result binding are owned by the SCF frame. A language that uses `scf` +//! result binding are owned by the SCF frame: the block walker surfaces a +//! structured `Yield` as [`Completion::Yielded`], which the SCF frame consumes, +//! while a function `Return` ([`Completion::Returned`]) is relayed unchanged so +//! it bubbles to the nearest `CallFrame`. A language that uses `scf` //! composes a total frame type embedding these via [`BuildScfIf`]/[`BuildScfFor`] //! (and the abstract equivalents [`BuildAbstractScfIf`]/[`BuildAbstractScfFor`]). @@ -28,7 +31,7 @@ use kirin_interpreter::dialect::{ StrongDemand, }; use kirin_interpreter::{ - AbstractBlockFrame, AbstractCompletion, AbstractFrameBuild, AbstractFrameDriver, BodyFrame, + AbstractBlockFrame, AbstractCompletion, AbstractFrameBuild, AbstractFrameDriver, BlockFrame, CallContext, Completion, ConcreteInterpreter, DenseBackwardCompletion, DenseBackwardFrameDriver, DenseBlockFrame, DenseFrameBuild, EnvIndex, FrameBuild, FrameDriver, FrameEffect, PointFacts, SparseForwardTransfer, @@ -642,10 +645,12 @@ where // Concrete if frame: pick the decided arm, relay its completion. // =========================================================================== -/// Concrete `scf.if` traversal: push the framework [`BodyFrame`] for the decided -/// arm and relay its completion to the pusher. The structured *decision* (which -/// arm) is owned here; an undecided condition is impossible under concrete -/// execution (`IndeterminateBranch`). +/// Concrete `scf.if` traversal: push the framework [`BlockFrame`] for the +/// decided arm, consume the arm's structured `Yield`, and hand the yielded +/// values to the pusher. The structured *decision* (which arm) is owned here; +/// an undecided condition is impossible under concrete execution +/// (`IndeterminateBranch`). A function `Return` inside the arm is relayed +/// unchanged so it bubbles to the nearest `CallFrame`. pub struct ScfIfFrame { stage: CompileStage, env: EnvIndex, @@ -687,10 +692,10 @@ where Some(false) => self.else_body, None => return Err(E::from(InterpreterError::IndeterminateBranch)), }; - let body = BodyFrame::block(self.stage, self.env, arm, Product::new()); + let body = BlockFrame::new(self.stage, self.env, arm, Product::new()); Ok(FrameEffect::Push { parent: F::scf_if(self), - child: F::from_body(body), + child: F::from_block(body), }) } @@ -704,8 +709,16 @@ where self, completion: Completion, ) -> Result>, E> { - // Relay the chosen arm's completion (yield-finish or function return). - Ok(FrameEffect::Complete(completion)) + match completion { + // The arm's structured yield: its values are this operation's + // results, delivered to the pusher as a finished sub-computation. + Completion::Yielded(values) => Ok(FrameEffect::Complete(Completion::Finished(values))), + // A `ret` inside the arm: relay it toward the nearest `CallFrame`. + Completion::Returned(values) => Ok(FrameEffect::Complete(Completion::Returned(values))), + Completion::Finished(_) => Err(E::from(InterpreterError::Custom( + "scf.if arm completed without a structured yield", + ))), + } } } @@ -875,10 +888,10 @@ where let args: Product = std::iter::once(self.induction.clone()) .chain(self.carried.iter().cloned()) .collect(); - let body = BodyFrame::block(self.stage, self.env, self.body, args); + let body = BlockFrame::new(self.stage, self.env, self.body, args); Ok(FrameEffect::Push { parent: F::scf_for(self), - child: F::from_body(body), + child: F::from_block(body), }) } Some(false) => Ok(FrameEffect::Complete(Completion::Finished(self.carried))), @@ -902,8 +915,9 @@ where F: FrameBuild + BuildScfFor, { match completion { - // The body yielded: advance the induction variable and re-check. - Completion::Finished(yielded) => { + // The body's structured yield: advance the induction variable, + // carry the yielded values forward, and re-check the condition. + Completion::Yielded(yielded) => { let step = interp.env_read(self.env, self.step)?; let next = self .induction @@ -913,8 +927,11 @@ where self.carried = yielded; Ok(FrameEffect::Continue(F::scf_for(self))) } - // A `ret` inside the body returns from the enclosing function. + // A `ret` inside the body: relay it toward the nearest `CallFrame`. Completion::Returned(values) => Ok(FrameEffect::Complete(Completion::Returned(values))), + Completion::Finished(_) => Err(E::from(InterpreterError::Custom( + "scf.for body completed without a structured yield", + ))), } } } diff --git a/crates/kirin-test-languages/src/graph_function_language.rs b/crates/kirin-test-languages/src/graph_function_language.rs index ffb8896f02..6000fc5435 100644 --- a/crates/kirin-test-languages/src/graph_function_language.rs +++ b/crates/kirin-test-languages/src/graph_function_language.rs @@ -8,7 +8,9 @@ use kirin_arith::{Arith, ArithType, ArithValue}; use kirin_cf::ControlFlow; use kirin_constant::Constant; use kirin_function::{Call, Return}; -use kirin_ir::{Block, Cfg, DiGraph, Dialect, Placeholder as _, ResultValue, SSAValue, Signature}; +use kirin_ir::{ + Block, Cfg, DiGraph, Dialect, Placeholder as _, ResultValue, SSAValue, Signature, UnGraph, +}; #[derive(Debug, Clone, PartialEq, Dialect)] #[cfg_attr(feature = "parser", derive(kirin_chumsky::HasParser))] @@ -44,6 +46,18 @@ pub enum GraphFunctionLanguage { body: Block, sig: Signature, }, + /// UnGraph-bodied callable. The framework has no default walker for an + /// undirected graph body: calling one requires the compiler to supply a + /// traversal policy (`FrameBuild::from_ungraph_entry`), otherwise the + /// engine reports `NoDefaultWalker`. + #[cfg_attr( + any(feature = "parser", feature = "pretty"), + chumsky(format = "fn {:name}{sig} {body}") + )] + UnGraphFunction { + body: UnGraph, + sig: Signature, + }, /// Inline graph evaluation: enters its owned digraph via a pushed frame. #[cfg_attr( any(feature = "parser", feature = "pretty"), @@ -99,7 +113,8 @@ mod interpreter { match self { GraphFunctionLanguage::Function { .. } | GraphFunctionLanguage::GraphFunction { .. } - | GraphFunctionLanguage::LinearFunction { .. } => Ok(SparseForwardEffect::Next), + | GraphFunctionLanguage::LinearFunction { .. } + | GraphFunctionLanguage::UnGraphFunction { .. } => Ok(SparseForwardEffect::Next), GraphFunctionLanguage::GraphEval { lhs, rhs, @@ -109,7 +124,10 @@ mod interpreter { let args: Product = [interp.read(*lhs)?, interp.read(*rhs)?] .into_iter() .collect(); - let frame = DiGraphFrame::nested(interp.stage(), interp.index(), *graph, args); + // A nested (uncallable) graph body: no function activation, + // no callee resolution — the operation pushes the walker + // directly into the current activation. + let frame = DiGraphFrame::new(interp.stage(), interp.index(), *graph, args); Ok(SparseForwardEffect::Push { frame: I::Frame::from_digraph(frame), results: [SSAValue::from(*result)].into_iter().collect(), @@ -140,6 +158,9 @@ mod interpreter { GraphFunctionLanguage::LinearFunction { body, .. } => { Ok(CallableBody::new(*body).args(args)) } + GraphFunctionLanguage::UnGraphFunction { body, .. } => { + Ok(CallableBody::new(*body).args(args)) + } _ => Err(I::Error::from(InterpreterError::NotCallable( interp.statement(), ))), diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index 7336cf6809..ac46909d10 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -199,7 +199,9 @@ SCF has two such operations: reads the condition value and hands the `Option` decision to the frame; the **frame** picks the arm (concrete; undecided is `IndeterminateBranch`) or explores both arms and **joins** their finish results (abstract). It walks each - arm by pushing the framework `BodyFrame`/`AbstractBlockFrame` building block. + arm by pushing the framework `BlockFrame`/`AbstractBlockFrame` building block, + consumes the arm's `Completion::Yielded` values, and relays a bubbled + `Completion::Returned` unchanged toward the nearest `CallFrame`. - **`scf.for`** → `ScfForFrame` / `AbstractScfForFrame`, built via `ScfForDispatch`. The frame pushes a body frame each iteration, advances the @@ -209,10 +211,11 @@ SCF has two such operations: accumulating finish values across exits — so `scf.for` over a lattice converges, with no framework "scope hook". -The framework `BodyFrame`/`AbstractBlockFrame` (single-block body walkers, -completing on `Yield`) are reusable **building blocks**, not framework-owned -structured semantics: the SCF frames build them to walk a chosen body, but the -structured *decision* and result binding stay in the SCF frame. A language that +The framework `BlockFrame`/`AbstractBlockFrame` (single-block body walkers, +surfacing `Yield` to their parent) are reusable **building blocks**, not +framework-owned structured semantics: the SCF frames build them to walk a +chosen body, but the structured *decision* and result binding stay in the SCF +frame. A language that uses SCF composes a total frame type embedding the standard frames plus `ScfIfFrame`/`ScfForFrame` (via `BuildScfIf`/`BuildScfFor` and the abstract equivalents); see `example/toy-lang`'s `ToyFrame`/`ToyAbstractFrame`. Future @@ -294,13 +297,40 @@ belongs to. A generic **frame-stack driver**: it pops the top frame, calls `Frame::step`, and applies the returned `FrameEffect` (`Continue` / `Push` / `Done` / `Complete`) — it owns *no* traversal logic itself. Traversal lives in the -frames. The default total frame type `StandardFrame` wraps the standard -`BodyFrame` (walks a function-body CFG, or a single body block that -completes on `Yield` — `Jump` retargets it, `Return` completes it) and -`CallFrame` (dispatch a callee, await its `Return`). The dialect-produced -`SparseForwardEffect` is consumed by `BodyFrame`, which maps it to a `FrameEffect` -(handling `Push` by pushing the carried frame). `StandardFrame` is -structured-control-free; a custom `F` +frames, organized along two independent axes: + +- **Body representation** (the closed `Body` vocabulary — an intentional IR + design decision): each framework-walkable representation has one + *representation walker* owning traversal mechanics only — `CfgFrame` + (multi-block, follows `Jump`, rejects an undecided `Branch`), `BlockFrame` + (one linear block; `Jump`/`Branch` are errors), and `DiGraphFrame` + (dependency-ordered DAG walk collecting the declared yields). `UnGraph` has + **no default walker**: an undirected graph has no inherent execution order, + so callable-UnGraph traversal is a dialect/compiler-supplied policy + (`FrameBuild::from_ungraph_entry`, defaulting to `NoDefaultWalker`). +- **Entry context**: the same walker serves a *callable* body (entered + through `CallFrame`) and a *nested structured-operation* body (entered + through a dialect frame); analysis owners are the abstract engines' third + context. Walkers never know their role — they surface exits through the + completion protocol (`Completion::Returned` for a function `Return`, + `Completion::Yielded` for a structured `Yield`, `Completion::Finished` for + natural completion such as a digraph's output yields) and the parent frame + decides what each means. + +`CallFrame` is the **call boundary**: it resolves the callee, allocates the +callee activation, selects the entry walker for the closed `Body` variant, +validates the completion kind (`Returned`, or a graph's natural `Finished`; +a structured `Yielded` is an error), frees the callee activation exactly +once, and delivers the values — into the caller's result slots, or as the +run's result for a root call (`ConcreteInterpreter::call` pushes a +`CallFrame::root`, so root and nested calls share one boundary +implementation). Representation walkers never free activations; a `Returned` +bubbles through dialect frames to the nearest `CallFrame`. + +The default total frame type `StandardFrame` bundles the three walkers +plus `CallFrame`. The dialect-produced `SparseForwardEffect` is consumed by +the walkers, which map it to a `FrameEffect` (handling `Push` by pushing the +carried frame). `StandardFrame` is structured-control-free; a custom `F` ([Custom traversal and policies](#custom-traversal-and-policies)) adds dialect frames or replaces traversal without touching the engine. @@ -426,13 +456,17 @@ it**. The concrete and abstract standard frames are two *implementations* of this one protocol — not parallel frameworks. -### Concrete frames — `BodyFrame` / `CallFrame` / `StandardFrame` +### Concrete frames — `BlockFrame` / `CfgFrame` / `DiGraphFrame` / `CallFrame` / `StandardFrame` `ConcreteInterpreter` is generic over the total frame type `F` (default -`StandardFrame`). A custom enum reuses the standard `BodyFrame`/`CallFrame` -single-path traversal through `FrameBuild` (`from_body`/`from_call`) and their -`*_into` delegating methods, adds dialect frames / observation, and instantiates -the engine with that `F`. (Examples: `example/toy-lang`'s `ToyFrame`, which adds +`StandardFrame`). A custom enum reuses the standard single-path traversal — +the representation walkers and the call boundary — through `FrameBuild` +(`from_block`/`from_cfg`/`from_call`/`from_digraph`) and their `*_into` +delegating methods, adds dialect frames / observation, and instantiates the +engine with that `F`. Overriding `FrameBuild::from_ungraph_entry` (default: +`NoDefaultWalker`) supplies a callable-UnGraph traversal policy without +touching the generic logic (see the workspace `tests/body_kinds.rs` policy +test). (Further examples: `example/toy-lang`'s `ToyFrame`, which adds `kirin_scf`'s `ScfIfFrame`/`ScfForFrame` via `BuildScfIf`/`BuildScfFor`; and a `TracingFrame` counting call/body visitation while running the real program — see `example/toy-lang`'s `interpreter::tests::advanced`.) diff --git a/example/toy-lang/src/interpreter/frame.rs b/example/toy-lang/src/interpreter/frame.rs index 276b491e5a..9931a1b0da 100644 --- a/example/toy-lang/src/interpreter/frame.rs +++ b/example/toy-lang/src/interpreter/frame.rs @@ -3,16 +3,18 @@ //! The toy language uses `kirin-scf`, whose `scf.for` pushes a dialect-owned //! loop frame ([`ScfForFrame`]/[`AbstractScfForFrame`]). A language that uses //! such a dialect composes its own total frame enum embedding the standard -//! framework frames (via [`FrameBuild`]/[`AbstractFrameBuild`]) plus the -//! dialect frames (via [`BuildScfFor`]/[`BuildAbstractScfFor`]). The engine is +//! framework frames — the representation walkers +//! ([`BlockFrame`]/[`CfgFrame`]/[`DiGraphFrame`]) and the [`CallFrame`] call +//! boundary, via [`FrameBuild`]/[`AbstractFrameBuild`] — plus the dialect +//! frames (via [`BuildScfFor`]/[`BuildAbstractScfFor`]). The engine is //! not forked — only the engine's `F` type parameter changes. use std::hash::Hash; use kirin_interpreter::engine::{ AbstractBlockFrame, AbstractCallFrame, AbstractCompletion, AbstractFrameBuild, - AbstractFrameDriver, BodyFrame, CallFrame, Completion, DiGraphFrame, Frame, FrameBuild, - FrameDriver, FrameEffect, InterpreterError, SparseForwardInterp, + AbstractFrameDriver, BlockFrame, CallFrame, CfgFrame, Completion, DiGraphFrame, Frame, + FrameBuild, FrameDriver, FrameEffect, InterpreterError, SparseForwardInterp, }; use kirin_scf::{ AbstractScfForFrame, AbstractScfIfFrame, BuildAbstractScfFor, BuildAbstractScfIf, BuildScfFor, @@ -23,9 +25,11 @@ use kirin_scf::{ // Concrete // =========================================================================== -/// Concrete total frame: standard body/call traversal plus the SCF if/for frames. +/// Concrete total frame: the standard representation walkers and call +/// boundary plus the SCF if/for frames. pub enum ToyFrame { - Body(BodyFrame), + Block(BlockFrame), + Cfg(CfgFrame), Call(CallFrame), DiGraph(DiGraphFrame), ScfIf(ScfIfFrame), @@ -33,8 +37,11 @@ pub enum ToyFrame { } impl FrameBuild for ToyFrame { - fn from_body(frame: BodyFrame) -> Self { - ToyFrame::Body(frame) + fn from_block(frame: BlockFrame) -> Self { + ToyFrame::Block(frame) + } + fn from_cfg(frame: CfgFrame) -> Self { + ToyFrame::Cfg(frame) } fn from_call(frame: CallFrame) -> Self { ToyFrame::Call(frame) @@ -66,7 +73,8 @@ where fn step(self, interp: &mut I) -> Result, I::Error> { match self { - ToyFrame::Body(frame) => frame.step_into::(interp), + ToyFrame::Block(frame) => frame.step_into::(interp), + ToyFrame::Cfg(frame) => frame.step_into::(interp), ToyFrame::Call(frame) => frame.step_into::(interp), ToyFrame::DiGraph(frame) => frame.step_into::(interp), ToyFrame::ScfIf(frame) => frame.step_into::(interp), @@ -76,7 +84,8 @@ where fn resume_done(self, _interp: &mut I) -> Result, I::Error> { match self { - ToyFrame::Body(frame) => Ok(frame.resume_done_into::()), + ToyFrame::Block(frame) => Ok(frame.resume_done_into::()), + ToyFrame::Cfg(frame) => Ok(frame.resume_done_into::()), ToyFrame::Call(frame) => frame.resume_done_into::().map_err(I::Error::from), ToyFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), ToyFrame::ScfIf(frame) => frame.resume_done_into::(), @@ -90,7 +99,8 @@ where interp: &mut I, ) -> Result, I::Error> { match self { - ToyFrame::Body(frame) => frame.resume_into::(completion, interp), + ToyFrame::Block(frame) => frame.resume_into::(completion, interp), + ToyFrame::Cfg(frame) => frame.resume_into::(completion, interp), ToyFrame::Call(frame) => frame.resume_into::(completion, interp), ToyFrame::DiGraph(frame) => frame.resume_into::(completion, interp), ToyFrame::ScfIf(frame) => frame.resume_into::(completion), diff --git a/example/toy-lang/src/interpreter/tests.rs b/example/toy-lang/src/interpreter/tests.rs index 7057924243..2fae32dc9f 100644 --- a/example/toy-lang/src/interpreter/tests.rs +++ b/example/toy-lang/src/interpreter/tests.rs @@ -566,9 +566,10 @@ mod advanced { use kirin_constprop::{ConstPropContext, ConstPropValue}; use kirin_interpreter::engine::{ AbstractBlockFrame, AbstractCallFrame, AbstractCompletion, AbstractFrameBuild, - AbstractFrameDriver, BodyFrame, CallContext, CallFrame, Completion, ConcreteInterpreter, - CrossStageLinker, DiGraphFrame, Frame, FrameBuild, FrameDriver, FrameEffect, - InterpreterError, SparseForwardInterp, SparseForwardInterpreter, expect_single, + AbstractFrameDriver, BlockFrame, CallContext, CallFrame, CfgFrame, Completion, + ConcreteInterpreter, CrossStageLinker, DiGraphFrame, Frame, FrameBuild, FrameDriver, + FrameEffect, InterpreterError, SparseForwardInterp, SparseForwardInterpreter, + expect_single, }; use kirin_scf::{ AbstractScfForFrame, AbstractScfIfFrame, BuildAbstractScfFor, BuildAbstractScfIf, @@ -581,8 +582,9 @@ mod advanced { // --- A custom total frame enum ----------------------------------------- // - // It reuses the standard `BodyFrame`/`CallFrame` traversal (and the SCF loop - // frame) verbatim via `FrameBuild`/`BuildScfFor` + the delegating `*_into` + // It reuses the standard representation walkers (`BlockFrame`/`CfgFrame`/ + // `DiGraphFrame`), the `CallFrame` call boundary, and the SCF frames + // verbatim via `FrameBuild`/`BuildScfFor` + the delegating `*_into` // methods, and adds *observation*: every call and every body step is counted // in a side log. The engine is not forked — only `ConcreteInterpreter`'s `F` // type parameter changes. @@ -598,7 +600,8 @@ mod advanced { } enum TracingFrame { - Body(BodyFrame), + Block(BlockFrame), + Cfg(CfgFrame), Call(CallFrame), DiGraph(DiGraphFrame), ScfIf(ScfIfFrame), @@ -606,8 +609,11 @@ mod advanced { } impl FrameBuild for TracingFrame { - fn from_body(frame: BodyFrame) -> Self { - TracingFrame::Body(frame) + fn from_block(frame: BlockFrame) -> Self { + TracingFrame::Block(frame) + } + fn from_cfg(frame: CfgFrame) -> Self { + TracingFrame::Cfg(frame) } fn from_call(frame: CallFrame) -> Self { TracingFrame::Call(frame) @@ -639,7 +645,11 @@ mod advanced { fn step(self, interp: &mut I) -> Result, I::Error> { match self { - TracingFrame::Body(frame) => { + TracingFrame::Block(frame) => { + TRACE.with(|t| t.borrow_mut().body_steps += 1); + frame.step_into::(interp) + } + TracingFrame::Cfg(frame) => { TRACE.with(|t| t.borrow_mut().body_steps += 1); frame.step_into::(interp) } @@ -658,7 +668,8 @@ mod advanced { _interp: &mut I, ) -> Result, I::Error> { match self { - TracingFrame::Body(frame) => Ok(frame.resume_done_into::()), + TracingFrame::Block(frame) => Ok(frame.resume_done_into::()), + TracingFrame::Cfg(frame) => Ok(frame.resume_done_into::()), TracingFrame::Call(frame) => { frame.resume_done_into::().map_err(I::Error::from) } @@ -674,7 +685,8 @@ mod advanced { interp: &mut I, ) -> Result, I::Error> { match self { - TracingFrame::Body(frame) => frame.resume_into::(completion, interp), + TracingFrame::Block(frame) => frame.resume_into::(completion, interp), + TracingFrame::Cfg(frame) => frame.resume_into::(completion, interp), TracingFrame::Call(frame) => frame.resume_into::(completion, interp), TracingFrame::DiGraph(frame) => frame.resume_into::(completion, interp), TracingFrame::ScfIf(frame) => frame.resume_into::(completion), @@ -706,15 +718,16 @@ mod advanced { .unwrap(); // (1)+(2): the custom frame ran the real program correctly by reusing - // the standard BodyFrame/CallFrame traversal (no engine fork). + // the standard walker/CallFrame traversal (no engine fork). assert_eq!(result, 120); // (3): traversal is observable through the custom frame. factorial(5) - // makes 4 recursive calls (5→4→3→2→1; the base case at 1 makes none), - // all routed through the custom Call arm; body statements run through - // its Body arm. + // is 5 activations: the root call plus 4 recursive calls (5→4→3→2→1; + // the base case at 1 makes none) — every call, root included, is one + // `CallFrame` routed through the custom Call arm; body statements run + // through its Block/Cfg arms. let trace = TRACE.with(|t| *t.borrow()); - assert_eq!(trace.calls, 4); + assert_eq!(trace.calls, 5); assert!(trace.body_steps > 0); } diff --git a/tests/body_kinds.rs b/tests/body_kinds.rs index 0b1e9d332a..2a74ef4bb0 100644 --- a/tests/body_kinds.rs +++ b/tests/body_kinds.rs @@ -1,16 +1,42 @@ -//! Acceptance tests for generic interpreter bodies (issue #667): a mixed -//! language where regular Cfg-SSA code and DiGraph computational graphs -//! call into each other, plus linear (Block-bodied) callables. +//! Acceptance tests for generic interpreter bodies (issue #667). +//! +//! Two independent axes organize concrete traversal: +//! +//! - **Body representation** — the closed `Body` vocabulary: `Cfg`, `Block`, +//! `DiGraph`, `UnGraph`. Each framework-walkable representation has one +//! walker (`CfgFrame`/`BlockFrame`/`DiGraphFrame`); `UnGraph` traversal is +//! a compiler-supplied policy (`FrameBuild::from_ungraph_entry`). +//! - **Entry context** — callable (entered through `CallFrame`, which owns +//! the callee activation) vs. nested (entered through a dialect frame +//! pushed with `SparseForwardEffect::Push`, borrowing the current +//! activation). +//! +//! The tests cover the composition matrix: callable Cfg/Block/DiGraph bodies +//! (`CallFrame` → walker), nested DiGraph and scf Blocks (dialect frame → +//! walker), returns bubbling through dialect frames to the nearest +//! `CallFrame`, and the callable-UnGraph policy hook (with and without a +//! policy). + +use std::collections::VecDeque; use kirin::prelude::*; -use kirin_arith::{ArithConversionError, interpreter::DivisionByZero}; +use kirin_arith::{ + Arith, ArithConversionError, ArithType, ArithValue, interpreter::DivisionByZero, +}; +use kirin_cmp::Cmp; +use kirin_constant::Constant; +use kirin_function::Lexical; use kirin_interpreter::{ - ConcreteInterpreter, InterpreterError, SameStageLinker, StandardFrame, expect_single, + BlockFrame, Body, CallFrame, CfgFrame, Completion, ConcreteInterpreter, DiGraphFrame, Env, + EnvIndex, Frame, FrameBuild, FrameDriver, FrameEffect, FunctionEntry, Interpretable, + InterpreterError, SameStageLinker, SparseForwardEffect, StandardFrame, UnGraphEntry, + expect_single, }; +use kirin_scf::{BuildScfFor, BuildScfIf, ScfForFrame, ScfIfFrame, StructuredControlFlow}; use kirin_test_languages::GraphFunctionLanguage; -/// Total error for the test engine: the framework error plus the value -/// conversion/trap errors the mixed language's rules can raise. +/// Total error for the test engines: the framework error plus the value +/// conversion/trap errors the languages' rules can raise. #[derive(Debug)] enum TestError { Core(InterpreterError), @@ -49,9 +75,17 @@ fn run(pipeline: &Pipeline, function: &str, args: &[i64]) -> Result i64 { assert_eq!(run(&pipeline, "main", &[]).unwrap(), 5); } -/// Entry path 2 (the dialect-frame path): a statement inside a Cfg block -/// owns a DiGraph body and enters it with `Push` — the same way `scf.if` -/// enters its Block arms. +// =========================================================================== +// 2. Nested/pushed DiGraph: dialect operation → DiGraphFrame. +// =========================================================================== + +/// A statement inside a Cfg block owns a DiGraph body and enters it with +/// `SparseForwardEffect::Push` — the same way `scf.if` enters its Block +/// arms. Unlike test 1 there is **no** `CallFrame` in the chain: no callee +/// resolution happens, no function activation is allocated or freed — the +/// pushed `DiGraphFrame` runs in the *pusher's* activation, and its +/// `Finished` yields land in the pushing statement's `Push` result slots +/// rather than in call-return slots. #[test] fn cfg_statement_pushes_digraph_body() { let pipeline = parse( @@ -102,8 +144,16 @@ specialize @test fn @main() -> i64 { assert_eq!(run(&pipeline, "main", &[]).unwrap(), 42); } +// =========================================================================== +// 3. Callable Block: caller → CallFrame → BlockFrame. +// =========================================================================== + /// A linear (single-Block) callable: the flat-instruction-list function -/// shape (QOS-style compile targets). Exits with `Return`. +/// shape. `Body::Block` maps to the same +/// `BlockFrame` that walks nested structured blocks — there is no separate +/// "linear function frame"; the `CallFrame` parent is what makes this walk a +/// function body. The block exits with `Return`, which the `CallFrame` +/// validates and consumes. #[test] fn linear_block_callable() { let pipeline = parse( @@ -129,8 +179,20 @@ specialize @test fn @main() -> i64 { assert_eq!(run(&pipeline, "main", &[]).unwrap(), 42); } -/// Graph nodes run in dependency order, not declaration order: the yield -/// depends on a node declared after its operand producer. +// =========================================================================== +// 4. DiGraph dependency order. +// =========================================================================== + +/// Graph nodes run in dependency order, not declaration order. The graph +/// branches visibly: one input port feeds two independent producers declared +/// *after* their consumer, whose results merge into the yielded node — so a +/// textual/linear walk would read unbound operands. +/// +/// ```text +/// %x ──┬─▶ %c = add %x, %x ──┐ +/// │ ├─▶ %d = mul %c, %e ──▶ yield +/// └─▶ %e = add %x, %one ┘ +/// ``` #[test] fn digraph_runs_in_topological_order() { let pipeline = parse( @@ -139,8 +201,10 @@ stage @test fn @g(i64) -> i64; stage @test fn @main() -> i64; specialize @test fn @g(i64) -> i64 digraph ^g0(%x: i64) { - %d = mul %c, %c -> i64; + %d = mul %c, %e -> i64; %c = add %x, %x -> i64; + %e = add %x, %one -> i64; + %one = constant 1 -> i64; yield %d; } @@ -153,6 +217,489 @@ specialize @test fn @main() -> i64 { } "#, ); - // (3 + 3)^2 = 36 — requires running `add` before `mul` despite text order. - assert_eq!(run(&pipeline, "main", &[]).unwrap(), 36); + // (3 + 3) * (3 + 1) = 24 — requires running both `add`s (and the + // constant) before the `mul` despite the textual order. + assert_eq!(run(&pipeline, "main", &[]).unwrap(), 24); +} + +// =========================================================================== +// An scf-composed language for tests 5 and 6: structured operations enter +// nested Blocks through dialect frames (ScfIfFrame/ScfForFrame → BlockFrame). +// =========================================================================== + +/// Inline language wrapping functions (Cfg bodies), scf, and arithmetic. +/// Specific to this integration suite; shared test dialects live in +/// `kirin-test-languages`. +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Dialect, FunctionEntry, HasParser, PrettyPrint, Interpretable, +)] +#[kirin(builders, type = ArithType)] +enum ScfLanguage { + #[wraps] + #[callable] + Lexical(Lexical), + #[wraps] + Structured(StructuredControlFlow), + #[wraps] + Constant(Constant), + #[wraps] + Arith(Arith), + #[wraps] + Cmp(Cmp), +} + +/// Total frame enum for the scf tests: the standard representation walkers +/// and call boundary plus the dialect-owned SCF frames (composition, not an +/// engine fork). +enum ScfTestFrame { + Block(BlockFrame), + Cfg(CfgFrame), + Call(CallFrame), + DiGraph(DiGraphFrame), + ScfIf(ScfIfFrame), + ScfFor(ScfForFrame), +} + +impl FrameBuild for ScfTestFrame { + fn from_block(frame: BlockFrame) -> Self { + ScfTestFrame::Block(frame) + } + fn from_cfg(frame: CfgFrame) -> Self { + ScfTestFrame::Cfg(frame) + } + fn from_call(frame: CallFrame) -> Self { + ScfTestFrame::Call(frame) + } + fn from_digraph(frame: DiGraphFrame) -> Self { + ScfTestFrame::DiGraph(frame) + } +} + +impl BuildScfIf for ScfTestFrame { + fn scf_if(frame: ScfIfFrame) -> Self { + ScfTestFrame::ScfIf(frame) + } +} + +impl BuildScfFor for ScfTestFrame { + fn scf_for(frame: ScfForFrame) -> Self { + ScfTestFrame::ScfFor(frame) + } +} + +impl Frame for ScfTestFrame +where + I: FrameDriver + + kirin_interpreter::SparseForwardInterp>, + V: Clone + kirin_scf::ForLoopValue, + E: From, +{ + type Completion = Completion; + + fn step(self, interp: &mut I) -> Result, I::Error> { + match self { + ScfTestFrame::Block(frame) => frame.step_into::(interp), + ScfTestFrame::Cfg(frame) => frame.step_into::(interp), + ScfTestFrame::Call(frame) => frame.step_into::(interp), + ScfTestFrame::DiGraph(frame) => frame.step_into::(interp), + ScfTestFrame::ScfIf(frame) => frame.step_into::(interp), + ScfTestFrame::ScfFor(frame) => frame.step_into::(interp), + } + } + + fn resume_done(self, _interp: &mut I) -> Result, I::Error> { + match self { + ScfTestFrame::Block(frame) => Ok(frame.resume_done_into::()), + ScfTestFrame::Cfg(frame) => Ok(frame.resume_done_into::()), + ScfTestFrame::Call(frame) => frame.resume_done_into::().map_err(I::Error::from), + ScfTestFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), + ScfTestFrame::ScfIf(frame) => frame.resume_done_into::(), + ScfTestFrame::ScfFor(frame) => frame.resume_done_into::(), + } + } + + fn resume( + self, + completion: Self::Completion, + interp: &mut I, + ) -> Result, I::Error> { + match self { + ScfTestFrame::Block(frame) => frame.resume_into::(completion, interp), + ScfTestFrame::Cfg(frame) => frame.resume_into::(completion, interp), + ScfTestFrame::Call(frame) => frame.resume_into::(completion, interp), + ScfTestFrame::DiGraph(frame) => frame.resume_into::(completion, interp), + ScfTestFrame::ScfIf(frame) => frame.resume_into::(completion), + ScfTestFrame::ScfFor(frame) => frame.resume_into::(completion, interp), + } + } +} + +type ScfL = StageInfo; +type ScfEngine<'ir> = + ConcreteInterpreter<'ir, ScfL, i64, TestError, SameStageLinker, ScfTestFrame>; + +fn parse_scf(program: &str) -> Pipeline { + let mut pipeline: Pipeline = Pipeline::new(); + ParsePipelineText::parse(&mut pipeline, program).expect("program parses"); + pipeline +} + +fn run_scf(pipeline: &Pipeline, function: &str, args: &[i64]) -> Result { + let mut interp: ScfEngine<'_> = ConcreteInterpreter::new(pipeline).with_linker(SameStageLinker); + expect_single(interp.call_by_name("test", function, args.iter().copied())?) +} + +// =========================================================================== +// 5. Nested structured Block: ScfIfFrame/ScfForFrame → BlockFrame. +// =========================================================================== + +/// `scf.if` picks the decided arm and pushes the framework `BlockFrame` for +/// it; the arm's `yield` surfaces as `Completion::Yielded`, which the +/// `ScfIfFrame` consumes and hands to the pusher as the operation's results. +#[test] +fn scf_if_arm_yields_to_dialect_frame() { + let pipeline = parse_scf( + r#" +stage @test fn @abs(i64) -> i64; + +specialize @test fn @abs(i64) -> i64 { + ^entry(%x: i64) { + %zero = constant 0 -> i64; + %is_neg = lt %x, %zero -> i64; + %result = if %is_neg then ^then() { + %negated = neg %x -> i64; + yield %negated; + } else ^else() { + yield %x; + } -> i64; + ret %result; + } +} +"#, + ); + assert_eq!(run_scf(&pipeline, "abs", &[-7]).unwrap(), 7); + assert_eq!(run_scf(&pipeline, "abs", &[4]).unwrap(), 4); +} + +/// `scf.for` re-pushes the framework `BlockFrame` per iteration; each +/// `Completion::Yielded` carries the loop-carried values into the next turn, +/// and loop exit completes `Finished` to the pusher. +#[test] +fn scf_for_loop_carries_yielded_values() { + let pipeline = parse_scf( + r#" +stage @test fn @sum_below(i64) -> i64; + +specialize @test fn @sum_below(i64) -> i64 { + ^entry(%n: i64) { + %zero = constant 0 -> i64; + %one = constant 1 -> i64; + %sum = for %zero in %zero..%n step %one iter_args(%zero) do ^body(%i: i64, %acc: i64) { + %next = add %acc, %i -> i64; + yield %next; + } -> i64; + ret %sum; + } +} +"#, + ); + // 0 + 1 + 2 + 3 + 4 = 10. + assert_eq!(run_scf(&pipeline, "sum_below", &[5]).unwrap(), 10); + // Zero iterations: the initial carried value flows through. + assert_eq!(run_scf(&pipeline, "sum_below", &[0]).unwrap(), 0); +} + +// =========================================================================== +// 6. Return through nested structured control. +// =========================================================================== + +/// A function `Return` inside an `scf.if` arm: the arm's `BlockFrame` +/// completes `Returned`, the `ScfIfFrame` relays it (it is not a call +/// boundary), the function's `CfgFrame` relays it too, and the nearest +/// `CallFrame` consumes it — freeing the callee activation exactly once and +/// writing the caller's result slots. Execution must not continue after the +/// return: the statements below the `if` never run on the early-return path. +#[test] +fn return_bubbles_through_scf_frames_to_call_frame() { + let pipeline = parse_scf( + r#" +stage @test fn @clamp0(i64) -> i64; +stage @test fn @twice(i64) -> i64; + +specialize @test fn @clamp0(i64) -> i64 { + ^entry(%x: i64) { + %zero = constant 0 -> i64; + %is_neg = lt %x, %zero -> i64; + %kept = if %is_neg then ^then() { + ret %zero; + } else ^else() { + yield %x; + } -> i64; + %one = constant 1 -> i64; + %r = add %kept, %one -> i64; + ret %r; + } +} + +specialize @test fn @twice(i64) -> i64 { + ^entry(%x: i64) { + %a = call.named @clamp0(%x) -> i64; + %b = call.named @clamp0(%x) -> i64; + %s = add %a, %b -> i64; + ret %s; + } +} +"#, + ); + // Early return: 0, not 0 + 1 — the add after the `if` did not run. + assert_eq!(run_scf(&pipeline, "clamp0", &[-5]).unwrap(), 0); + // Normal path: the arm yields, execution continues after the `if`. + assert_eq!(run_scf(&pipeline, "clamp0", &[5]).unwrap(), 6); + // Two nested calls taking the early-return path in one run: each callee + // activation is freed exactly once and the caller's activation survives, + // otherwise the second call (or the final add) would read freed state. + assert_eq!(run_scf(&pipeline, "twice", &[-5]).unwrap(), 0); + assert_eq!(run_scf(&pipeline, "twice", &[5]).unwrap(), 12); +} + +// =========================================================================== +// 7. Custom callable-UnGraph policy: caller → CallFrame → policy frame. +// =========================================================================== + +// The framework refuses to invent an execution order for an undirected +// graph, so the *compiler* supplies one by overriding +// `FrameBuild::from_ungraph_entry` on its total frame type. This policy +// interprets an ungraph as a sequential dataflow chain: +// +// - **scheduling**: node statements run in the graph's canonical node +// enumeration order (an explicit policy choice — the generic `CallFrame` +// never orders anything); +// - **outputs**: the call returns the result values of the *last* scheduled +// node (an ungraph has no framework output convention such as a digraph's +// declared yields, so the policy defines one). + +/// Total frame enum for the UnGraph-policy engine: the standard frames plus +/// the policy's own walker. Only `from_ungraph_entry` differs from the +/// default composition — the generic traversal logic is untouched. +enum UnPolicyFrame { + Block(BlockFrame), + Cfg(CfgFrame), + Call(CallFrame), + DiGraph(DiGraphFrame), + Chain(UnGraphChainFrame), +} + +impl FrameBuild for UnPolicyFrame { + fn from_block(frame: BlockFrame) -> Self { + UnPolicyFrame::Block(frame) + } + fn from_cfg(frame: CfgFrame) -> Self { + UnPolicyFrame::Cfg(frame) + } + fn from_call(frame: CallFrame) -> Self { + UnPolicyFrame::Call(frame) + } + fn from_digraph(frame: DiGraphFrame) -> Self { + UnPolicyFrame::DiGraph(frame) + } + fn from_ungraph_entry(entry: UnGraphEntry) -> Result { + Ok(UnPolicyFrame::Chain(UnGraphChainFrame::new(entry))) + } +} + +/// The compiler-owned callable-UnGraph walker (the policy itself). +struct UnGraphChainFrame { + stage: CompileStage, + /// The callee activation — owned and freed by the awaiting `CallFrame`, + /// merely used here. + index: EnvIndex, + graph: UnGraph, + /// Entry arguments awaiting the boundary-port binding on the first step. + pending: Option>, + /// The policy's schedule (canonical node enumeration order). + schedule: VecDeque, + /// The policy's output convention: the last scheduled node's results. + outputs: Vec, +} + +impl UnGraphChainFrame { + fn new(entry: UnGraphEntry) -> Self { + Self { + stage: entry.stage, + index: entry.index, + graph: entry.graph, + pending: Some(entry.args), + schedule: VecDeque::new(), + outputs: Vec::new(), + } + } + + fn step( + mut self, + interp: &mut UnEngine<'_>, + ) -> Result>, TestError> { + // First step: bind the boundary ports and fix the policy's schedule + // and output convention from the graph's structure. + if let Some(args) = self.pending.take() { + let info = interp + .pipeline() + .stage(self.stage) + .ok_or(InterpreterError::MissingStage(self.stage))?; + let graph_info = self + .graph + .get_info(info) + .ok_or(InterpreterError::Custom("ungraph has no info"))?; + if graph_info.ports().len() != args.len() { + return Err(TestError::Core(InterpreterError::ProductArityMismatch { + expected: graph_info.ports().len(), + actual: args.len(), + })); + } + let ports: Vec<_> = graph_info.ports().to_vec(); + let nodes: Vec = graph_info.graph().node_weights().copied().collect(); + let last = *nodes + .last() + .ok_or(InterpreterError::Custom("empty ungraph body"))?; + self.outputs = last + .definition(info) + .results() + .map(|result| SSAValue::from(*result)) + .collect(); + self.schedule = nodes.into(); + for (port, value) in ports.into_iter().zip(args) { + interp.env_write(self.index, SSAValue::from(port), value)?; + } + return Ok(FrameEffect::Continue(UnPolicyFrame::Chain(self))); + } + + match self.schedule.pop_front() { + Some(statement) => match interp.run_statement(self.stage, statement, self.index)? { + SparseForwardEffect::Next => Ok(FrameEffect::Continue(UnPolicyFrame::Chain(self))), + _ => Err(TestError::Core(InterpreterError::Custom( + "the chain policy supports only ordinary dataflow nodes", + ))), + }, + // Natural completion: the policy's outputs become the call's + // returned values (the awaiting CallFrame accepts `Finished`). + None => { + let values: Product = self + .outputs + .iter() + .map(|&value| interp.env_read(self.index, value)) + .collect::>()?; + Ok(FrameEffect::Complete(Completion::Finished(values))) + } + } + } +} + +type UnEngine<'ir> = ConcreteInterpreter<'ir, L, i64, TestError, SameStageLinker, UnPolicyFrame>; + +impl<'ir> Frame> for UnPolicyFrame { + type Completion = Completion; + + fn step( + self, + interp: &mut UnEngine<'ir>, + ) -> Result, TestError> { + match self { + UnPolicyFrame::Block(frame) => frame.step_into::, Self>(interp), + UnPolicyFrame::Cfg(frame) => frame.step_into::, Self>(interp), + UnPolicyFrame::Call(frame) => frame.step_into::, Self>(interp), + UnPolicyFrame::DiGraph(frame) => frame.step_into::, Self>(interp), + UnPolicyFrame::Chain(frame) => frame.step(interp), + } + } + + fn resume_done( + self, + _interp: &mut UnEngine<'ir>, + ) -> Result, TestError> { + match self { + UnPolicyFrame::Block(frame) => Ok(frame.resume_done_into::()), + UnPolicyFrame::Cfg(frame) => Ok(frame.resume_done_into::()), + UnPolicyFrame::Call(frame) => frame.resume_done_into::().map_err(TestError::from), + UnPolicyFrame::DiGraph(frame) => Ok(frame.resume_done_into::()), + UnPolicyFrame::Chain(_) => Err(TestError::Core(InterpreterError::Custom( + "the chain policy pushes no children", + ))), + } + } + + fn resume( + self, + completion: Self::Completion, + interp: &mut UnEngine<'ir>, + ) -> Result, TestError> { + match self { + UnPolicyFrame::Block(frame) => { + frame.resume_into::, Self>(completion, interp) + } + UnPolicyFrame::Cfg(frame) => { + frame.resume_into::, Self>(completion, interp) + } + UnPolicyFrame::Call(frame) => { + frame.resume_into::, Self>(completion, interp) + } + UnPolicyFrame::DiGraph(frame) => { + frame.resume_into::, Self>(completion, interp) + } + UnPolicyFrame::Chain(_) => Err(TestError::Core(InterpreterError::Custom( + "the chain policy pushes no children", + ))), + } + } +} + +const UNGRAPH_PROGRAM: &str = r#" +stage @test fn @usq(i64, i64) -> i64; +stage @test fn @main() -> i64; + +specialize @test fn @usq(i64, i64) -> i64 ungraph ^u0(%x: i64, %y: i64) { + %s = add %x, %y -> i64; + %t = mul %s, %s -> i64; +} + +specialize @test fn @main() -> i64 { + ^entry() { + %a = constant 2 -> i64; + %b = constant 3 -> i64; + %r = call.named @usq(%a, %b) -> i64; + ret %r; + } +} +"#; + +/// A language *with* an UnGraph policy: the call goes caller → `CallFrame` → +/// the compiler's `UnGraphChainFrame`. The `CallFrame` still owns the callee +/// activation and return bookkeeping; only the walker construction was +/// delegated. +#[test] +fn custom_ungraph_policy_is_callable() { + let pipeline = parse(UNGRAPH_PROGRAM); + let mut interp: UnEngine<'_> = ConcreteInterpreter::new(&pipeline); + let result: i64 = + expect_single::(interp.call_by_name("test", "main", []).unwrap()).unwrap(); + // (2 + 3)^2 = 25, via the policy's chain schedule and last-node outputs. + assert_eq!(result, 25); +} + +// =========================================================================== +// 8. UnGraph without a policy: a clear no-default-walker error. +// =========================================================================== + +/// The standard frames supply no UnGraph traversal, so calling an +/// UnGraph-bodied function reports `NoDefaultWalker` instead of inventing a +/// node order. +#[test] +fn ungraph_without_policy_reports_no_default_walker() { + let pipeline = parse(UNGRAPH_PROGRAM); + let error = run(&pipeline, "main", &[]).unwrap_err(); + assert!( + matches!( + error, + TestError::Core(InterpreterError::NoDefaultWalker(Body::UnGraph(_))) + ), + "expected NoDefaultWalker(UnGraph), got {error:?}" + ); } From 969914e910ba38cb39e09e05a4817ba9de9ecdbc Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Thu, 16 Jul 2026 13:33:42 -0400 Subject: [PATCH 5/6] refactor: simplify liveness analysis by removing demand pre-pass --- crates/kirin-liveness/src/lib.rs | 35 ++++++++-- docs/design/interpreter/index.md | 3 +- example/toy-lang/src/interpreter/mod.rs | 34 +++------ example/toy-lang/src/interpreter/tests.rs | 84 ++++++++++++++++------- example/toy-lang/src/main.rs | 7 +- example/toy-lang/tests/e2e.rs | 25 +++++++ 6 files changed, 132 insertions(+), 56 deletions(-) diff --git a/crates/kirin-liveness/src/lib.rs b/crates/kirin-liveness/src/lib.rs index 59ee1bdfe8..30bd1d6344 100644 --- a/crates/kirin-liveness/src/lib.rs +++ b/crates/kirin-liveness/src/lib.rs @@ -26,7 +26,8 @@ pub use live::{Live, LiveSet}; pub use result::{DemandResult, DenseLivenessResult}; use kirin_interpreter::{ - Body, DenseBackwardInterpreter, DenseBackwardTransfer, InterpDispatch, InterpreterError, + Body, DenseBackwardCompletion, DenseBackwardDriver, DenseBackwardInterpreter, + DenseBackwardTransfer, DenseFrameBuild, Frame, InterpDispatch, InterpreterError, SparseBackwardDriver, SparseBackwardInterpreter, StageQuery, StandardDenseBackwardFrame, }; use kirin_ir::{CompileStage, Pipeline, StageMeta}; @@ -59,9 +60,9 @@ where } /// Run classic per-point liveness (dense backward) over `body` in `stage`, -/// with the standard (structured-control-free) frames. Languages with scf -/// compose [`DenseLiveness`] with their own frame type and build the result -/// via [`DenseLivenessResult::from_engine`]. +/// with the standard (structured-control-free) frames. Languages with +/// structured dialects select their total frame through +/// [`analyze_dense_with_frame`] instead. pub fn analyze_dense<'ir, S>( pipeline: &'ir Pipeline, stage: CompileStage, @@ -79,9 +80,33 @@ where StandardDenseBackwardFrame, >, >, +{ + analyze_dense_with_frame::>( + pipeline, stage, body, + ) +} + +/// Run classic per-point liveness (dense backward) over `body` in `stage` +/// with a caller-selected total frame type `F` — the entry point for +/// languages whose structured dialects require a language-specific total +/// frame. The analysis consumes the finalized IR directly; it neither +/// requires nor computes a demand ([`DemandResult`]) pre-pass. +pub fn analyze_dense_with_frame<'ir, S, F>( + pipeline: &'ir Pipeline, + stage: CompileStage, + body: impl Into, +) -> Result +where + S: StageMeta + + StageQuery + + InterpDispatch>, + F: Frame< + DenseBackwardDriver<'ir, S, LiveSet, InterpreterError, F>, + Completion = DenseBackwardCompletion, + > + DenseFrameBuild, { let body = body.into(); - let mut engine = DenseLiveness::::new(pipeline); + let mut engine = DenseLiveness::::new(pipeline); engine.analyze(stage, body)?; DenseLivenessResult::from_engine(&mut engine, stage, body) } diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index 4ec735c6fb..96ef9fd20d 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -563,7 +563,8 @@ and terminating on unknown inputs (both fold to `Top`). Runnable as kill/gen transfer (`gen_uses_kill_defs`, purity-irrelevant). Block owners converge boundary summaries; `live_before`/`live_after` are reconstructed per point on demand (never persisted by the fixpoint); scf owns dense - frames (arm-join, loop fixpoint). + frames (arm-join, loop fixpoint). Classic liveness consumes finalized IR + directly and does not require a sparse-demand pre-pass. Strong per-point sets are the composition `dense ∩ demanded`, not a third analysis. Because the `Semantics` parameter distinguishes impls, one dialect carries all three rules at once, as every shipped dialect demonstrates. diff --git a/example/toy-lang/src/interpreter/mod.rs b/example/toy-lang/src/interpreter/mod.rs index 07b6974516..eebd6e6219 100644 --- a/example/toy-lang/src/interpreter/mod.rs +++ b/example/toy-lang/src/interpreter/mod.rs @@ -22,7 +22,7 @@ use kirin_interpreter::engine::{ CallContext, ConcreteInterpreter, CrossStageLinker, SameStageLinker, SparseForwardInterpreter, expect_single, }; -use kirin_liveness::{DemandResult, DenseLivenessResult, LiveSet}; +use kirin_liveness::{DenseLivenessResult, LiveSet}; use crate::language::{HighLevel, LowLevel}; use crate::stage::Stage; @@ -47,18 +47,6 @@ pub type ToyConstProp<'ir, Lk = CrossStageLinker> = SparseForwardInterpreter< ToyAbstractFrame, >; -/// Classic per-point liveness (dense backward) over toy programs, with a -/// frame type embedding the SCF dense frames (arm join + loop fixpoint). -/// Strong liveness needs no composition — the sparse demand engine has no -/// frames (loop-carried demand converges on the value worklist), so -/// [`kirin_liveness::analyze_demand`] applies directly. -pub type ToyDenseLiveness<'ir> = kirin_liveness::DenseLiveness< - 'ir, - Stage, - InterpreterError, - ToyDenseBackwardFrame, ->; - /// Execute `function_name` starting at `stage_name`, following calls across /// language boundaries. pub fn run_i64( @@ -188,18 +176,18 @@ fn function_cfg( Ok((stage_id, cfg)) } -/// Run both liveness analyses over `function_name`'s body at `stage_name`: -/// strong liveness (the sparse demanded set — DCE-grade) and classic per-point -/// liveness (dense block-boundary sets — regalloc-grade). -pub fn analyze_liveness( +/// Run classic per-point liveness (dense backward — regalloc-grade +/// block-boundary and per-statement sets) over `function_name`'s body at +/// `stage_name`. Consumes the finalized IR directly; strong demand +/// ([`kirin_liveness::analyze_demand`]) is an independent analysis and is +/// not involved. +pub fn analyze_classic_liveness( pipeline: &Pipeline, stage_name: &str, function_name: &str, -) -> Result<(DemandResult, DenseLivenessResult), InterpreterError> { +) -> Result { let (stage, cfg) = function_cfg(pipeline, stage_name, function_name)?; - let demand = kirin_liveness::analyze_demand(pipeline, stage, cfg)?; - let mut engine: ToyDenseLiveness<'_> = ToyDenseLiveness::new(pipeline); - engine.analyze(stage, cfg)?; - let dense = DenseLivenessResult::from_engine(&mut engine, stage, cfg)?; - Ok((demand, dense)) + kirin_liveness::analyze_dense_with_frame::<_, ToyDenseBackwardFrame>( + pipeline, stage, cfg, + ) } diff --git a/example/toy-lang/src/interpreter/tests.rs b/example/toy-lang/src/interpreter/tests.rs index b4530db1a7..d3d4810874 100644 --- a/example/toy-lang/src/interpreter/tests.rs +++ b/example/toy-lang/src/interpreter/tests.rs @@ -1255,32 +1255,35 @@ specialize @source fn @main(i64, i64) -> i64 { } // =========================================================================== -// Classic (dense, per-point) liveness through scf's dialect-owned dense -// frames: arm-join for `scf.if`, the loop-carried fixpoint for `scf.for`, and -// per-point reconstruction inside structured bodies. +// Classic (dense, per-point) liveness through the toy language's total dense +// frame: arm-join for `scf.if`, the loop-carried fixpoint for `scf.for`, and +// per-point reconstruction inside structured bodies. Runs on the finalized IR +// alone — no demand pre-pass. // =========================================================================== mod dense { use kirin::prelude::{CFG, CompileStage, Pipeline, SSAValue, Statement}; use kirin_arith::{Arith, ArithValue}; - use kirin_liveness::{DenseLivenessResult, LiveSet, analyze_demand}; + use kirin_interpreter::InterpreterError; + use kirin_liveness::{DenseLivenessResult, LiveSet}; use super::demand::{FOR_CARRIED_DEMAND, IF_DEAD_RESULT}; use super::demand::{constant_result, entry_params, find_value, parse, source_cfg}; - use crate::interpreter::ToyDenseLiveness; + use crate::interpreter::ToyDenseBackwardFrame; use crate::language::HighLevel; use crate::stage::Stage; - /// Run classic dense liveness with the toy total frame (scf frames - /// embedded). + /// Run classic dense liveness with the toy total frame. fn analyze_dense_toy( pipeline: &Pipeline, stage: CompileStage, cfg: CFG, ) -> DenseLivenessResult { - let mut engine: ToyDenseLiveness<'_> = ToyDenseLiveness::new(pipeline); - engine.analyze(stage, cfg).expect("analysis succeeds"); - DenseLivenessResult::from_engine(&mut engine, stage, cfg).expect("reconstruction succeeds") + kirin_liveness::analyze_dense_with_frame::< + _, + ToyDenseBackwardFrame, + >(pipeline, stage, cfg) + .expect("analysis succeeds") } /// The statement whose definition matches `select` (anywhere in the @@ -1309,15 +1312,15 @@ mod dense { values.iter().copied().collect() } - /// Per-point sets inside an `scf.if` arm follow classic semantics (the - /// yield's operand is live after its def even though the result is dead), - /// and intersecting with the demand set recovers the strong view. + /// Per-point sets inside an `scf.if` arm follow classic semantics: the + /// yield's operand is live after its def even though the result is dead. + /// (The strong view is the `dense ∩ demanded` composition, covered in + /// kirin-liveness — no demand pass runs here.) #[test] fn dense_per_point_inside_scf_if_arm() { let pipeline = parse(IF_DEAD_RESULT); let (stage, cfg) = source_cfg(&pipeline, "if_dead"); let dense = analyze_dense_toy(&pipeline, stage, cfg); - let demand = analyze_demand(&pipeline, stage, cfg).expect("demand succeeds"); let cond = entry_params(&pipeline, cfg)[0]; let a = constant_result(&pipeline, cfg, 1); @@ -1331,20 +1334,55 @@ mod dense { assert_eq!(dense.live_after(a_const), Some(&live_set(&[cond, a]))); assert_eq!(dense.live_before(a_const), Some(&live_set(&[cond]))); - // The if's own points: its dead result is live after it (classic - // records what the walk saw: nothing uses it, so it is NOT live), and - // before it only the condition survives the arm join. + // The if's dead result is not live after it because nothing uses it; + // before it, only the condition survives the arm join. let if_stmt = find_statement(&pipeline, cfg, |definition| { matches!(definition, HighLevel::Structured(_)) }); assert_eq!(dense.live_before(if_stmt), Some(&live_set(&[cond]))); + } + + const IF_ARMS_DIFFERENT_USES: &str = r#" +stage @source fn @if_arms(i64, i64, i64) -> i64; + +specialize @source fn @if_arms(i64, i64, i64) -> i64 { + ^entry(%cond: i64, %x: i64, %y: i64) { + %r = if %cond then ^then() { + yield %x; + } else ^else() { + yield %y; + } -> i64; + ret %r; + } +} +"#; + + /// The scf.if liveness frame walks BOTH arms backward and joins their + /// live-entry states: the arms use different SSA values (%x vs %y), so + /// the state before the `if` must contain the condition and both. + #[test] + fn dense_scf_if_joins_both_arm_entries() { + let pipeline = parse(IF_ARMS_DIFFERENT_USES); + let (stage, cfg) = source_cfg(&pipeline, "if_arms"); + let dense = analyze_dense_toy(&pipeline, stage, cfg); + + let params = entry_params(&pipeline, cfg); + let (cond, x, y) = (params[0], params[1], params[2]); + let if_stmt = find_statement(&pipeline, cfg, |definition| { + matches!(definition, HighLevel::Structured(_)) + }); + let r = find_value(&pipeline, cfg, |definition| match definition { + HighLevel::Structured(_) => { + use kirin::prelude::HasResults; + definition.results().next().map(|v| SSAValue::from(*v)) + } + _ => None, + }); - // Strong per-point view: %a is classically live after its def but not - // demanded (the if result is dead), so the composition drops it. - let strong = dense - .strong_live_after(a_const, &demand) - .expect("point reconstructed"); - assert_eq!(strong, live_set(&[cond])); + // After the if only its result matters; before it, the then-arm + // contributed %x, the else-arm %y, and the rule genned %cond. + assert_eq!(dense.live_after(if_stmt), Some(&live_set(&[r]))); + assert_eq!(dense.live_before(if_stmt), Some(&live_set(&[cond, x, y]))); } /// The scf.for dense frame iterates the body walk to the loop-carried diff --git a/example/toy-lang/src/main.rs b/example/toy-lang/src/main.rs index 069a4631de..1a39465b41 100644 --- a/example/toy-lang/src/main.rs +++ b/example/toy-lang/src/main.rs @@ -41,8 +41,8 @@ enum Command { /// Run constant propagation instead of concrete execution. #[arg(long)] constprop: bool, - /// Run liveness analysis (strong demand + classic per-point) instead - /// of concrete execution. + /// Run classic per-program-point liveness analysis (dense backward) + /// instead of concrete execution. #[arg(long)] liveness: bool, /// Restrict execution to the entry stage's language; reject calls @@ -108,8 +108,7 @@ fn run_program( } if liveness { - let (demand, dense) = interpreter::analyze_liveness(&pipeline, stage_name, func_name)?; - println!("demanded: {:?}", demand.demanded()); + let dense = interpreter::analyze_classic_liveness(&pipeline, stage_name, func_name)?; let mut boundaries: Vec<_> = dense .blocks() .map(|(block, live_in, live_out)| format!("{block:?}: in={live_in:?} out={live_out:?}")) diff --git a/example/toy-lang/tests/e2e.rs b/example/toy-lang/tests/e2e.rs index 4fc3f9373f..2156364027 100644 --- a/example/toy-lang/tests/e2e.rs +++ b/example/toy-lang/tests/e2e.rs @@ -177,3 +177,28 @@ fn test_run_missing_stage() { .assert() .failure(); } + +#[test] +fn test_liveness_prints_dense_sets_without_demand() { + // `--liveness` runs exactly one analysis: classic dense-backward + // per-point liveness. It prints block boundary sets and must NOT run or + // print the independent strong-demand analysis. + toy_lang() + .args([ + "run", + "programs/branching.kirin", + "--stage", + "source", + "--function", + "abs", + "--liveness", + ]) + .current_dir(env!("CARGO_MANIFEST_DIR")) + .assert() + .success() + .stdout( + predicate::str::contains("in=") + .and(predicate::str::contains("out=")) + .and(predicate::str::contains("demanded:").not()), + ); +} From 31bd51175e723196021708c246426e392e1880bc Mon Sep 17 00:00:00 2001 From: Dennis Liew <48105496+zhenrongliew@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:19:04 -0400 Subject: [PATCH 6/6] Fixed SSAInfo.uses def-use population at finalize. (#687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builders set `SSAInfo.kind` (use→def) but nobody ever populated `SSAInfo.uses` (def→use). Both finalize paths just copied an always-empty vec through. So finalized IR shipped an empty def-use. - `StageInfo::rebuild_use_index()`: clears every live value's `uses,` then scans each live statement's operands (`HasArguments` order) and records one `Use { stmt, operand_index } `on the value each slot reads. Idempotent, so the future rewriter can re-run it. Skips deleted statements/tombstoned SSAs. - both `finalize()` and `finalize_unchecked()` call it before returning. - `Use` gains `new()` + `stmt()/operand_index()` accessors, `Copy`, and serde parity with its container `SSAInfo`. - `rebuild_use_index` now scan over nodes.digraphs, pushing a `DiGraphYield { graph, index }` into `SSAInfo.uses`. ```rust pub enum Use { StatementOperand { stmt: Statement, index: usize }, DiGraphYield { graph: DiGraph, index: usize }, } ``` --- crates/kirin-ir/src/builder/stage_info.rs | 12 +++-- crates/kirin-ir/src/lib.rs | 2 +- crates/kirin-ir/src/node/mod.rs | 2 +- crates/kirin-ir/src/node/ssa.rs | 31 +++++++++-- crates/kirin-ir/src/stage/info.rs | 66 ++++++++++++++++++++++- crates/kirin-ir/tests/builder_block.rs | 52 ++++++++++++++++++ crates/kirin-ir/tests/builder_graph.rs | 33 ++++++++++++ 7 files changed, 186 insertions(+), 12 deletions(-) diff --git a/crates/kirin-ir/src/builder/stage_info.rs b/crates/kirin-ir/src/builder/stage_info.rs index d121eb2a4a..3393af5da4 100644 --- a/crates/kirin-ir/src/builder/stage_info.rs +++ b/crates/kirin-ir/src/builder/stage_info.rs @@ -236,10 +236,12 @@ impl BuilderStageInfo { }, |_info| None, ); - Ok(StageInfo { + let mut stage = StageInfo { nodes: self.nodes, ssas, - }) + }; + stage.rebuild_use_index(); + Ok(stage) } /// Convert to [`StageInfo`] without validation. @@ -279,10 +281,12 @@ impl BuilderStageInfo { // Deleted items become None tombstones — safe, no zeroed memory. |_info| None, ); - StageInfo { + let mut stage = StageInfo { nodes: self.nodes, ssas, - } + }; + stage.rebuild_use_index(); + stage } } diff --git a/crates/kirin-ir/src/lib.rs b/crates/kirin-ir/src/lib.rs index 78659f84c1..ac761e83da 100644 --- a/crates/kirin-ir/src/lib.rs +++ b/crates/kirin-ir/src/lib.rs @@ -36,7 +36,7 @@ pub use node::{ GraphInfo, LinkedList, LinkedListNode, Port, PortParent, ResolutionInfo, ResultValue, SSAInfo, SSAKind, SSAValue, SpecializedFunction, SpecializedFunctionInfo, StagedFunction, StagedFunctionInfo, StagedNamePolicy, Statement, StatementInfo, StatementParent, Successor, - Symbol, TestSSAValue, UnGraph, UnGraphExtra, UnGraphInfo, UniqueLiveSpecializationError, + Symbol, TestSSAValue, UnGraph, UnGraphExtra, UnGraphInfo, UniqueLiveSpecializationError, Use, }; pub use pipeline::Pipeline; pub use product::{HasProduct, Product}; diff --git a/crates/kirin-ir/src/node/mod.rs b/crates/kirin-ir/src/node/mod.rs index 2615537850..bc485be5c7 100644 --- a/crates/kirin-ir/src/node/mod.rs +++ b/crates/kirin-ir/src/node/mod.rs @@ -22,7 +22,7 @@ pub use linked_list::{LinkedList, LinkedListNode}; pub use port::{Port, PortParent}; pub use ssa::{ BlockArgument, BuilderKey, BuilderSSAInfo, BuilderSSAKind, DeletedSSAValue, ResolutionInfo, - ResultValue, SSAInfo, SSAKind, SSAValue, TestSSAValue, + ResultValue, SSAInfo, SSAKind, SSAValue, TestSSAValue, Use, }; pub use stmt::{Statement, StatementInfo, StatementParent}; pub use symbol::{GlobalSymbol, Symbol}; diff --git a/crates/kirin-ir/src/node/ssa.rs b/crates/kirin-ir/src/node/ssa.rs index a3e47cc600..4efe340bbb 100644 --- a/crates/kirin-ir/src/node/ssa.rs +++ b/crates/kirin-ir/src/node/ssa.rs @@ -3,6 +3,7 @@ use crate::identifier; use crate::{Dialect, Symbol}; use smallvec::SmallVec; +use super::digraph::DiGraph; use super::port::{Port, PortParent}; use super::{block::Block, stmt::Statement}; @@ -238,10 +239,32 @@ impl From> for BuilderSSAInfo { } } -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct Use { - stmt: Statement, - operand_index: usize, +/// One def-use edge: a position in the IR that reads an SSA value. +/// +/// Stored in [`SSAInfo::uses`] as the reverse index of the authoritative +/// storage. Two kinds of position read a value: +/// +/// - a **statement operand** slot (the usual case — `arith.add`'s operands, +/// CFG branch arguments, `scf.yield` values, function returns), and +/// - a **`DiGraph` body yield** — a value the graph exports across its body +/// boundary. A yield has no backing statement (it lives in +/// [`DiGraphInfo::yields`](crate::DiGraphInfo)), so it cannot be named as a +/// statement operand, but it is a genuine use. +/// +/// `UnGraph` has no analogue: its `Extra` is a list of edge *statements*, whose +/// operands are already ordinary statement-operand uses. +/// +/// Populated by +/// [`StageInfo::rebuild_use_index`](crate::StageInfo::rebuild_use_index) at +/// finalization; a mutation layer (the rewriter) must keep it in sync with +/// every operand and yield change. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Use { + /// The `index`-th operand slot of `stmt`, in `HasArguments` order. + StatementOperand { stmt: Statement, index: usize }, + /// The `index`-th yield slot of the directed graph body `graph`. + DiGraphYield { graph: DiGraph, index: usize }, } /// A lookup key for builder placeholders — resolved at build time to the real SSA value. diff --git a/crates/kirin-ir/src/stage/info.rs b/crates/kirin-ir/src/stage/info.rs index aadf8112cd..abd41b33e0 100644 --- a/crates/kirin-ir/src/stage/info.rs +++ b/crates/kirin-ir/src/stage/info.rs @@ -1,7 +1,7 @@ use std::ops::{Deref, DerefMut}; -use crate::arena::Arena; -use crate::node::ssa::SSAInfo; +use crate::arena::{Arena, Id}; +use crate::node::ssa::{SSAInfo, Use}; use crate::{BuilderStageInfo, Dialect, node::*}; use super::arenas::Arenas; @@ -117,6 +117,68 @@ impl StageInfo { &self.ssas } + /// Rebuild the def-use index ([`SSAInfo::uses`](crate::SSAInfo)) from the + /// authoritative storage. + /// + /// Clears every live value's use list, then records one [`Use`](crate::Use) + /// per position that reads a value: + /// + /// - each live statement's operands (in [`HasArguments`](crate::HasArguments) + /// order) → [`Use::StatementOperand`](crate::Use), and + /// - each live `DiGraph` body's yields (in yield order) → + /// [`Use::DiGraphYield`](crate::Use). A yield is a boundary export with no + /// backing statement, so it would otherwise be invisible to the operand + /// scan. + /// + /// The operand and yield slots are the ground truth; this list is a derived + /// reverse index over them. `UnGraph` contributes nothing: its edges are + /// statements whose operands are already covered above; graph ports and + /// block arguments are definitions, not uses. + /// + /// Idempotent — safe to re-run. Called at + /// [`finalize`](crate::BuilderStageInfo::finalize) so finalized IR ships a + /// populated index; a mutation layer must call it (or maintain the index + /// incrementally) after changing operands or yields. + pub fn rebuild_use_index(&mut self) { + let StageInfo { nodes, ssas } = self; + + for item in ssas.iter_mut() { + if let Some(info) = (**item).as_mut() { + info.uses_mut().clear(); + } + } + + for (raw, item) in nodes.statements.items.iter().enumerate() { + if item.deleted() { + continue; + } + let stmt = Statement(Id(raw)); + let operands: Vec = item.data.definition.arguments().copied().collect(); + for (index, operand) in operands.into_iter().enumerate() { + if let Some(slot) = ssas.get_mut(operand) + && let Some(info) = (**slot).as_mut() + { + info.uses_mut().push(Use::StatementOperand { stmt, index }); + } + } + } + + for (raw, item) in nodes.digraphs.items.iter().enumerate() { + if item.deleted() { + continue; + } + let graph = DiGraph::from(Id(raw)); + let yields: Vec = item.data.yields().to_vec(); + for (index, yielded) in yields.into_iter().enumerate() { + if let Some(slot) = ssas.get_mut(yielded) + && let Some(info) = (**slot).as_mut() + { + info.uses_mut().push(Use::DiGraphYield { graph, index }); + } + } + } + } + /// Temporarily convert to a [`BuilderStageInfo`] for construction, then /// convert back. /// diff --git a/crates/kirin-ir/tests/builder_block.rs b/crates/kirin-ir/tests/builder_block.rs index 8ae314b163..31729749cc 100644 --- a/crates/kirin-ir/tests/builder_block.rs +++ b/crates/kirin-ir/tests/builder_block.rs @@ -88,6 +88,58 @@ fn block_builder_substitutes_builder_block_arguments() { assert!(matches!(ssa1.kind(), SSAKind::BlockArgument(_, 1))); } +#[test] +fn finalize_populates_def_use_index() { + let mut stage = new_stage(); + + let arg0 = stage.block_argument().index(0); + let arg1 = stage.block_argument().index(1); + + // arg0 is read twice (add operand 0, use operand 0); arg1 once (add operand 1). + let add_stmt = stage + .statement() + .definition(BuilderDialect::Add(arg0, arg1)) + .new(); + let use_stmt = stage + .statement() + .definition(BuilderDialect::Use(arg0)) + .new(); + + let block = stage + .block() + .argument(TestType::I32) + .argument(TestType::I64) + .stmt(add_stmt) + .stmt(use_stmt) + .new(); + + let stage = stage.finalize().unwrap(); + let block_info = block.expect_info(&stage); + let real_arg0: SSAValue = block_info.arguments[0].into(); + let real_arg1: SSAValue = block_info.arguments[1].into(); + + let uses0 = real_arg0.get_info(&stage).unwrap().uses(); + assert_eq!(uses0.len(), 2, "arg0 is read by two statements"); + assert!(uses0.contains(&Use::StatementOperand { + stmt: add_stmt, + index: 0 + })); + assert!(uses0.contains(&Use::StatementOperand { + stmt: use_stmt, + index: 0 + })); + + let uses1 = real_arg1.get_info(&stage).unwrap().uses(); + assert_eq!(uses1.len(), 1, "arg1 is read only by the add"); + assert_eq!( + uses1[0], + Use::StatementOperand { + stmt: add_stmt, + index: 1 + } + ); +} + #[test] #[should_panic(expected = "is not a terminator")] fn block_builder_terminator_rejects_non_terminator() { diff --git a/crates/kirin-ir/tests/builder_graph.rs b/crates/kirin-ir/tests/builder_graph.rs index feba59349b..ef71dd8be5 100644 --- a/crates/kirin-ir/tests/builder_graph.rs +++ b/crates/kirin-ir/tests/builder_graph.rs @@ -34,6 +34,39 @@ fn digraph_builder_two_node_dag() { assert_eq!(*s1.parent(&stage), Some(StatementParent::DiGraph(dg))); } +#[test] +fn finalize_indexes_digraph_yields_as_uses() { + let mut stage = new_stage(); + + // s0 produces %r; the graph yields %r. No statement reads %r, so its only + // use is the boundary yield — invisible to an operand-only scan. + let s0 = stage.statement().definition(BuilderDialect::Nop).new(); + let result_ssa = stage + .ssa() + .ty(TestType::I32) + .kind(BuilderSSAKind::Result(s0, 0)) + .new(); + + let dg = stage + .digraph() + .node(s0) + .yield_value(result_ssa) + .name("yielder") + .new(); + + let stage = stage.finalize().unwrap(); + + let uses = result_ssa.get_info(&stage).unwrap().uses(); + assert_eq!(uses.len(), 1, "%r is used only by the graph yield"); + assert_eq!( + uses[0], + Use::DiGraphYield { + graph: dg, + index: 0 + } + ); +} + #[test] fn digraph_builder_port_and_capture_creation() { let mut stage = new_stage();