From d6040c3850a2f8b857c1df2df7b92918639de7e9 Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Mon, 17 Aug 2026 12:01:37 -0400 Subject: [PATCH 1/4] added Symbol entry points for forward interpreter. Renamed function body to be function definition. Actual body terminology remains unchanged for CFG, Block, graphs, Function.body, Lambda.body, and CallableBody.body. --- AGENTS.md | 2 +- .../kirin-chumsky/src/function_text/error.rs | 6 +- .../src/function_text/parse_text.rs | 34 ++++---- .../kirin-chumsky/src/function_text/syntax.rs | 18 ++--- .../kirin-chumsky/src/function_text/tests.rs | 46 +++++------ crates/kirin-chumsky/src/tests.rs | 10 +-- crates/kirin-derive-chumsky/src/format.rs | 2 +- .../src/interp_dispatch.rs | 4 +- crates/kirin-derive-ir/src/lib.rs | 4 +- .../src/parse_dispatch.rs | 8 +- ...sts__parse_dispatch_duplicate_dialect.snap | 9 +-- ...__tests__parse_dispatch_multi_dialect.snap | 9 +-- .../src/{body.rs => function.rs} | 2 +- crates/kirin-function/src/lib.rs | 7 +- crates/kirin-interpreter/src/core/dispatch.rs | 8 +- crates/kirin-interpreter/src/core/effect.rs | 6 ++ crates/kirin-interpreter/src/core/frame.rs | 2 +- crates/kirin-interpreter/src/core/linker.rs | 8 +- crates/kirin-interpreter/src/core/query.rs | 28 ++++--- .../src/engines/concrete/frames/call_frame.rs | 2 +- .../src/engines/concrete/interp.rs | 31 +++++++- .../src/engines/sparse_forward/interp.rs | 46 +++++++---- crates/kirin-ir/src/builder/error.rs | 4 +- crates/kirin-ir/src/builder/redefine.rs | 2 +- crates/kirin-ir/src/builder/staged.rs | 6 +- crates/kirin-ir/src/language.rs | 2 +- crates/kirin-ir/src/node/function/mod.rs | 2 +- .../kirin-ir/src/node/function/specialized.rs | 19 ++--- crates/kirin-ir/src/pipeline.rs | 8 +- .../kirin-ir/src/signature/has_signature.rs | 10 +-- crates/kirin-ir/src/stage/info.rs | 8 +- crates/kirin-ir/tests/builder_staged.rs | 49 ++++++------ crates/kirin-liveness/tests/cfg.rs | 6 +- .../src/document/ir_render.rs | 10 +-- .../kirin-prettyless/src/tests/edge_cases.rs | 22 ++++-- crates/kirin-prettyless/src/tests/impls.rs | 2 +- crates/kirin-prettyless/src/tests/mod.rs | 2 +- crates/kirin-prettyless/src/tests/pipeline.rs | 18 ++++- .../src/tests/sprint_with_globals.rs | 2 +- docs/design/formalism/syntax.md | 2 +- docs/design/interpreter/index.md | 2 +- example/toy-lang/src/interpreter/mod.rs | 22 +++--- example/toy-lang/src/interpreter/tests.rs | 10 +-- example/toy-qc/src/circuit.rs | 2 +- example/toy-qc/src/zx.rs | 2 +- tests/body_kinds.rs | 77 ++++++++++++++++++- tests/frame_engine_capabilities.rs | 2 +- .../roundtrip/composable_existing_dialects.rs | 4 +- tests/roundtrip/digraph.rs | 20 ++--- tests/simple.rs | 2 +- 50 files changed, 382 insertions(+), 227 deletions(-) rename crates/kirin-function/src/{body.rs => function.rs} (89%) diff --git a/AGENTS.md b/AGENTS.md index 2c7d33c530..89a0f1f7df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -153,7 +153,7 @@ For user-defined dialects not in this table, ask the user for domain context dur - **SCF is the example**: `scf.if` → `kirin_scf::ScfIfFrame` (concrete) / `AbstractScfIfFrame` (abstract); `scf.for` → `ScfForFrame` / `AbstractScfForFrame`. Each is selected 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 includes the corresponding SCF continuations in its private stack-item composition and owns the narrow `From` conversions. SCF frames return themselves for local continuation and require only conversions for the child walkers they push; neither the dialect nor a member continuation constructs an enclosing enum variant. See `example/toy-lang`'s private concrete and dense stack-item enums and its current `ToyAbstractFrame` composition. (Future structured dialects follow the same ownership rule; only the existing SCF operations 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. +- **Calling conventions are linkers**: `Linker` resolves `Callee` to a `(stage, specialization, definition)` 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**: all frame-using engines share one driver loop, `drive_frames` (`core/frame.rs`), over the direction-neutral `Frame` protocol — pop the top stack item, call `step_into`, and apply its `FrameEffect`, while owning no representation traversal itself. A reusable *member continuation* returns `Self` for `Continue` and `Push.parent`; the generic currently named `F` is only the configured representation of a differently typed pushed child. A private closed *frame stack item* enum is the composition root: it owns `From` conversions, exhaustively dispatches to members, and uses `FrameEffect::map_next` to wrap returned member state. `drive_frames` sees only the homogeneous stack-item type and requires it to use itself as its child representation. `FrameEngine` remains the minimal anchor (just a total `Error`), so this protocol is independent of forward evaluation. `ConcreteInterpreter` hides the framework-default concrete stack item; a language with additional continuations wraps `ConcreteInterpreterCore<.., FrameStackItem>` behind its own API. Sparse-forward currently exposes `StandardAbstractFrame` as its default composition pending the separate facade/core review. Dense backward uses `DenseBlockFrame` directly by default and a language-private stack-item enum when structured continuations coexist. Sparse backward's `DemandFrame` is already its complete homogeneous composition because it never pushes a differently typed child. Analysis crates remain a lattice plus an engine/policy/composition choice (see `kirin-constprop` and `kirin-liveness`). diff --git a/crates/kirin-chumsky/src/function_text/error.rs b/crates/kirin-chumsky/src/function_text/error.rs index a7fef9087d..b347437c23 100644 --- a/crates/kirin-chumsky/src/function_text/error.rs +++ b/crates/kirin-chumsky/src/function_text/error.rs @@ -9,7 +9,7 @@ pub enum FunctionParseErrorKind { UnknownStage, InconsistentFunctionName, MissingStageDeclaration, - BodyParseFailed, + DefinitionParseFailed, EmitFailed, } @@ -24,7 +24,9 @@ impl Display for FunctionParseErrorKind { FunctionParseErrorKind::MissingStageDeclaration => { write!(f, "missing stage declaration") } - FunctionParseErrorKind::BodyParseFailed => write!(f, "function body parse failed"), + FunctionParseErrorKind::DefinitionParseFailed => { + write!(f, "function definition parse failed") + } FunctionParseErrorKind::EmitFailed => write!(f, "IR emission failed"), } } diff --git a/crates/kirin-chumsky/src/function_text/parse_text.rs b/crates/kirin-chumsky/src/function_text/parse_text.rs index 44edf13e96..4bf1c3d0f0 100644 --- a/crates/kirin-chumsky/src/function_text/parse_text.rs +++ b/crates/kirin-chumsky/src/function_text/parse_text.rs @@ -15,10 +15,10 @@ //! - collect `(stage, function) -> staged_function` mappings; //! - record offsets of `specialize` declarations for pass 2. //! -//! 2. **Pass 2 (specialize bodies)** +//! 2. **Pass 2 (specialization definitions)** //! - re-parse only the previously recorded `specialize` declarations; //! - resolve the target staged function from the pass-1 lookup; -//! - emit specialization bodies into the resolved stage dialect. +//! - emit specialization definitions into the resolved stage dialect. //! //! This separation guarantees that specialization emission sees a complete //! staged-function header set, which keeps behavior deterministic even when @@ -27,7 +27,7 @@ //! ## Why stage dispatch is central //! //! A pipeline can contain different dialects per stage (for example stage `A` -//! with `FunctionBody`, stage `B` with `LowerBody`). The parser does not guess +//! with `FunctionDefinition`, stage `B` with `LowerDefinition`). The parser does not guess //! which dialect to use from text alone. Instead it: //! //! - resolves/creates the stage symbol first (`@A`, `@B`, ...); @@ -39,7 +39,7 @@ //! //! ## Illustrative examples //! -//! Same-stage header + body: +//! Same-stage header + definition: //! //! ```text //! stage @A fn @foo(()) -> (); @@ -275,7 +275,7 @@ where let Declaration::Specialize { stage: _stage_sym, - body_span, + definition_span, span, } = declaration else { @@ -286,7 +286,7 @@ where )); }; - let body_text = &ctx.src[body_span.start..body_span.end]; + let definition_text = &ctx.src[definition_span.start..definition_span.end]; // Use the function name from parse_declaration_head (always available), // not from the chumsky Declaration (empty for dialect-controlled format). @@ -300,7 +300,7 @@ where stage_id, &function_name, ctx.function_symbol, - body_text, + definition_text, span, &mut *ctx.function_lookup, &mut *ctx.staged_lookup, @@ -607,7 +607,7 @@ fn apply_specialize_declaration( stage_id: CompileStage, function_name: &SymbolName<'_>, function_symbol: GlobalSymbol, - body_text: &str, + definition_text: &str, span: SimpleSpan, function_lookup: &mut FxHashMap, staged_lookup: &mut FxHashMap, @@ -617,14 +617,14 @@ where L: Dialect + ParseEmit + kirin_ir::HasSignature, L::Type: kirin_ir::Placeholder, { - // Parse and emit the body first — we need it to extract signature if needed - let body_statement = stage + // Parse and emit the definition first — we need it to extract the signature. + let definition = stage .with_builder(|builder| { let mut emit_ctx = EmitContext::new(builder); - L::parse_and_emit(body_text, &mut emit_ctx).map_err(|err| { + L::parse_and_emit(definition_text, &mut emit_ctx).map_err(|err| { let (kind, message) = match &err { crate::ChumskyError::Parse(errs) => ( - FunctionParseErrorKind::BodyParseFailed, + FunctionParseErrorKind::DefinitionParseFailed, errs.iter() .map(|e| e.to_string()) .collect::>() @@ -645,11 +645,11 @@ where ) })?; - // Get signature from HasSignature on the body statement. + // Get the signature from HasSignature on the definition statement. // The Signature field is populated by the statement parser from format string elements. - let def = body_statement.expect_info(stage).definition(); - let signature = def.signature().unwrap_or_else(|| { - // Fallback: create a placeholder signature if the body type + let definition_value = definition.expect_info(stage).definition(); + let signature = definition_value.signature().unwrap_or_else(|| { + // Fallback: create a placeholder signature if the definition type // doesn't carry one (e.g., no Signature field). kirin_ir::Signature::placeholder() }); @@ -673,7 +673,7 @@ where .specialize() .staged_func(staged_function) .signature(signature.clone()) - .body(body_statement) + .definition(definition) .new() .map_err(|err| { FunctionParseError::new( diff --git a/crates/kirin-chumsky/src/function_text/syntax.rs b/crates/kirin-chumsky/src/function_text/syntax.rs index 94110b3bfe..f89ff37a9d 100644 --- a/crates/kirin-chumsky/src/function_text/syntax.rs +++ b/crates/kirin-chumsky/src/function_text/syntax.rs @@ -24,8 +24,8 @@ pub(super) enum Declaration<'src, T> { Stage(Header<'src, T>), Specialize { stage: SymbolName<'src>, - /// Span of the body portion (from keyword through closing `}`). - body_span: SimpleSpan, + /// Span of the definition (from keyword through closing `}`). + definition_span: SimpleSpan, /// Span of the entire specialize declaration. span: SimpleSpan, }, @@ -69,11 +69,11 @@ where .labelled("function signature") } -/// Body span scanner. Matches an optional keyword prefix (e.g. `digraph`, +/// Definition span scanner. Matches an optional keyword prefix (e.g. `digraph`, /// `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>> +/// brace) through the matching closing brace. Does not parse the definition. +fn definition_span<'src, I>() -> impl Parser<'src, I, SimpleSpan, ParserError<'src>> where I: TokenInput<'src>, { @@ -88,7 +88,7 @@ where None => { return Err(Rich::custom( input.span_since(&start), - "expected '{' in body", + "expected '{' in function definition", )); } } @@ -132,10 +132,10 @@ where // The function name is extracted post-parse from EmitContext::function_name(). let specialize_decl = identifier("specialize") .ignore_then(symbol()) - .then(body_span::()) // captures from keyword (e.g. `fn`) through closing `}` - .map_with(|(stage, body_span), extra| Declaration::Specialize { + .then(definition_span::()) // captures from keyword (e.g. `fn`) through closing `}` + .map_with(|(stage, definition_span), extra| Declaration::Specialize { stage, - body_span, + definition_span, span: extra.span(), }); diff --git a/crates/kirin-chumsky/src/function_text/tests.rs b/crates/kirin-chumsky/src/function_text/tests.rs index 0d55bf6483..d601cfa2be 100644 --- a/crates/kirin-chumsky/src/function_text/tests.rs +++ b/crates/kirin-chumsky/src/function_text/tests.rs @@ -88,7 +88,7 @@ trivial_type_lattice!(I32Type, "i32", just(Token::Identifier("i32"))); #[derive(Clone, Debug, PartialEq, Eq, Hash, kirin_ir::Dialect, HasParser, PrettyPrint)] #[kirin(builders, type = UnitType, crate = kirin_ir)] #[chumsky(crate = crate, format = "fn {:name}{sig} {body}")] -struct FunctionBody { +struct FunctionDefinition { body: CFG, sig: Signature, } @@ -109,9 +109,9 @@ struct LowerBody { #[stage(crate = "kirin_ir", chumsky_crate = "crate")] enum StageBucket { #[stage(name = "A")] - Parse(StageInfo), + Parse(StageInfo), #[stage(name = "B")] - Lower(StageInfo), + Lower(StageInfo), } // --------------------------------------------------------------------------- @@ -122,7 +122,7 @@ enum StageBucket { #[stage(crate = "kirin_ir", chumsky_crate = "crate")] enum MixedStage { #[stage(name = "A")] - StageA(StageInfo), + StageA(StageInfo), #[stage(name = "B")] StageB(StageInfo), } @@ -155,7 +155,7 @@ fn parsed_names(pipeline: &Pipeline, functions: Vec) -> BTreeSet #[test] fn test_pipeline_parse_accepts_mixed_function_names() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let input = format!( "stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () {BODY} \ stage @B fn @bar(()) -> (); specialize @B fn @bar(()) -> () {BODY}" @@ -171,7 +171,7 @@ fn test_pipeline_parse_accepts_mixed_function_names() { #[test] fn test_pipeline_parse_uses_pipeline_global_table() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let input = format!("stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () {BODY}"); let parsed = pipeline.parse(&input).unwrap(); @@ -217,14 +217,14 @@ fn test_stage_enum_pipeline_parse_suggests_declared_name() { #[test] fn test_stage_requires_semicolon() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let err = pipeline.parse("stage @A fn @foo(()) -> ()").unwrap_err(); assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader); } #[test] fn test_specialize_requires_body() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let err = pipeline .parse("specialize @A fn @foo(()) -> ();") .unwrap_err(); @@ -233,7 +233,7 @@ fn test_specialize_requires_body() { #[test] fn test_global_symbol_prefix_is_required() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let err = pipeline.parse("stage 1 fn @foo(()) -> ();").unwrap_err(); assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader); } @@ -242,7 +242,7 @@ fn test_global_symbol_prefix_is_required() { fn test_specialize_without_stage_auto_creates() { // With auto-creation, specialize without a prior stage declaration // succeeds by auto-creating the staged function. - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); pipeline .add_stage() .stage(StageInfo::default()) @@ -259,17 +259,17 @@ fn test_specialize_without_stage_auto_creates() { #[test] fn test_comments_and_whitespace_are_accepted() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let input = format!( "/* stage declaration */ stage @A fn @foo(()) -> (); \ - // specialization body\n specialize @A fn @foo(()) -> () /* body */ {BODY}" + // specialization definition\n specialize @A fn @foo(()) -> () /* definition */ {BODY}" ); pipeline.parse(&input).unwrap(); } #[test] fn test_pipeline_roundtrip_print_parse_print() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let stage_a = pipeline .add_stage() .stage(StageInfo::default()) @@ -287,18 +287,18 @@ fn test_pipeline_roundtrip_print_parse_print() { pipeline.stage_mut(stage_a).unwrap().with_builder(|b| { let block = b.block().new(); let cfg = b.cfg().add_block(block).new(); - let body = FunctionBody::new(b, cfg, Signature::new(vec![], UnitType, ())); + let definition = FunctionDefinition::new(b, cfg, Signature::new(vec![], UnitType, ())); b.specialize() .staged_func(staged_function) .signature(unit_sig()) - .body(body) + .definition(definition) .new() .unwrap(); }); let rendered = function.sprint(&pipeline); - let mut parsed_pipeline: Pipeline> = Pipeline::new(); + let mut parsed_pipeline: Pipeline> = Pipeline::new(); let parsed_functions = parsed_pipeline.parse(&rendered).unwrap(); let parsed_function = parsed_functions .into_iter() @@ -355,7 +355,7 @@ fn test_pipeline_parse_uses_stage_language_dispatch() { #[test] fn test_pipeline_parse_empty_input() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let err = pipeline.parse("").unwrap_err(); assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader); assert!(err.message.contains("expected at least one declaration")); @@ -363,7 +363,7 @@ fn test_pipeline_parse_empty_input() { #[test] fn test_pipeline_parse_whitespace_only() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let err = pipeline.parse(" \n\t ").unwrap_err(); assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader); } @@ -374,7 +374,7 @@ fn test_pipeline_parse_whitespace_only() { #[test] fn test_pipeline_parse_numeric_stage_symbol_rejected() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); // Numeric tokens like `1` are not prefixed with `@`, so `stage 1` should fail let err = pipeline.parse("stage 1 fn @foo(()) -> ();").unwrap_err(); assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader); @@ -383,7 +383,7 @@ fn test_pipeline_parse_numeric_stage_symbol_rejected() { #[test] fn test_pipeline_numeric_stage_lookup_by_existing_id() { // When a stage already exists in the pipeline, @ can find it by raw ID - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let stage_id = pipeline .add_stage() .stage(StageInfo::default()) @@ -442,7 +442,7 @@ fn test_stage_suggestion_very_distant_name() { #[test] fn test_invalid_body_parse_has_source() { use std::error::Error; - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); // Valid header but invalid body tokens let err = pipeline .parse("stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () { invalid }") @@ -458,7 +458,7 @@ fn test_invalid_body_parse_has_source() { #[test] fn test_duplicate_stage_declaration_same_signature() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let input = format!( "stage @A fn @foo(()) -> (); \ stage @A fn @foo(()) -> (); \ @@ -474,7 +474,7 @@ fn test_duplicate_stage_declaration_same_signature() { #[test] fn test_invalid_declaration_keyword() { - let mut pipeline: Pipeline> = Pipeline::new(); + let mut pipeline: Pipeline> = Pipeline::new(); let err = pipeline.parse("define @A fn @foo(()) -> ();").unwrap_err(); assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader); } diff --git a/crates/kirin-chumsky/src/tests.rs b/crates/kirin-chumsky/src/tests.rs index c2cd207401..649706ef79 100644 --- a/crates/kirin-chumsky/src/tests.rs +++ b/crates/kirin-chumsky/src/tests.rs @@ -538,8 +538,8 @@ fn test_function_parse_error_kind_display() { "missing stage declaration" ); assert_eq!( - format!("{}", crate::FunctionParseErrorKind::BodyParseFailed), - "function body parse failed" + format!("{}", crate::FunctionParseErrorKind::DefinitionParseFailed), + "function definition parse failed" ); assert_eq!( format!("{}", crate::FunctionParseErrorKind::EmitFailed), @@ -559,7 +559,7 @@ fn test_function_parse_error_source() { // With source let source_err = std::io::Error::other("inner"); let err = crate::FunctionParseError::new( - crate::FunctionParseErrorKind::BodyParseFailed, + crate::FunctionParseErrorKind::DefinitionParseFailed, None, "outer", ) @@ -1117,7 +1117,7 @@ fn test_function_parse_error_all_kinds_display() { crate::FunctionParseErrorKind::UnknownStage, crate::FunctionParseErrorKind::InconsistentFunctionName, crate::FunctionParseErrorKind::MissingStageDeclaration, - crate::FunctionParseErrorKind::BodyParseFailed, + crate::FunctionParseErrorKind::DefinitionParseFailed, crate::FunctionParseErrorKind::EmitFailed, ]; let mut displays: Vec = kinds.iter().map(|k| format!("{k}")).collect(); @@ -1328,7 +1328,7 @@ fn test_function_parse_error_chained_source() { let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "not found"); let middle = crate::FunctionParseError::new( - crate::FunctionParseErrorKind::BodyParseFailed, + crate::FunctionParseErrorKind::DefinitionParseFailed, None, "body failed", ) diff --git a/crates/kirin-derive-chumsky/src/format.rs b/crates/kirin-derive-chumsky/src/format.rs index 3a5f477776..54154f87c8 100644 --- a/crates/kirin-derive-chumsky/src/format.rs +++ b/crates/kirin-derive-chumsky/src/format.rs @@ -77,7 +77,7 @@ //! // Quantum gate with multiple results: //! "$cnot {ctrl}, {tgt} -> {ctrl_out:type}, {tgt_out:type}" //! -//! // Function body with signature projections and context name: +//! // Function definition with signature projections and context name: //! "fn {:name}({sig:inputs}) -> {sig:return} ({body:ports}) captures ({body:captures}) {{ {body:body} }}" //! //! // Block field with args/body projections: diff --git a/crates/kirin-derive-interpreter/src/interp_dispatch.rs b/crates/kirin-derive-interpreter/src/interp_dispatch.rs index 7eb02631da..fcc3291822 100644 --- a/crates/kirin-derive-interpreter/src/interp_dispatch.rs +++ b/crates/kirin-derive-interpreter/src/interp_dispatch.rs @@ -87,7 +87,7 @@ pub fn generate(input: &DeriveInput) -> Result { let entry_arms = build_arms(&variants, enum_ident, |_| { quote! { #interp_crate::InterpDispatch::dispatch_function_entry( - stage_info, body, args, interp, + stage_info, definition, args, interp, ) } }); @@ -112,7 +112,7 @@ pub fn generate(input: &DeriveInput) -> Result { fn dispatch_function_entry( &self, - body: #ir_crate::Statement, + definition: #ir_crate::Statement, args: #ir_crate::Product<<__InterpI as #interp_crate::Interp>::Value>, interp: &mut __InterpI, ) -> Result< diff --git a/crates/kirin-derive-ir/src/lib.rs b/crates/kirin-derive-ir/src/lib.rs index 2886d3bcf5..1c2c12a58c 100644 --- a/crates/kirin-derive-ir/src/lib.rs +++ b/crates/kirin-derive-ir/src/lib.rs @@ -113,9 +113,9 @@ pub fn derive_stage_meta(input: TokenStream) -> TokenStream { /// #[stage(crate = "kirin_ir", chumsky_crate = "kirin_chumsky")] /// enum MixedStage { /// #[stage(name = "A")] -/// StageA(StageInfo), +/// StageA(StageInfo), /// #[stage(name = "B")] -/// StageB(StageInfo), +/// StageB(StageInfo), /// } /// ``` #[proc_macro_derive(ParseDispatch, attributes(stage))] diff --git a/crates/kirin-derive-toolkit/src/parse_dispatch.rs b/crates/kirin-derive-toolkit/src/parse_dispatch.rs index 72ebcf67a2..f8c447f4bf 100644 --- a/crates/kirin-derive-toolkit/src/parse_dispatch.rs +++ b/crates/kirin-derive-toolkit/src/parse_dispatch.rs @@ -169,9 +169,9 @@ mod tests { #[stage(crate = "kirin_ir", chumsky_crate = "kirin_chumsky")] enum MixedStage { #[stage(name = "A")] - StageA(StageInfo), + StageA(StageInfo), #[stage(name = "B")] - StageB(StageInfo), + StageB(StageInfo), } }; insta::assert_snapshot!(generate_parse_dispatch_code(input)); @@ -183,9 +183,9 @@ mod tests { #[stage(crate = "kirin_ir", chumsky_crate = "kirin_chumsky")] enum StageBucket { #[stage(name = "A")] - Parse(StageInfo), + Parse(StageInfo), #[stage(name = "B")] - Lower(StageInfo), + Lower(StageInfo), } }; insta::assert_snapshot!(generate_parse_dispatch_code(input)); diff --git a/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_duplicate_dialect.snap b/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_duplicate_dialect.snap index 47aedf9d29..2c28681bdc 100644 --- a/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_duplicate_dialect.snap +++ b/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_duplicate_dialect.snap @@ -1,6 +1,5 @@ --- source: crates/kirin-derive-toolkit/src/parse_dispatch.rs -assertion_line: 191 expression: generate_parse_dispatch_code(input) --- #[automatically_derived] @@ -15,11 +14,11 @@ impl kirin_chumsky::ParseDispatch for StageBucket { > { match self { StageBucket::Parse(stage_info) => { - kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } StageBucket::Lower(stage_info) => { - kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } } @@ -32,11 +31,11 @@ impl kirin_chumsky::ParseDispatch for StageBucket { { match self { StageBucket::Parse(stage_info) => { - kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } StageBucket::Lower(stage_info) => { - kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } } diff --git a/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_multi_dialect.snap b/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_multi_dialect.snap index ca59c440ee..0545ea675f 100644 --- a/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_multi_dialect.snap +++ b/crates/kirin-derive-toolkit/src/snapshots/kirin_derive_toolkit__parse_dispatch__tests__parse_dispatch_multi_dialect.snap @@ -1,6 +1,5 @@ --- source: crates/kirin-derive-toolkit/src/parse_dispatch.rs -assertion_line: 177 expression: generate_parse_dispatch_code(input) --- #[automatically_derived] @@ -15,11 +14,11 @@ impl kirin_chumsky::ParseDispatch for MixedStage { > { match self { MixedStage::StageA(stage_info) => { - kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } MixedStage::StageB(stage_info) => { - kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::first_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } } @@ -32,11 +31,11 @@ impl kirin_chumsky::ParseDispatch for MixedStage { { match self { MixedStage::StageA(stage_info) => { - kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } MixedStage::StageB(stage_info) => { - kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) + kirin_chumsky::second_pass_concrete::(stage_info, stage_id, ctx) .map(::core::option::Option::Some) } } diff --git a/crates/kirin-function/src/body.rs b/crates/kirin-function/src/function.rs similarity index 89% rename from crates/kirin-function/src/body.rs rename to crates/kirin-function/src/function.rs index baf0e1ecc7..00521be92b 100644 --- a/crates/kirin-function/src/body.rs +++ b/crates/kirin-function/src/function.rs @@ -1,6 +1,6 @@ use kirin::prelude::*; -/// Structural function-body statement used by function text parsing. +/// Structural function-definition statement used by function text parsing. /// /// The `sig` field stores the function's type signature (`(T, T) -> T`), /// parsed from the format string. `derive(Dialect)` generates `HasSignature` diff --git a/crates/kirin-function/src/lib.rs b/crates/kirin-function/src/lib.rs index 0dfd20a0db..8a41b1d11f 100644 --- a/crates/kirin-function/src/lib.rs +++ b/crates/kirin-function/src/lib.rs @@ -18,20 +18,17 @@ use kirin::prelude::*; use kirin_interpreter::{FunctionEntry, Interpretable}; pub mod bind; -pub mod body; pub mod call; +pub mod function; pub mod lambda; pub mod ret; pub use bind::Bind; -pub use body::Function; pub use call::{Call, CallFunction, CallLike, CallNamed, CallSpecialized, CallStaged}; +pub use function::Function; pub use lambda::Lambda; pub use ret::Return; -#[deprecated(note = "use Function")] -pub type FunctionBody = Function; - pub mod interpreter; #[cfg(test)] diff --git a/crates/kirin-interpreter/src/core/dispatch.rs b/crates/kirin-interpreter/src/core/dispatch.rs index 19f6428f87..bf2cb45934 100644 --- a/crates/kirin-interpreter/src/core/dispatch.rs +++ b/crates/kirin-interpreter/src/core/dispatch.rs @@ -51,7 +51,7 @@ pub trait InterpDispatch: StageMeta { fn dispatch_function_entry( &self, - body: Statement, + definition: Statement, args: Product, interp: &mut I, ) -> Result, I::Error>; @@ -73,11 +73,11 @@ where fn dispatch_function_entry( &self, - body: Statement, + definition: Statement, args: Product, interp: &mut I, ) -> Result, I::Error> { - let definition = body.definition(self).clone(); - definition.function_entry(args, interp) + let callable = definition.definition(self).clone(); + callable.function_entry(args, interp) } } diff --git a/crates/kirin-interpreter/src/core/effect.rs b/crates/kirin-interpreter/src/core/effect.rs index 9bdb0a01c5..03a74ace87 100644 --- a/crates/kirin-interpreter/src/core/effect.rs +++ b/crates/kirin-interpreter/src/core/effect.rs @@ -126,6 +126,12 @@ pub enum Callee { Specialized(SpecializedFunction), } +impl From for Callee { + fn from(symbol: Symbol) -> Self { + Self::Named(symbol) + } +} + /// The body a callable statement enters when invoked, plus the entry /// arguments bound to its boundary (block parameters / graph ports). /// diff --git a/crates/kirin-interpreter/src/core/frame.rs b/crates/kirin-interpreter/src/core/frame.rs index f120734be1..6b02742f43 100644 --- a/crates/kirin-interpreter/src/core/frame.rs +++ b/crates/kirin-interpreter/src/core/frame.rs @@ -340,7 +340,7 @@ pub trait CallServices: Env { fn enter_function( &mut self, stage: CompileStage, - body: Statement, + definition: Statement, args: Product, index: EnvIndex, ) -> Result, Self::Error>; diff --git a/crates/kirin-interpreter/src/core/linker.rs b/crates/kirin-interpreter/src/core/linker.rs index ea531e8a8c..24a30c63ce 100644 --- a/crates/kirin-interpreter/src/core/linker.rs +++ b/crates/kirin-interpreter/src/core/linker.rs @@ -4,12 +4,12 @@ use super::query; use crate::{Callee, InterpreterError, StageQuery}; /// A fully resolved call target: the stage to execute in, the specialization, -/// and its body statement. +/// and its callable definition statement. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct FunctionTarget { pub stage: CompileStage, pub function: SpecializedFunction, - pub body: Statement, + pub definition: Statement, } /// The calling-convention component of an engine. @@ -76,11 +76,11 @@ fn target_at_stage( Callee::Staged(staged) => query::unique_specialization(pipeline, stage, staged)?, Callee::Specialized(specialized) => specialized, }; - let body = query::function_body(pipeline, stage, specialized)?; + let definition = query::function_definition(pipeline, stage, specialized)?; Ok(FunctionTarget { stage, function: specialized, - body, + definition, }) } diff --git a/crates/kirin-interpreter/src/core/query.rs b/crates/kirin-interpreter/src/core/query.rs index 565e5ba848..401df3b985 100644 --- a/crates/kirin-interpreter/src/core/query.rs +++ b/crates/kirin-interpreter/src/core/query.rs @@ -262,10 +262,10 @@ where } } -/// Body statement of a specialized function. -pub struct FunctionBody(pub SpecializedFunction); +/// Definition statement of a specialized function. +pub struct FunctionDefinition(pub SpecializedFunction); -impl StageAction for FunctionBody +impl StageAction for FunctionDefinition where S: StageMeta + HasStageInfo, L: Dialect, @@ -281,8 +281,10 @@ where Ok(self .0 .get_info(info) - .map(|info| *info.body()) - .ok_or(InterpreterError::Custom("specialized function has no body"))) + .map(|info| *info.definition()) + .ok_or(InterpreterError::Custom( + "specialized function has no definition", + ))) } } @@ -535,8 +537,7 @@ where /// Satisfied automatically by any stage enum built from `StageInfo` /// variants (and by `StageInfo` itself for single-language pipelines); /// compiler authors never implement it by hand. -pub trait StageQuery: - StageMeta +pub trait StageQuery: StageMeta + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> @@ -547,7 +548,7 @@ pub trait StageQuery: UniqueSpecialization, Result, InterpreterError, - > + SupportsStageDispatch, InterpreterError> + > + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch @@ -574,8 +575,11 @@ impl StageQuery for S where UniqueSpecialization, Result, InterpreterError, - > + SupportsStageDispatch, InterpreterError> - + SupportsStageDispatch, InterpreterError> + > + SupportsStageDispatch< + FunctionDefinition, + Result, + InterpreterError, + > + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch + SupportsStageDispatch< @@ -663,12 +667,12 @@ pub(crate) fn unique_specialization( dispatch(pipeline, stage, UniqueSpecialization(staged))? } -pub(crate) fn function_body( +pub(crate) fn function_definition( pipeline: &Pipeline, stage: CompileStage, specialized: SpecializedFunction, ) -> Result { - dispatch(pipeline, stage, FunctionBody(specialized))? + dispatch(pipeline, stage, FunctionDefinition(specialized))? } pub(crate) fn resolve_symbol_name( diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs b/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs index 3196d1ab29..47734259a7 100644 --- a/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs +++ b/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs @@ -136,7 +136,7 @@ where } => { let target = interp.resolve_call(resolve_stage, &callee)?; let index = interp.alloc_env(); - let entry = interp.enter_function(target.stage, target.body, args, index)?; + let entry = interp.enter_function(target.stage, target.definition, 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 traversal. diff --git a/crates/kirin-interpreter/src/engines/concrete/interp.rs b/crates/kirin-interpreter/src/engines/concrete/interp.rs index ebd9f1ff42..329daec888 100644 --- a/crates/kirin-interpreter/src/engines/concrete/interp.rs +++ b/crates/kirin-interpreter/src/engines/concrete/interp.rs @@ -1,6 +1,8 @@ use std::marker::PhantomData; -use kirin_ir::{Block, CFG, CompileStage, Pipeline, Product, SSAValue, StageMeta, Statement}; +use kirin_ir::{ + Block, CFG, CompileStage, Pipeline, Product, SSAValue, StageMeta, Statement, Symbol, +}; use crate::core::query; use crate::{ @@ -160,7 +162,7 @@ where fn enter_function( &mut self, stage: CompileStage, - body: Statement, + definition: Statement, args: Product, index: EnvIndex, ) -> Result, E> { @@ -170,10 +172,10 @@ where .ok_or_else(|| E::from(InterpreterError::MissingStage(stage)))?; let previous = self.location.replace(InterpLocation { stage, - statement: body, + statement: definition, index, }); - let result = info.dispatch_function_entry(body, args, self); + let result = info.dispatch_function_entry(definition, args, self); self.location = previous; result } @@ -286,6 +288,18 @@ where self.call(stage, Callee::Function(function), args) } + /// Resolve a stage-local `symbol` through the linker and execute the + /// selected callable to completion. This is the convenience form of + /// [`call`](Self::call) with [`Callee::Named`]. + pub fn call_by_symbol( + &mut self, + stage: CompileStage, + symbol: Symbol, + args: impl IntoIterator, + ) -> Result, E> { + self.call(stage, symbol.into(), args) + } + /// Execute a function to completion and return its return product. /// /// The root call is an ordinary [`CallFrame`](crate::CallFrame): the same call boundary @@ -340,6 +354,15 @@ where self.inner.call_by_name(stage_name, function_name, args) } + pub fn call_by_symbol( + &mut self, + stage: CompileStage, + symbol: Symbol, + args: impl IntoIterator, + ) -> Result, E> { + self.inner.call_by_symbol(stage, symbol, args) + } + pub fn call( &mut self, stage: CompileStage, diff --git a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs index 5c6d0d1bba..a0821ad934 100644 --- a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs @@ -37,7 +37,7 @@ use std::marker::PhantomData; use kirin_ir::{ Block, CFG, CompileStage, DiGraph, HasBottom, Pipeline, Product, SSAValue, SpecializedFunction, - StageMeta, Statement, Widen, + StageMeta, Statement, Symbol, Widen, }; use crate::core::query; @@ -304,7 +304,7 @@ enum ForwardUpdate { FunctionEntry { key: K, stage: CompileStage, - body: Statement, + definition: Statement, args: Product, }, /// Merge a return contribution into a function context's return (join); on @@ -635,7 +635,7 @@ where fn enter_function( &mut self, stage: CompileStage, - body: Statement, + definition: Statement, args: Product, index: EnvIndex, ) -> Result, E> { @@ -645,10 +645,10 @@ where .ok_or_else(|| E::from(InterpreterError::MissingStage(stage)))?; let previous = self.location.replace(InterpLocation { stage, - statement: body, + statement: definition, index, }); - let result = info.dispatch_function_entry(body, args, self); + let result = info.dispatch_function_entry(definition, args, self); self.location = previous; result } @@ -775,11 +775,12 @@ where fn enter_function( &mut self, stage: CompileStage, - body: Statement, + definition: Statement, args: Product, index: EnvIndex, ) -> Result, E> { - self.inner_mut().enter_function(stage, body, args, index) + self.inner_mut() + .enter_function(stage, definition, args, index) } } @@ -913,7 +914,7 @@ where self.apply_update(ForwardUpdate::FunctionEntry { key: key.clone(), stage: target.stage, - body: target.body, + definition: target.definition, args, })?; @@ -963,7 +964,7 @@ where ForwardUpdate::FunctionEntry { key, stage, - body, + definition, args, } => { let owner = Owner::Function(key.clone()); @@ -971,7 +972,7 @@ where self.summaries_mut().insert( owner.clone(), ForwardSummary::Function(FunctionSummary { - meta: Some((stage, body)), + meta: Some((stage, definition)), entry: args, entry_joins: 0, ret: None, @@ -1003,7 +1004,7 @@ where changed }; if changed { - self.seed_entry_block(&key, stage, body)?; + self.seed_entry_block(&key, stage, definition)?; } Ok(()) } @@ -1132,7 +1133,7 @@ where &mut self, key: &

>::Key, stage: CompileStage, - body: Statement, + definition: Statement, ) -> Result<(), E> { let env = match self.store().env(key) { Some(env) => env, @@ -1147,8 +1148,8 @@ where .and_then(|info| info.as_function()) .map(|function| function.entry.clone()) .expect("function summary present"); - let body_info = self.enter_function(stage, body, entry_args, env)?; - let owner = match body_info.body { + let entry = self.enter_function(stage, definition, entry_args, env)?; + let owner = match entry.body { Body::CFG(cfg) => Owner::Block { function: key.clone(), block: self @@ -1181,7 +1182,7 @@ where } self.apply_update(ForwardUpdate::OwnerEntry { owner, - args: body_info.args, + args: entry.args, }) } } @@ -1576,6 +1577,19 @@ where self.analyze(stage, Callee::Function(function), args) } + /// Resolve a stage-local `symbol` through the linker and analyze the + /// selected callable. This is the convenience form of + /// [`analyze`](Self::analyze) with [`Callee::Named`], and returns its + /// inferred return product at the fixpoint. + pub fn analyze_by_symbol( + &mut self, + stage: CompileStage, + symbol: Symbol, + args: impl IntoIterator, + ) -> Result, E> { + self.analyze(stage, symbol.into(), args) + } + /// Run the fixpoint from a single entry. Seeds the entry function's entry block /// owner and drains the owner worklist. pub fn analyze( @@ -1591,7 +1605,7 @@ where self.driver.apply_update(ForwardUpdate::FunctionEntry { key: key.clone(), stage: target.stage, - body: target.body, + definition: target.definition, args, })?; diff --git a/crates/kirin-ir/src/builder/error.rs b/crates/kirin-ir/src/builder/error.rs index 8c1705f5cd..69a4a39a41 100644 --- a/crates/kirin-ir/src/builder/error.rs +++ b/crates/kirin-ir/src/builder/error.rs @@ -86,8 +86,8 @@ pub struct SpecializeError { pub signature: Signature, /// Existing non-invalidated specializations with matching signatures. pub conflicting: Vec, - /// Preserved body statement for the new specialization. - pub body: Statement, + /// Preserved definition statement for the new specialization. + pub definition: Statement, /// Preserved backedges for the new specialization. pub backedges: Option>, } diff --git a/crates/kirin-ir/src/builder/redefine.rs b/crates/kirin-ir/src/builder/redefine.rs index 2e6477267a..e83092bc22 100644 --- a/crates/kirin-ir/src/builder/redefine.rs +++ b/crates/kirin-ir/src/builder/redefine.rs @@ -34,7 +34,7 @@ impl BuilderStageInfo { let specialized_function = SpecializedFunctionInfo::builder() .id(id) .signature(error.signature) - .body(error.body) + .definition(error.definition) .maybe_backedges(error.backedges) .new(); staged_function_info diff --git a/crates/kirin-ir/src/builder/staged.rs b/crates/kirin-ir/src/builder/staged.rs index 775d0b1ae7..25fb9907df 100644 --- a/crates/kirin-ir/src/builder/staged.rs +++ b/crates/kirin-ir/src/builder/staged.rs @@ -255,7 +255,7 @@ impl BuilderStageInfo { &mut self, #[builder(name = staged_func)] func: StagedFunction, signature: Option>, - #[builder(into)] body: Statement, + #[builder(into)] definition: Statement, backedges: Option>, ) -> Result> { let staged_function_info = &mut self.staged_functions[func]; @@ -274,7 +274,7 @@ impl BuilderStageInfo { staged_function: func, signature, conflicting, - body, + definition, backedges, }); } @@ -284,7 +284,7 @@ impl BuilderStageInfo { let specialized_function = SpecializedFunctionInfo::builder() .id(id) .signature(signature) - .body(body) + .definition(definition) .maybe_backedges(backedges) .new(); staged_function_info diff --git a/crates/kirin-ir/src/language.rs b/crates/kirin-ir/src/language.rs index 4c8f4b333c..50fd1b6a24 100644 --- a/crates/kirin-ir/src/language.rs +++ b/crates/kirin-ir/src/language.rs @@ -75,7 +75,7 @@ pub trait HasUngraphsMut<'a> { /// 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 +/// individual operations (e.g., `Function`, `Lambda`) that contain a single /// `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 { diff --git a/crates/kirin-ir/src/node/function/mod.rs b/crates/kirin-ir/src/node/function/mod.rs index 8f87af4381..a71db283d6 100644 --- a/crates/kirin-ir/src/node/function/mod.rs +++ b/crates/kirin-ir/src/node/function/mod.rs @@ -20,7 +20,7 @@ //! //! - [`SpecializedFunction`] / [`SpecializedFunctionInfo`] — A concrete //! instantiation of a staged function for a particular (possibly narrower) -//! signature. Owns the IR body. Dispatch selects the most specific +//! signature. Owns the IR definition statement. Dispatch selects the most specific //! non-invalidated specialization via [`SignatureSemantics`]. //! //! Each level can be *invalidated* (staged or specialized) when the function is diff --git a/crates/kirin-ir/src/node/function/specialized.rs b/crates/kirin-ir/src/node/function/specialized.rs index 625704468a..3b02e70e7f 100644 --- a/crates/kirin-ir/src/node/function/specialized.rs +++ b/crates/kirin-ir/src/node/function/specialized.rs @@ -18,14 +18,15 @@ impl SpecializedFunction { /// A concrete instantiation of a staged function for a specific signature. /// /// The specialized signature is a subset of the parent [`StagedFunctionInfo`](super::staged::StagedFunctionInfo)'s -/// generic signature. This is the level that owns the IR [`body`](Self::body). +/// generic signature. This is the level that owns the IR +/// [`definition`](Self::definition). /// Like staged functions, specializations can be invalidated and are then /// excluded from dispatch while remaining available for backedge tracking. #[derive(Clone, Debug)] pub struct SpecializedFunctionInfo { id: SpecializedFunction, signature: Signature, - body: Statement, + definition: Statement, /// Functions that call this function (used for inter-procedural analyses). backedges: Vec, /// Whether this specialization has been invalidated by a redefinition. @@ -42,15 +43,15 @@ impl SpecializedFunctionInfo { id: SpecializedFunction, /// The signature of this specialized function. signature: Signature, - /// The body of this specialized function. - body: Statement, + /// The definition statement of this specialized function. + definition: Statement, /// The functions that call this specialized function. backedges: Option>, ) -> Self { Self { id, signature, - body, + definition, backedges: backedges.unwrap_or_default(), invalidated: false, } @@ -68,12 +69,12 @@ impl SpecializedFunctionInfo { self.id } - pub fn body(&self) -> &Statement { - &self.body + pub fn definition(&self) -> &Statement { + &self.definition } - pub fn body_mut(&mut self) -> &mut Statement { - &mut self.body + pub fn definition_mut(&mut self) -> &mut Statement { + &mut self.definition } pub fn return_type(&self) -> &L::Type { diff --git a/crates/kirin-ir/src/pipeline.rs b/crates/kirin-ir/src/pipeline.rs index 0c62533d21..37d34a1fac 100644 --- a/crates/kirin-ir/src/pipeline.rs +++ b/crates/kirin-ir/src/pipeline.rs @@ -361,7 +361,7 @@ impl Pipeline { /// /// This is a convenience shorthand for the common case of creating the full /// three-level function hierarchy (Function → StagedFunction → SpecializedFunction) - /// with a single body. + /// with a single definition. /// /// # Errors /// @@ -374,7 +374,7 @@ impl Pipeline { /// ```ignore /// let (func, sf, spec) = pipeline.define_function::() /// .stage(stage_id) - /// .body(body_stmt) + /// .definition(definition) /// .name("my_func") /// .signature(sig) /// .new() @@ -386,7 +386,7 @@ impl Pipeline { #[builder(into)] name: Option, stage: CompileStage, signature: Option>, - #[builder(into)] body: Statement, + #[builder(into)] definition: Statement, ) -> Result<(Function, StagedFunction, SpecializedFunction), PipelineStagedError> where S: HasStageInfo, @@ -409,7 +409,7 @@ impl Pipeline { // Omit signature — specialize defaults to the staged function's signature. let spec = stage_info - .with_builder(|b| b.specialize().staged_func(sf).body(body).new()) + .with_builder(|b| b.specialize().staged_func(sf).definition(definition).new()) .expect("specialization conflict on newly created staged function"); Ok((func, sf, spec)) diff --git a/crates/kirin-ir/src/signature/has_signature.rs b/crates/kirin-ir/src/signature/has_signature.rs index 87fc0fa20a..4e31868d2f 100644 --- a/crates/kirin-ir/src/signature/has_signature.rs +++ b/crates/kirin-ir/src/signature/has_signature.rs @@ -2,11 +2,11 @@ use crate::Dialect; use super::Signature; -/// Extract the function signature from a parsed function-body statement. +/// Extract the function signature from a parsed function-definition statement. /// -/// Implemented by dialect types that serve as function bodies (e.g., `FunctionBody`, -/// `CircuitFunction`). The framework calls this after parsing to construct the -/// `SpecializedFunction`. +/// Implemented by dialect types that serve as function definitions (e.g., +/// `Function`, `CircuitFunction`). The framework calls this after parsing to +/// construct the `SpecializedFunction`. /// /// With RFC 0004, the signature is a field on the statement type — `derive(Dialect)` /// generates this trait automatically. Types with a `Signature` field return @@ -17,6 +17,6 @@ use super::Signature; /// - `L`: The dialect whose `Type` is used in the signature. pub trait HasSignature { /// Returns the function signature from this statement, or `None` - /// if the type does not carry a signature (e.g. non-function-body statements). + /// if the type does not carry a signature (e.g. non-definition statements). fn signature(&self) -> Option>; } diff --git a/crates/kirin-ir/src/stage/info.rs b/crates/kirin-ir/src/stage/info.rs index 3158aff588..7b2b527000 100644 --- a/crates/kirin-ir/src/stage/info.rs +++ b/crates/kirin-ir/src/stage/info.rs @@ -59,9 +59,13 @@ use super::arenas::Arenas; /// let ret = b.statement().definition(MyDialect::Return(arg)).new(); /// let block = b.block().argument(MyType::I64).terminator(ret).new(); /// let cfg = b.cfg().add_block(block).new(); -/// let body = b.statement().definition(MyDialect::FuncBody(cfg)).new(); +/// let definition = b.statement().definition(MyDialect::Function(cfg)).new(); /// -/// b.specialize().staged_func(sf).body(body).new().unwrap(); +/// b.specialize() +/// .staged_func(sf) +/// .definition(definition) +/// .new() +/// .unwrap(); /// }); /// // stage is back to StageInfo with the new function added /// ``` diff --git a/crates/kirin-ir/tests/builder_staged.rs b/crates/kirin-ir/tests/builder_staged.rs index 99ce5b29c6..c8c1d75faa 100644 --- a/crates/kirin-ir/tests/builder_staged.rs +++ b/crates/kirin-ir/tests/builder_staged.rs @@ -100,19 +100,19 @@ fn specialize_success_and_duplicate_error() { let sf = stage.staged_function().new().unwrap(); - let body1 = stage.statement().definition(BuilderDialect::Return).new(); + let definition1 = stage.statement().definition(BuilderDialect::Return).new(); let _spec1 = stage .specialize() .staged_func(sf) - .body(body1) + .definition(definition1) .new() .expect("first specialize should succeed"); - let body2 = stage.statement().definition(BuilderDialect::Return).new(); + let definition2 = stage.statement().definition(BuilderDialect::Return).new(); let err = stage .specialize() .staged_func(sf) - .body(body2) + .definition(definition2) .new() .expect_err("duplicate signature should fail"); @@ -124,19 +124,19 @@ fn redefine_specialization_invalidates_and_registers() { let mut stage = new_stage(); let sf = stage.staged_function().new().unwrap(); - let body1 = stage.statement().definition(BuilderDialect::Return).new(); + let definition1 = stage.statement().definition(BuilderDialect::Return).new(); let spec1 = stage .specialize() .staged_func(sf) - .body(body1) + .definition(definition1) .new() .unwrap(); - let body2 = stage.statement().definition(BuilderDialect::Return).new(); + let definition2 = stage.statement().definition(BuilderDialect::Return).new(); let err = stage .specialize() .staged_func(sf) - .body(body2) + .definition(definition2) .new() .expect_err("duplicate"); @@ -181,23 +181,23 @@ fn staged_function_all_matching_returns_most_specific() { let mut stage = new_stage(); let sf = stage.staged_function().new().unwrap(); - let body1 = stage.statement().definition(BuilderDialect::Return).new(); + let definition1 = stage.statement().definition(BuilderDialect::Return).new(); let sig_i32 = Signature::new(vec![TestType::I32], TestType::Any, ()); let _spec1 = stage .specialize() .staged_func(sf) .signature(sig_i32.clone()) - .body(body1) + .definition(definition1) .new() .unwrap(); - let body2 = stage.statement().definition(BuilderDialect::Return).new(); + let definition2 = stage.statement().definition(BuilderDialect::Return).new(); let sig_i64 = Signature::new(vec![TestType::I64], TestType::Any, ()); let _spec2 = stage .specialize() .staged_func(sf) .signature(sig_i64) - .body(body2) + .definition(definition2) .new() .unwrap(); @@ -214,21 +214,21 @@ fn staged_function_all_matching_excludes_invalidated() { let mut stage = new_stage(); let sf = stage.staged_function().new().unwrap(); - let body1 = stage.statement().definition(BuilderDialect::Return).new(); + let definition1 = stage.statement().definition(BuilderDialect::Return).new(); let default_sig: Signature = Signature::placeholder(); let spec1 = stage .specialize() .staged_func(sf) - .body(body1) + .definition(definition1) .new() .unwrap(); // Invalidate spec1 by redefining - let body2 = stage.statement().definition(BuilderDialect::Return).new(); + let definition2 = stage.statement().definition(BuilderDialect::Return).new(); let err = stage .specialize() .staged_func(sf) - .body(body2) + .definition(definition2) .new() .expect_err("duplicate"); let _spec2 = stage.redefine_specialization(err); @@ -248,8 +248,13 @@ fn staged_function_unique_live_specialization_returns_only_live_specialization() let mut stage = new_stage(); let sf = stage.staged_function().new().unwrap(); - let body = stage.statement().definition(BuilderDialect::Return).new(); - let spec = stage.specialize().staged_func(sf).body(body).new().unwrap(); + let definition = stage.statement().definition(BuilderDialect::Return).new(); + let spec = stage + .specialize() + .staged_func(sf) + .definition(definition) + .new() + .unwrap(); let stage = stage.finalize().unwrap(); let sf_info = sf.get_info(&stage).unwrap(); @@ -262,23 +267,23 @@ fn staged_function_unique_live_specialization_rejects_ambiguous_live_set() { let mut stage = new_stage(); let sf = stage.staged_function().new().unwrap(); - let body1 = stage.statement().definition(BuilderDialect::Return).new(); + let definition1 = stage.statement().definition(BuilderDialect::Return).new(); let sig_i32 = Signature::new(vec![TestType::I32], TestType::Any, ()); stage .specialize() .staged_func(sf) .signature(sig_i32) - .body(body1) + .definition(definition1) .new() .unwrap(); - let body2 = stage.statement().definition(BuilderDialect::Return).new(); + let definition2 = stage.statement().definition(BuilderDialect::Return).new(); let sig_i64 = Signature::new(vec![TestType::I64], TestType::Any, ()); stage .specialize() .staged_func(sf) .signature(sig_i64) - .body(body2) + .definition(definition2) .new() .unwrap(); diff --git a/crates/kirin-liveness/tests/cfg.rs b/crates/kirin-liveness/tests/cfg.rs index 50e134f7ae..65615d9c89 100644 --- a/crates/kirin-liveness/tests/cfg.rs +++ b/crates/kirin-liveness/tests/cfg.rs @@ -62,11 +62,11 @@ fn main_cfg( .expect("@main is staged at @test"); let sf_info = sf.get_info(stage).expect("staged function info"); let spec = &sf_info.specializations()[0]; - let body = *spec.body(); + let definition = *spec.definition(); - let cfg = match body.definition(stage) { + let cfg = match definition.definition(stage) { ArithFunctionLanguage::Function { body, .. } => *body, - other => panic!("expected a function body, got {other:?}"), + other => panic!("expected a function definition, got {other:?}"), }; (stage_id, cfg) } diff --git a/crates/kirin-prettyless/src/document/ir_render.rs b/crates/kirin-prettyless/src/document/ir_render.rs index 4c284cb57d..d08251864c 100644 --- a/crates/kirin-prettyless/src/document/ir_render.rs +++ b/crates/kirin-prettyless/src/document/ir_render.rs @@ -234,17 +234,17 @@ where let staged_info = staged_fn.expect_info(self.stage); let spec = &staged_info.specializations()[idx]; - // Set function context so the body's PrettyPrint can access it + // Set function context so the definition's PrettyPrint can access it // via doc.print_function_name() and doc.print_return_types(). let prev_name = self.function_name(); self.set_function_name(staged_info.name()); let header = self.text("specialize @") + self.text(self.stage_symbol_text()); - let body = self.print_statement(spec.body()); + let definition = self.print_statement(spec.definition()); self.set_function_name(prev_name); - header + self.text(" ") + body + header + self.text(" ") + definition } /// Pretty print a staged function with all its non-invalidated specializations. @@ -271,14 +271,14 @@ where return doc; } - // Set function context for body PrettyPrint projections. + // Set function context for definition PrettyPrint projections. let prev_name = self.function_name(); self.set_function_name(info.name()); for spec in active { doc += self.line_(); doc += self.text("specialize @") + self.text(self.stage_symbol_text()); - doc += self.text(" ") + self.print_statement(spec.body()); + doc += self.text(" ") + self.print_statement(spec.definition()); } self.set_function_name(prev_name); diff --git a/crates/kirin-prettyless/src/tests/edge_cases.rs b/crates/kirin-prettyless/src/tests/edge_cases.rs index 8eb0c25749..265ec9b82e 100644 --- a/crates/kirin-prettyless/src/tests/edge_cases.rs +++ b/crates/kirin-prettyless/src/tests/edge_cases.rs @@ -311,7 +311,11 @@ fn test_staged_function_unnamed() { let block = ctx.block().stmt(a).terminator(ret).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(); + ctx.specialize() + .staged_func(sf) + .definition(fdef) + .new() + .unwrap(); }); let output = PrintExt::sprint(&func, &pipeline); @@ -341,7 +345,7 @@ fn test_staged_function_no_params() { let _ = stage .specialize() .staged_func(staged_function) - .body(fdef) + .definition(fdef) .new() .unwrap(); @@ -392,7 +396,11 @@ fn test_pipeline_render_builder_write_to() { let block = ctx.block().stmt(a).terminator(ret).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(); + ctx.specialize() + .staged_func(sf) + .definition(fdef) + .new() + .unwrap(); }); let mut output = Vec::new(); @@ -428,7 +436,11 @@ fn test_function_render_builder_write_to() { let block = ctx.block().stmt(a).terminator(ret).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(); + ctx.specialize() + .staged_func(sf) + .definition(fdef) + .new() + .unwrap(); }); let mut output = Vec::new(); @@ -462,7 +474,7 @@ fn test_render_very_narrow_width() { let _ = stage .specialize() .staged_func(sf) - .body(fdef) + .definition(fdef) .new() .unwrap(); diff --git a/crates/kirin-prettyless/src/tests/impls.rs b/crates/kirin-prettyless/src/tests/impls.rs index dd0550ed4a..312f2dd226 100644 --- a/crates/kirin-prettyless/src/tests/impls.rs +++ b/crates/kirin-prettyless/src/tests/impls.rs @@ -432,7 +432,7 @@ fn test_render_builder_config() { let f = stage .specialize() .staged_func(sf) - .body(fdef) + .definition(fdef) .new() .unwrap(); diff --git a/crates/kirin-prettyless/src/tests/mod.rs b/crates/kirin-prettyless/src/tests/mod.rs index 8a5ccca40b..d7060454fb 100644 --- a/crates/kirin-prettyless/src/tests/mod.rs +++ b/crates/kirin-prettyless/src/tests/mod.rs @@ -76,7 +76,7 @@ fn create_test_function() -> ( let f = stage .specialize() .staged_func(staged_function) - .body(fdef) + .definition(fdef) .new() .unwrap(); diff --git a/crates/kirin-prettyless/src/tests/pipeline.rs b/crates/kirin-prettyless/src/tests/pipeline.rs index 04636aa454..45f596d297 100644 --- a/crates/kirin-prettyless/src/tests/pipeline.rs +++ b/crates/kirin-prettyless/src/tests/pipeline.rs @@ -23,7 +23,11 @@ fn test_pipeline_function_print() { let block = ctx0.block().stmt(a).terminator(ret).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(); + ctx0.specialize() + .staged_func(sf0) + .definition(fdef) + .new() + .unwrap(); }); // --- Stage B: a different version with two constants --- @@ -48,7 +52,11 @@ fn test_pipeline_function_print() { let block = ctx1.block().stmt(a).stmt(b).stmt(c).terminator(ret).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(); + ctx1.specialize() + .staged_func(sf1) + .definition(fdef) + .new() + .unwrap(); }); // Print the function across both stages @@ -80,7 +88,11 @@ fn test_pipeline_unnamed_stage() { let block = ctx.block().stmt(a).terminator(ret).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(); + ctx.specialize() + .staged_func(sf) + .definition(fdef) + .new() + .unwrap(); }); // Should fall back to numeric symbol form: "stage @0" diff --git a/crates/kirin-prettyless/src/tests/sprint_with_globals.rs b/crates/kirin-prettyless/src/tests/sprint_with_globals.rs index 800af41212..5d16eff7bc 100644 --- a/crates/kirin-prettyless/src/tests/sprint_with_globals.rs +++ b/crates/kirin-prettyless/src/tests/sprint_with_globals.rs @@ -19,7 +19,7 @@ fn test_sprint_with_globals() { let _ = stage .specialize() .staged_func(staged_function) - .body(fdef) + .definition(fdef) .new() .unwrap(); diff --git a/docs/design/formalism/syntax.md b/docs/design/formalism/syntax.md index 3c5e2c2446..158c2b927e 100644 --- a/docs/design/formalism/syntax.md +++ b/docs/design/formalism/syntax.md @@ -48,7 +48,7 @@ Language ::= DialectEnumVariant* Function ::= FunctionInfo + staged variants StagedFunction ::= stage-specific callable variant -Specialized ::= concrete specialization with body Statement +Specialized ::= concrete specialization with definition Statement Statement ::= dialect definition + operands + results + nested blocks/cfgs/successors CFG ::= Block* diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index 146550b423..87f28e5aac 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -297,7 +297,7 @@ pub trait Linker { ``` A linker resolves `Callee::{Named, Function, Staged, Specialized}` to a -`(stage, specialization, body)` target. It is a *field of the engine*, never +`(stage, specialization, definition)` target. It is a *field of the engine*, never a trait the user implements on the engine type — this is a deliberate coherence rule: policies must be swappable without newtype-cloning a driver. diff --git a/example/toy-lang/src/interpreter/mod.rs b/example/toy-lang/src/interpreter/mod.rs index de1ef10cbd..7cc35d0bcc 100644 --- a/example/toy-lang/src/interpreter/mod.rs +++ b/example/toy-lang/src/interpreter/mod.rs @@ -170,15 +170,16 @@ fn function_cfg( }); } }; - let spec_info = spec - .get_info(info) - .ok_or(InterpreterError::Custom("specialized function has no body"))?; - match spec_info.body().definition(info) { + let spec_info = spec.get_info(info).ok_or(InterpreterError::Custom( + "specialized function has no definition", + ))?; + let definition = *spec_info.definition(); + match definition.definition(info) { HighLevel::Lexical(Lexical::Function(function)) => { use kirin::prelude::HasCFGBody; *function.cfg() } - _ => return Err(InterpreterError::Custom("expected a function body")), + _ => return Err(InterpreterError::Custom("expected a function definition")), } } Stage::Lowered(info) => { @@ -197,15 +198,16 @@ fn function_cfg( }); } }; - let spec_info = spec - .get_info(info) - .ok_or(InterpreterError::Custom("specialized function has no body"))?; - match spec_info.body().definition(info) { + let spec_info = spec.get_info(info).ok_or(InterpreterError::Custom( + "specialized function has no definition", + ))?; + let definition = *spec_info.definition(); + match definition.definition(info) { LowLevel::Lifted(Lifted::Function(function)) => { use kirin::prelude::HasCFGBody; *function.cfg() } - _ => return Err(InterpreterError::Custom("expected a function body")), + _ => return Err(InterpreterError::Custom("expected a function definition")), } } }; diff --git a/example/toy-lang/src/interpreter/tests.rs b/example/toy-lang/src/interpreter/tests.rs index 45630e8d42..d116bfa4fa 100644 --- a/example/toy-lang/src/interpreter/tests.rs +++ b/example/toy-lang/src/interpreter/tests.rs @@ -263,7 +263,7 @@ fn build_cross_stage_specialized_pipeline() -> Pipeline { .terminator(ret) .new(); let cfg = builder.cfg().add_block(block).new(); - let body = Function::::new( + let definition = Function::::new( builder, cfg, Signature::new(vec![ArithType::I64], ArithType::I64, ()), @@ -271,7 +271,7 @@ fn build_cross_stage_specialized_pipeline() -> Pipeline { builder .specialize() .staged_func(caller) - .body(body) + .definition(definition) .new() .unwrap(); }); @@ -624,10 +624,10 @@ mod demand { .resolve_staged_function(name, stage_id) .expect("staged function"); let sf_info = sf.get_info(info).expect("staged function info"); - let body = *sf_info.specializations()[0].body(); - let cfg = match body.definition(info) { + let definition = *sf_info.specializations()[0].definition(); + let cfg = match definition.definition(info) { HighLevel::Lexical(Lexical::Function(function)) => *function.cfg(), - other => panic!("expected a function body, got {other:?}"), + other => panic!("expected a function definition, got {other:?}"), }; (stage_id, cfg) } diff --git a/example/toy-qc/src/circuit.rs b/example/toy-qc/src/circuit.rs index c328b4bd0b..a2c2a970d1 100644 --- a/example/toy-qc/src/circuit.rs +++ b/example/toy-qc/src/circuit.rs @@ -1,7 +1,7 @@ use crate::types::QubitType; use kirin::prelude::*; -/// Function body holding a DiGraph for circuit-stage programs. +/// Function definition whose body is a DiGraph for circuit-stage programs. /// Circuits are naturally directed acyclic graphs: qubit values flow /// forward through gates. #[derive(Clone, Debug, PartialEq, Dialect, HasParser, PrettyPrint)] diff --git a/example/toy-qc/src/zx.rs b/example/toy-qc/src/zx.rs index 7ea160d960..3a328a2673 100644 --- a/example/toy-qc/src/zx.rs +++ b/example/toy-qc/src/zx.rs @@ -1,7 +1,7 @@ use crate::types::QubitType; use kirin::prelude::*; -/// Function body holding an UnGraph for ZX-stage programs. +/// Function definition whose body is an UnGraph for ZX-stage programs. /// ZX calculus diagrams are undirected graphs: wires are edges /// and spiders/boxes are nodes connected by those edges. #[derive(Clone, Debug, PartialEq, Dialect, HasParser, PrettyPrint)] diff --git a/tests/body_kinds.rs b/tests/body_kinds.rs index d933e478cd..56ce967ebf 100644 --- a/tests/body_kinds.rs +++ b/tests/body_kinds.rs @@ -37,7 +37,7 @@ use kirin_constprop::{ConstPropContext, ConstPropValue}; use kirin_function::Lexical; use kirin_interpreter::{ AbstractBlockFrame, AbstractCallFrame, AbstractCompletion, AbstractDiGraphFrame, BlockFrame, - Body, CFGFrame, CallContext, CallFrame, CallRequest, Completion, ConcreteInterpreter, + Body, CFGFrame, CallContext, CallFrame, CallRequest, Callee, Completion, ConcreteInterpreter, ConcreteInterpreterCore, ContextInsensitive, DefaultCallBodyTraversal, DiGraphFrame, Frame, FrameEffect, FrameEngine, FunctionEntry, Interpretable, InterpreterError, SameStageLinker, SparseForwardInterpreter, expect_single, @@ -86,6 +86,23 @@ fn parse(program: &str) -> Pipeline { pipeline } +fn local_function_symbol( + pipeline: &Pipeline, + stage_name: &str, + function_name: &str, +) -> (CompileStage, Symbol) { + let stage = pipeline + .stage_by_name(stage_name) + .expect("named stage exists"); + let symbol = pipeline + .stage(stage) + .expect("stage info exists") + .symbol_table() + .lookup(function_name) + .expect("function name is interned in the stage-local symbol table"); + (stage, symbol) +} + fn run(pipeline: &Pipeline, function: &str, args: &[i64]) -> Result { expect_single(run_product(pipeline, function, args)?) } @@ -715,6 +732,64 @@ fn analyze_insensitive( expect_single(analysis.analyze_by_name("test", function, args.iter().cloned())?) } +#[test] +fn concrete_symbol_entry_matches_name_and_direct_callee() { + let pipeline = parse(DIGRAPH_CALLABLE_PROGRAM); + let (stage, symbol) = local_function_symbol(&pipeline, "test", "gadd"); + assert_eq!(Callee::from(symbol), Callee::Named(symbol)); + + let mut by_name: Engine<'_> = ConcreteInterpreter::new(&pipeline); + let by_name = + expect_single::(by_name.call_by_name("test", "gadd", [2, 3]).unwrap()) + .unwrap(); + + let mut by_symbol: Engine<'_> = ConcreteInterpreter::new(&pipeline); + let by_symbol = + expect_single::(by_symbol.call_by_symbol(stage, symbol, [2, 3]).unwrap()) + .unwrap(); + + let mut by_callee: Engine<'_> = ConcreteInterpreter::new(&pipeline); + let by_callee = expect_single::( + by_callee + .call(stage, Callee::Named(symbol), [2, 3]) + .unwrap(), + ) + .unwrap(); + + assert_eq!((by_name, by_symbol, by_callee), (5, 5, 5)); +} + +#[test] +fn sparse_forward_symbol_entry_matches_name_and_direct_callee() { + let pipeline = parse(DIGRAPH_CALLABLE_PROGRAM); + let (stage, symbol) = local_function_symbol(&pipeline, "test", "gadd"); + let args = || [ConstPropValue::Const(2), ConstPropValue::Const(3)]; + + let mut by_name: AbstractEngine<'_> = SparseForwardInterpreter::new(&pipeline); + let by_name = expect_single::( + by_name.analyze_by_name("test", "gadd", args()).unwrap(), + ) + .unwrap(); + + let mut by_symbol: AbstractEngine<'_> = SparseForwardInterpreter::new(&pipeline); + let by_symbol = expect_single::( + by_symbol.analyze_by_symbol(stage, symbol, args()).unwrap(), + ) + .unwrap(); + + let mut by_callee: AbstractEngine<'_> = SparseForwardInterpreter::new(&pipeline); + let by_callee = expect_single::( + by_callee + .analyze(stage, Callee::Named(symbol), args()) + .unwrap(), + ) + .unwrap(); + + assert_eq!(by_name, ConstPropValue::Const(5)); + assert_eq!(by_symbol, by_name); + assert_eq!(by_callee, by_name); +} + /// A CFG body whose branch condition is an *unknown* argument, so neither /// successor can be decided: the abstract block frame explores both and joins /// their returns. Identical arms fold to a constant; differing arms join to diff --git a/tests/frame_engine_capabilities.rs b/tests/frame_engine_capabilities.rs index bc268bdf73..6694a0941e 100644 --- a/tests/frame_engine_capabilities.rs +++ b/tests/frame_engine_capabilities.rs @@ -270,7 +270,7 @@ impl CallServices for CallOnlyEngine { fn enter_function( &mut self, _stage: CompileStage, - _body: Statement, + _definition: Statement, _args: Product, _index: EnvIndex, ) -> Result, InterpreterError> { diff --git a/tests/roundtrip/composable_existing_dialects.rs b/tests/roundtrip/composable_existing_dialects.rs index 663da5a628..03a7d440ed 100644 --- a/tests/roundtrip/composable_existing_dialects.rs +++ b/tests/roundtrip/composable_existing_dialects.rs @@ -2,7 +2,7 @@ use kirin::prelude::*; use kirin_arith::{Arith, ArithType, ArithValue}; use kirin_cmp::Cmp; use kirin_constant::Constant; -use kirin_function::{Function as FunctionBody, Lexical, Return}; +use kirin_function::{Function, Lexical, Return}; use kirin_scf::StructuredControlFlow; use kirin_test_utils::roundtrip; @@ -27,7 +27,7 @@ enum ComposedSourceLanguage { #[chumsky(crate = kirin::parsers)] enum WrappedConstantLanguage { #[wraps] - Function(FunctionBody), + Function(Function), #[wraps] Constant(Constant), #[wraps] diff --git a/tests/roundtrip/digraph.rs b/tests/roundtrip/digraph.rs index 512bb58e1e..2e125a2a4a 100644 --- a/tests/roundtrip/digraph.rs +++ b/tests/roundtrip/digraph.rs @@ -110,7 +110,7 @@ fn test_projected_digraph_empty_body_roundtrip() { // --- Pipeline-level projected format e2e test --- -/// A dialect where the function body uses projected DiGraph format. +/// A dialect where the function definition uses projected DiGraph format. /// The body format is `({body:ports}) {{ {body:body} }}` — ports and body /// are parsed from projections, and the function signature is extracted /// from the IR after emit. @@ -129,11 +129,11 @@ enum ProjectedFuncLang { #[kirin(into)] kirin_test_languages::Value, #[kirin(type = SimpleType::F64)] ResultValue, ), - /// Function body: `fn {:name}{sig} ({graph:ports}) captures ({graph:captures}) {{ {graph:body} }}` + /// Function definition: `fn {:name}{sig} ({graph:ports}) captures ({graph:captures}) {{ {graph:body} }}` #[chumsky( format = "fn {:name}{sig} ({graph:ports}) captures ({graph:captures}) {{ {graph:body} }}" )] - FuncBody { + FunctionDefinition { graph: DiGraph, sig: Signature, }, @@ -180,7 +180,7 @@ specialize @test fn @foo(f64) -> f64 (%p0: f64) captures () { %r = add %p0, %p0; // --- Use Case 5: Block projections pipeline test --- -/// A dialect using Block projections for the function body. +/// A dialect using Block projections for the function definition. #[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint)] #[kirin(builders, type = SimpleType, crate = kirin::ir)] #[chumsky(crate = kirin::parsers)] @@ -194,9 +194,9 @@ enum BlockProjectedLang { #[chumsky(format = "$ret {0}")] #[kirin(terminator)] Ret(SSAValue), - /// Function body: `fn {:name}{sig} ({body:args}) {{ {body:body} }}` + /// Function definition: `fn {:name}{sig} ({body:args}) {{ {body:body} }}` #[chumsky(format = "fn {:name}{sig} ({body:args}) {{ {body:body} }}")] - FuncBody { + FunctionDefinition { body: Block, sig: Signature, }, @@ -244,9 +244,9 @@ enum CFGProjectedLang { #[chumsky(format = "$ret {0}")] #[kirin(terminator)] Ret(SSAValue), - /// Function body: `fn {:name}{sig} {{ {body:body} }}` + /// Function definition: `fn {:name}{sig} {{ {body:body} }}` #[chumsky(format = "fn {:name}{sig} {{ {body:body} }}")] - FuncBody { + FunctionDefinition { body: CFG, sig: Signature, }, @@ -296,11 +296,11 @@ enum DialectControlledLang { #[kirin(into)] kirin_test_languages::Value, #[kirin(type = SimpleType::F64)] ResultValue, ), - /// Function body: `fn {:name}{sig} ({graph:ports}) captures ({graph:captures}) {{ {graph:body} }}` + /// Function definition: `fn {:name}{sig} ({graph:ports}) captures ({graph:captures}) {{ {graph:body} }}` #[chumsky( format = "fn {:name}{sig} ({graph:ports}) captures ({graph:captures}) {{ {graph:body} }}" )] - FuncBody { + FunctionDefinition { graph: DiGraph, sig: Signature, }, diff --git a/tests/simple.rs b/tests/simple.rs index 44a9de9917..10a5b079ec 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -49,7 +49,7 @@ fn test_block() { let f = stage .specialize() .staged_func(staged_function) - .body(fdef) + .definition(fdef) .new() .unwrap(); From f6eff64c080bcc6639bd870d502cc3cf1e411335 Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Mon, 31 Aug 2026 09:08:01 -0400 Subject: [PATCH 2/4] Refactor interpreter and analysis interfaces for improved clarity and functionality - Updated the `FunctionEntry` trait to remove the argument passing, simplifying callable statement handling. - Refactored the `function_cfg` method to streamline the retrieval of function bodies and their corresponding CFGs. - Enhanced the `analyze_classic_liveness` and `analyze_dense_toy` functions to utilize the new `Callee` structure for function calls. - Adjusted test cases to align with the new callable structure, ensuring consistent analysis across various scenarios. - Removed unnecessary complexity in the interpreter's handling of function entries and CFG retrieval, promoting cleaner code and better maintainability. - Deleted redundant code and improved documentation for clarity on the new structure and its implications for future development. --- .gitignore | 1 + .../src/function_entry.rs | 34 +- .../src/interp_dispatch.rs | 11 +- crates/kirin-derive-interpreter/src/lib.rs | 2 +- ..._function_entry_for_callable_variants.snap | 32 +- crates/kirin-function/src/interpreter.rs | 24 +- crates/kirin-interpreter/src/core/dispatch.rs | 37 +-- crates/kirin-interpreter/src/core/effect.rs | 33 +- crates/kirin-interpreter/src/core/error.rs | 4 +- crates/kirin-interpreter/src/core/frame.rs | 17 +- crates/kirin-interpreter/src/core/linker.rs | 33 +- crates/kirin-interpreter/src/core/query.rs | 5 +- .../src/engines/concrete/frames/call_frame.rs | 18 +- .../src/engines/concrete/frames/protocol.rs | 8 +- .../src/engines/concrete/interp.rs | 31 +- .../src/engines/dense_backward/interp.rs | 73 +++-- .../src/engines/sparse_backward/interp.rs | 75 +++-- .../src/engines/sparse_forward/interp.rs | 85 ++--- crates/kirin-liveness/Cargo.toml | 1 + crates/kirin-liveness/src/lib.rs | 50 +-- crates/kirin-liveness/src/result.rs | 38 ++- crates/kirin-liveness/tests/callable_root.rs | 296 ++++++++++++++++++ crates/kirin-liveness/tests/cfg.rs | 154 +++++---- .../src/arith_function_language.rs | 20 +- .../src/graph_function_language.rs | 72 +++-- docs/.DS_Store | Bin 6148 -> 0 bytes docs/design/interpreter/index.md | 57 ++-- example/toy-lang/src/interpreter/mod.rs | 108 ++----- example/toy-lang/src/interpreter/tests.rs | 106 ++++--- example/toy-lang/src/main.rs | 4 +- tests/frame_engine_capabilities.rs | 21 +- 31 files changed, 888 insertions(+), 562 deletions(-) create mode 100644 crates/kirin-liveness/tests/callable_root.rs delete mode 100644 docs/.DS_Store diff --git a/.gitignore b/.gitignore index 4403bbe0cc..28526e6d13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target __pycache__/ *.py[cod] +.DS_Store skills-lock.json docs/superpowers/ refactor-workspace/ diff --git a/crates/kirin-derive-interpreter/src/function_entry.rs b/crates/kirin-derive-interpreter/src/function_entry.rs index b1ad6e3bea..2f88e272d1 100644 --- a/crates/kirin-derive-interpreter/src/function_entry.rs +++ b/crates/kirin-derive-interpreter/src/function_entry.rs @@ -29,26 +29,19 @@ pub fn do_derive_function_entry(input: &syn::DeriveInput) -> darling::Result, interp_crate: &syn::Path, - ir_crate: &syn::Path, + _ir_crate: &syn::Path, ) -> darling::Result> { validate_function_entry(ctx)?; let type_name = &ctx.meta.name; - let mut impl_generics = ctx.meta.generics.clone(); - // Specialized on the engine type `__EntryI`, mirroring `Interpretable`. - impl_generics - .params - .push(syn::GenericParam::Type(syn::parse_quote!(__EntryI))); - - let (impl_generics, _, _) = impl_generics.split_for_impl(); + let (impl_generics, _, _) = ctx.meta.generics.split_for_impl(); let (_, ty_generics, original_where) = ctx.meta.generics.split_for_impl(); let callable_wrappers = collect_callable_wrappers(ctx); - let mut predicates: Vec = - vec![syn::parse_quote! { __EntryI: #interp_crate::Interp }]; + let mut predicates: Vec = Vec::new(); for wrapper_ty in callable_wrappers { predicates.push(syn::parse_quote! { - #wrapper_ty: #interp_crate::FunctionEntry<__EntryI> + #wrapper_ty: #interp_crate::FunctionEntry }); } let extra_where: syn::WhereClause = syn::parse_quote! { where #(#predicates),* }; @@ -78,15 +71,11 @@ fn emit_function_entry( .as_ref() .ok_or_else(|| darling::Error::custom("expected wrapper binding"))?; arms.push(quote! { - #arm_pattern => #binding.function_entry(args, interp) + #arm_pattern => #binding.function_entry() }); } else { arms.push(quote! { - Self::#variant_name { .. } => Err(<__EntryI as #interp_crate::Interp>::Error::from( - #interp_crate::InterpreterError::NotCallable( - <__EntryI as #interp_crate::Interp>::statement(interp) - ) - )) + Self::#variant_name { .. } => None }); } } @@ -108,15 +97,8 @@ fn emit_function_entry( Ok(vec![quote! { #[automatically_derived] - impl #impl_generics #interp_crate::FunctionEntry<__EntryI> for #type_name #ty_generics #where_clause { - fn function_entry( - &self, - args: #ir_crate::Product<<__EntryI as #interp_crate::Interp>::Value>, - interp: &mut __EntryI, - ) -> Result< - #interp_crate::CallableBody<<__EntryI as #interp_crate::Interp>::Value>, - <__EntryI as #interp_crate::Interp>::Error, - > { + impl #impl_generics #interp_crate::FunctionEntry for #type_name #ty_generics #where_clause { + fn function_entry(&self) -> Option<#interp_crate::CallableBody> { #body } } diff --git a/crates/kirin-derive-interpreter/src/interp_dispatch.rs b/crates/kirin-derive-interpreter/src/interp_dispatch.rs index fcc3291822..3d1f93a962 100644 --- a/crates/kirin-derive-interpreter/src/interp_dispatch.rs +++ b/crates/kirin-derive-interpreter/src/interp_dispatch.rs @@ -68,7 +68,7 @@ pub fn generate(input: &DeriveInput) -> Result { // be interpretable for that engine's key — no higher-ranked GAT // projection. predicates.push(syn::parse_quote! { - #dialect_ty: #interp_crate::Interpretable<__InterpI, <__InterpI as #interp_crate::Interp>::Semantics> + #interp_crate::FunctionEntry<__InterpI> + #dialect_ty: #interp_crate::Interpretable<__InterpI, <__InterpI as #interp_crate::Interp>::Semantics> + #interp_crate::FunctionEntry }); } let mut where_clause = original_where.cloned().unwrap_or_else(|| syn::WhereClause { @@ -87,7 +87,7 @@ pub fn generate(input: &DeriveInput) -> Result { let entry_arms = build_arms(&variants, enum_ident, |_| { quote! { #interp_crate::InterpDispatch::dispatch_function_entry( - stage_info, definition, args, interp, + stage_info, definition, ) } }); @@ -113,12 +113,7 @@ pub fn generate(input: &DeriveInput) -> Result { fn dispatch_function_entry( &self, definition: #ir_crate::Statement, - args: #ir_crate::Product<<__InterpI as #interp_crate::Interp>::Value>, - interp: &mut __InterpI, - ) -> Result< - #interp_crate::CallableBody<<__InterpI as #interp_crate::Interp>::Value>, - <__InterpI as #interp_crate::Interp>::Error, - > { + ) -> Result<#interp_crate::CallableBody, <__InterpI as #interp_crate::Interp>::Error> { match self { #entry_arms } diff --git a/crates/kirin-derive-interpreter/src/lib.rs b/crates/kirin-derive-interpreter/src/lib.rs index be414edab7..32277f4b26 100644 --- a/crates/kirin-derive-interpreter/src/lib.rs +++ b/crates/kirin-derive-interpreter/src/lib.rs @@ -20,7 +20,7 @@ pub fn derive_interpretable(input: TokenStream) -> TokenStream { } } -/// Derive `FunctionEntry` for a `#[wraps]` wrapper enum. Variants marked +/// Derive `FunctionEntry` for a `#[wraps]` wrapper enum. Variants marked /// `#[callable]` delegate; all other variants report `NotCallable`. #[proc_macro_derive(FunctionEntry, attributes(wraps, callable, kirin, interpret))] pub fn derive_function_entry(input: TokenStream) -> TokenStream { 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 8524f2e6d5..6f6c1987fa 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 @@ -3,33 +3,17 @@ source: crates/kirin-derive-interpreter/src/function_entry.rs expression: generate_function_entry_code(input) --- #[automatically_derived] -impl ::kirin_interpreter::FunctionEntry<__EntryI> for Lexical +impl ::kirin_interpreter::FunctionEntry for Lexical where - __EntryI: ::kirin_interpreter::Interp, - Function: ::kirin_interpreter::FunctionEntry<__EntryI>, - Lambda: ::kirin_interpreter::FunctionEntry<__EntryI>, + Function: ::kirin_interpreter::FunctionEntry, + Lambda: ::kirin_interpreter::FunctionEntry, { - fn function_entry( - &self, - args: ::kirin::ir::Product<<__EntryI as ::kirin_interpreter::Interp>::Value>, - interp: &mut __EntryI, - ) -> Result< - ::kirin_interpreter::CallableBody<<__EntryI as ::kirin_interpreter::Interp>::Value>, - <__EntryI as ::kirin_interpreter::Interp>::Error, - > { + fn function_entry(&self) -> Option<::kirin_interpreter::CallableBody> { match self { - Self::Function(field_0) => field_0.function_entry(args, interp), - Self::Call { .. } => Err(<__EntryI as ::kirin_interpreter::Interp>::Error::from( - ::kirin_interpreter::InterpreterError::NotCallable( - <__EntryI as ::kirin_interpreter::Interp>::statement(interp), - ), - )), - Self::Lambda(field_0) => field_0.function_entry(args, interp), - Self::Return { .. } => Err(<__EntryI as ::kirin_interpreter::Interp>::Error::from( - ::kirin_interpreter::InterpreterError::NotCallable( - <__EntryI as ::kirin_interpreter::Interp>::statement(interp), - ), - )), + Self::Function(field_0) => field_0.function_entry(), + Self::Call { .. } => None, + Self::Lambda(field_0) => field_0.function_entry(), + Self::Return { .. } => None, } } } diff --git a/crates/kirin-function/src/interpreter.rs b/crates/kirin-function/src/interpreter.rs index 8cc8c3c80d..ac9fcbd668 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, CallableBody, Callee, ClassicLiveness, ClassicLivenessInterp, DemandInterp, - DenseBackwardEffect, ForwardEval, FunctionEntry, Interp, Interpretable, InterpreterError, + DenseBackwardEffect, ForwardEval, FunctionEntry, Interpretable, InterpreterError, SparseForwardEffect, SparseForwardInterp, StrongDemand, }; @@ -89,31 +89,21 @@ where } } -impl FunctionEntry for Function +impl FunctionEntry for Function where - I: Interp, T: CompileTimeValue, { - fn function_entry( - &self, - args: Product, - _interp: &mut I, - ) -> Result, I::Error> { - Ok(CallableBody::new(*self.cfg()).args(args)) + fn function_entry(&self) -> Option { + Some(CallableBody::new(*self.cfg())) } } -impl FunctionEntry for Lambda +impl FunctionEntry for Lambda where - I: Interp, T: CompileTimeValue, { - fn function_entry( - &self, - args: Product, - _interp: &mut I, - ) -> Result, I::Error> { - Ok(CallableBody::new(*self.cfg()).args(args)) + fn function_entry(&self) -> Option { + Some(CallableBody::new(*self.cfg())) } } diff --git a/crates/kirin-interpreter/src/core/dispatch.rs b/crates/kirin-interpreter/src/core/dispatch.rs index bf2cb45934..a45fd879e0 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 kirin_ir::{Dialect, StageInfo, StageMeta, Statement}; -use crate::{CallableBody, Interp}; +use crate::{CallableBody, Interp, InterpreterError}; /// Statement semantics. The single trait dialect authors implement. /// @@ -22,12 +22,13 @@ pub trait Interpretable: Dialect { /// when the function is invoked. Derived on language enums with /// `#[derive(FunctionEntry)]` where `#[callable]` marks the variants that wrap /// callable statements. -pub trait FunctionEntry: Dialect { - fn function_entry( - &self, - args: Product, - interp: &mut I, - ) -> Result, I::Error>; +pub trait FunctionEntry: Dialect { + /// Return this definition's callable body, or `None` when the statement is + /// not callable. + /// + /// Body discovery is structural and therefore independent of an engine's + /// value domain and boundary inputs. + fn function_entry(&self) -> Option; } /// Monomorphic statement dispatch over a stage enum. @@ -49,18 +50,13 @@ pub trait InterpDispatch: StageMeta { interp: &mut I, ) -> Result; - fn dispatch_function_entry( - &self, - definition: Statement, - args: Product, - interp: &mut I, - ) -> Result, I::Error>; + fn dispatch_function_entry(&self, definition: Statement) -> Result; } impl InterpDispatch for StageInfo where I: Interp, - L: Dialect + Interpretable::Semantics> + FunctionEntry, + L: Dialect + Interpretable::Semantics> + FunctionEntry, { fn dispatch_statement( &self, @@ -71,13 +67,10 @@ where definition.interpret(interp) } - fn dispatch_function_entry( - &self, - definition: Statement, - args: Product, - interp: &mut I, - ) -> Result, I::Error> { + fn dispatch_function_entry(&self, definition: Statement) -> Result { let callable = definition.definition(self).clone(); - callable.function_entry(args, interp) + callable + .function_entry() + .ok_or_else(|| I::Error::from(InterpreterError::NotCallable(definition))) } } diff --git a/crates/kirin-interpreter/src/core/effect.rs b/crates/kirin-interpreter/src/core/effect.rs index 03a74ace87..59a0548fb4 100644 --- a/crates/kirin-interpreter/src/core/effect.rs +++ b/crates/kirin-interpreter/src/core/effect.rs @@ -132,35 +132,28 @@ impl From for Callee { } } -/// The body a callable statement enters when invoked, plus the entry -/// arguments bound to its boundary (block parameters / graph ports). +/// The body a callable statement enters when invoked. /// /// This is the function-call entry descriptor — the call mechanism, not a /// structured-control abstraction. A [`FunctionEntry`](crate::FunctionEntry) /// 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 call-body traversal, rejected with +/// body kind and binds its own boundary input. Callable-body discovery is +/// deliberately value-independent: concrete execution and forward abstract +/// interpretation carry different argument domains, while backward analyses +/// have no argument product at this boundary. 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 call-body traversal, +/// rejected with /// [`InterpreterError::NoDefaultWalker`](crate::InterpreterError) when no /// policy is provided. -pub struct CallableBody { +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct CallableBody { pub body: Body, - pub args: Product, } -impl CallableBody { - /// A callable body, with no entry arguments yet. +impl CallableBody { pub fn new(body: impl Into) -> Self { - Self { - body: body.into(), - args: Product::new(), - } - } - - /// 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 + Self { body: body.into() } } } diff --git a/crates/kirin-interpreter/src/core/error.rs b/crates/kirin-interpreter/src/core/error.rs index 74a886bd86..f57bfd85cf 100644 --- a/crates/kirin-interpreter/src/core/error.rs +++ b/crates/kirin-interpreter/src/core/error.rs @@ -37,8 +37,8 @@ pub enum InterpreterError { function: StagedFunction, count: usize, }, - #[error("missing call target {0:?}")] - MissingCallTarget(Symbol), + #[error("missing call symbol {0:?}")] + MissingCallSymbol(Symbol), #[error("cfg has no entry block")] EmptyCFG, #[error("body {0:?} has no default walker in this engine")] diff --git a/crates/kirin-interpreter/src/core/frame.rs b/crates/kirin-interpreter/src/core/frame.rs index 6b02742f43..1293382c6c 100644 --- a/crates/kirin-interpreter/src/core/frame.rs +++ b/crates/kirin-interpreter/src/core/frame.rs @@ -330,20 +330,13 @@ pub trait CallServices: Env { fn alloc_env(&mut self) -> EnvIndex; /// Free an activation record. fn free_env(&mut self, index: EnvIndex) -> Result<(), Self::Error>; - /// Resolve a callee to a concrete function target via the engine's linker. - fn resolve_call( + /// Resolve a callee and discover its value-independent callable body at + /// the selected target stage. + fn resolve_callable( &self, stage: CompileStage, callee: &Callee, - ) -> Result; - /// Build the [`CallableBody`] a callable statement enters on invocation. - fn enter_function( - &mut self, - stage: CompileStage, - definition: Statement, - args: Product, - index: EnvIndex, - ) -> Result, Self::Error>; + ) -> Result<(FunctionTarget, CallableBody), Self::Error>; } /// An interpreter engine capable of running the complete standard **concrete** @@ -375,7 +368,7 @@ impl ForwardFrameEngine for T where /// not** [`CallServices`] or [`CFGQueries`]. An abstract engine does not descend /// into a callee (it [summarizes](Self::summarize_call) the call), so requiring /// it to expose concrete activation allocation, activation cleanup, -/// `resolve_call`, and `enter_function` would be demanding a call convention it +/// `resolve_callable` would be demanding a call convention it /// never performs. `cfg_entry` is likewise absent: the forward abstract engine /// reaches a callable body's entry block through [`Owner`](crate::Owner) seeding /// in the fixpoint driver, not by asking a frame to enter a CFG. A frame that diff --git a/crates/kirin-interpreter/src/core/linker.rs b/crates/kirin-interpreter/src/core/linker.rs index 24a30c63ce..dae3f4ae16 100644 --- a/crates/kirin-interpreter/src/core/linker.rs +++ b/crates/kirin-interpreter/src/core/linker.rs @@ -1,7 +1,7 @@ use kirin_ir::{CompileStage, Pipeline, SpecializedFunction, StageMeta, Statement}; use super::query; -use crate::{Callee, InterpreterError, StageQuery}; +use crate::{CallableBody, Callee, Interp, InterpDispatch, InterpreterError, StageQuery}; /// A fully resolved call target: the stage to execute in, the specialization, /// and its callable definition statement. @@ -28,6 +28,33 @@ pub trait Linker { ) -> Result; } +/// Run the framework's common callable-root protocol. +/// +/// Linking selects a concrete target; callable-entry dispatch then discovers +/// its body in the target's stage. Engines invoke this operation before +/// applying their own boundary inputs (runtime arguments, abstract arguments, +/// or analysis-specific seeds). +pub(crate) fn resolve_callable( + pipeline: &Pipeline, + linker: &Lk, + caller_stage: CompileStage, + callee: &Callee, +) -> Result<(FunctionTarget, CallableBody), I::Error> +where + I: Interp, + S: StageMeta + InterpDispatch, + Lk: Linker, +{ + let target = linker + .resolve(pipeline, caller_stage, callee) + .map_err(I::Error::from)?; + let info = pipeline + .stage(target.stage) + .ok_or_else(|| I::Error::from(InterpreterError::MissingStage(target.stage)))?; + let body = info.dispatch_function_entry(target.definition)?; + Ok((target, body)) +} + /// Resolve calls within the caller's stage only (the default). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct SameStageLinker; @@ -47,7 +74,7 @@ fn callee_function( match *callee { Callee::Named(symbol) => { let name = query::resolve_symbol_name(pipeline, caller_stage, symbol)? - .ok_or(InterpreterError::MissingCallTarget(symbol))?; + .ok_or(InterpreterError::MissingCallSymbol(symbol))?; let function = pipeline .lookup_function_by_name(&name) .ok_or(InterpreterError::MissingFunctionName(name))?; @@ -64,7 +91,7 @@ fn target_at_stage( callee: &Callee, ) -> Result { let specialized = match *callee { - Callee::Named(symbol) => return Err(InterpreterError::MissingCallTarget(symbol)), + Callee::Named(symbol) => return Err(InterpreterError::MissingCallSymbol(symbol)), Callee::Function(function) => { let staged = pipeline .function_info(function) diff --git a/crates/kirin-interpreter/src/core/query.rs b/crates/kirin-interpreter/src/core/query.rs index 401df3b985..dc70acf2b6 100644 --- a/crates/kirin-interpreter/src/core/query.rs +++ b/crates/kirin-interpreter/src/core/query.rs @@ -416,6 +416,7 @@ where } /// Blocks directly selected as dense fixpoint owners by an analysis root. +/// Graph bodies have no block-owner interpretation and fail explicitly. pub struct DirectBodyBlocks(pub Body); impl StageAction for DirectBodyBlocks @@ -434,7 +435,9 @@ where Ok(match self.0 { Body::CFG(cfg) => cfg.blocks(info).collect(), Body::Block(block) => vec![block], - Body::DiGraph(_) | Body::UnGraph(_) => Vec::new(), + body @ (Body::DiGraph(_) | Body::UnGraph(_)) => { + return Err(InterpreterError::NoDefaultWalker(body)); + } }) } } diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs b/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs index 47734259a7..66d5a66181 100644 --- a/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs +++ b/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs @@ -12,10 +12,11 @@ use super::{BodyFrameEntry, CallBodyTraversal, Completion, DefaultCallBodyTraver /// A `CallFrame` owns the whole activation lifecycle that representation /// walkers deliberately don't: /// -/// 1. resolve the callee through the engine's [`Linker`](crate::Linker); +/// 1. resolve the callee and discover its value-independent body through the +/// common callable-root protocol (`Linker` then `FunctionEntry` at the +/// target stage); /// 2. allocate the callee activation; -/// 3. ask [`FunctionEntry`](crate::FunctionEntry) for the callable body -/// descriptor ([`CallableBody`](crate::CallableBody)); +/// 3. retain the concrete argument product for the selected body walker; /// 4. select the entry frame for the closed [`Body`] variant — **delegated to /// the `T` traversal** ([`CallBodyTraversal`]), which defaults to /// [`DefaultCallBodyTraversal`]: `CFG` → `CFGFrame`, `Block` → `BlockFrame`, @@ -134,9 +135,8 @@ where args, dest, } => { - let target = interp.resolve_call(resolve_stage, &callee)?; + let (target, entry) = interp.resolve_callable(resolve_stage, &callee)?; let index = interp.alloc_env(); - let entry = interp.enter_function(target.stage, target.definition, 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 traversal. @@ -149,25 +149,25 @@ where stage: target.stage, index, body: cfg, - args: entry.args, + args, })?, Body::Block(block) => T::from_block(BodyFrameEntry { stage: target.stage, index, body: block, - args: entry.args, + args, })?, Body::DiGraph(graph) => T::from_digraph(BodyFrameEntry { stage: target.stage, index, body: graph, - args: entry.args, + args, })?, Body::UnGraph(graph) => T::from_ungraph(BodyFrameEntry { stage: target.stage, index, body: graph, - args: entry.args, + args, })?, }; Ok(FrameEffect::Push { diff --git a/crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs b/crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs index 99d0b15252..6238829c3a 100644 --- a/crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs +++ b/crates/kirin-interpreter/src/engines/concrete/frames/protocol.rs @@ -54,11 +54,11 @@ pub struct BodyFrameEntry { /// This is the *body-entry* half of [`CallFrame`](crate::CallFrame), split out so the two /// concerns are separately replaceable: /// -/// - the **call convention** — resolve the callee, allocate its activation, -/// ask [`FunctionEntry`](crate::FunctionEntry) for the body, suspend, +/// - the **call convention** — resolve the callee and value-independent body, +/// allocate its activation, bind the concrete boundary arguments, suspend, /// validate the completion kind, free the activation exactly once, bind the -/// results — stays in [`CallFrame`](crate::CallFrame) and is *not* configurable. It is where -/// double-frees would live. +/// results — stays in [`CallFrame`](crate::CallFrame) and is *not* +/// configurable. It is where double-frees would live. /// - the **walker choice** — which frame traverses that body — is this trait. /// /// So a language can say "walk my CFGs with my own scheduler" without forking diff --git a/crates/kirin-interpreter/src/engines/concrete/interp.rs b/crates/kirin-interpreter/src/engines/concrete/interp.rs index 329daec888..e6925ad085 100644 --- a/crates/kirin-interpreter/src/engines/concrete/interp.rs +++ b/crates/kirin-interpreter/src/engines/concrete/interp.rs @@ -4,7 +4,7 @@ use kirin_ir::{ Block, CFG, CompileStage, Pipeline, Product, SSAValue, StageMeta, Statement, Symbol, }; -use crate::core::query; +use crate::core::{linker::resolve_callable, query}; use crate::{ BlockQueries, CFGQueries, CallServices, CallableBody, Callee, Completion, DiGraphQueries, Env, EnvIndex, EnvStackStore, ForwardEval, Frame, FunctionTarget, Interp, InterpDispatch, @@ -153,31 +153,12 @@ where self.store.free(index).map_err(E::from) } - fn resolve_call(&self, stage: CompileStage, callee: &Callee) -> Result { - self.linker - .resolve(self.pipeline, stage, callee) - .map_err(E::from) - } - - fn enter_function( - &mut self, + fn resolve_callable( + &self, stage: CompileStage, - definition: Statement, - args: Product, - index: EnvIndex, - ) -> Result, E> { - let pipeline = self.pipeline; - let info = pipeline - .stage(stage) - .ok_or_else(|| E::from(InterpreterError::MissingStage(stage)))?; - let previous = self.location.replace(InterpLocation { - stage, - statement: definition, - index, - }); - let result = info.dispatch_function_entry(definition, args, self); - self.location = previous; - result + callee: &Callee, + ) -> Result<(FunctionTarget, CallableBody), E> { + resolve_callable::(self.pipeline, &self.linker, stage, callee) } } diff --git a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs index d3f6eecaaf..f28b2a7b47 100644 --- a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs @@ -54,13 +54,14 @@ use kirin_ir::{ use super::frames::DenseBlockFrame; use crate::Body; -use crate::core::query; +use crate::core::{linker::resolve_callable as resolve_callable_root, query}; use crate::engines::sparse_backward::BodyScope; use crate::{ - AbstractInterpreter, BackwardSummaryDeps, ClassicLiveness, DenseBackwardSemantic, EnvIndex, - FactStore, FixpointProfile, Frame, Interp, InterpDispatch, InterpLocation, InterpreterError, - OwnerSemantics, ProgramPoint, Scoped, StageQuery, StandardFixpointInterpreter, Summary, - SummaryDependency, SummaryDependencyIndex, SummaryEffect, TerminatorArgs, + AbstractInterpreter, BackwardSummaryDeps, Callee, ClassicLiveness, DenseBackwardSemantic, + EnvIndex, FactStore, FixpointProfile, Frame, Interp, InterpDispatch, InterpLocation, + InterpreterError, Linker, OwnerSemantics, ProgramPoint, SameStageLinker, Scoped, StageQuery, + StandardFixpointInterpreter, Summary, SummaryDependency, SummaryDependencyIndex, SummaryEffect, + TerminatorArgs, }; // =========================================================================== @@ -626,8 +627,8 @@ where /// /// ```ignore /// let mut analysis = DenseBackwardInterpreter::::new(&pipeline); -/// analysis.analyze(stage, cfg)?; -/// let point = Scoped::new((stage, Body::CFG(cfg)), ProgramPoint::BlockEntry(block)); +/// let scope = analysis.analyze(stage, callee)?; +/// let point = Scoped::new(scope, ProgramPoint::BlockEntry(block)); /// let live_in = analysis.point_facts(point); /// ``` pub struct DenseBackwardInterpreter< @@ -636,6 +637,7 @@ pub struct DenseBackwardInterpreter< V, E = InterpreterError, F = DenseBlockFrame, + Lk = SameStageLinker, Sem = ClassicLiveness, > where V: Clone + PartialEq + Lattice, @@ -644,9 +646,10 @@ pub struct DenseBackwardInterpreter< Sem: DenseBackwardSemantic, { driver: DenseBackwardDriver<'ir, S, V, E, F, Sem>, + linker: Lk, } -impl<'ir, S, V, E, F, Sem> DenseBackwardInterpreter<'ir, S, V, E, F, Sem> +impl<'ir, S, V, E, F, Sem> DenseBackwardInterpreter<'ir, S, V, E, F, SameStageLinker, Sem> where S: StageMeta, V: Clone + PartialEq + Lattice + HasBottom, @@ -661,6 +664,26 @@ where (), BackwardSummaryDeps::new(), ), + linker: SameStageLinker, + } + } +} + +impl<'ir, S, V, E, F, Lk, Sem> DenseBackwardInterpreter<'ir, S, V, E, F, Lk, Sem> +where + S: StageMeta, + V: Clone + PartialEq + Lattice + HasBottom, + E: From, + Sem: DenseBackwardSemantic, +{ + /// Replace the calling convention used by the canonical callable root. + pub fn with_linker( + self, + linker: Lk2, + ) -> DenseBackwardInterpreter<'ir, S, V, E, F, Lk2, Sem> { + DenseBackwardInterpreter { + driver: self.driver, + linker, } } @@ -711,11 +734,12 @@ where } } -impl<'ir, S, V, E, F, Sem> DenseBackwardInterpreter<'ir, S, V, E, F, Sem> +impl<'ir, S, V, E, F, Lk, Sem> DenseBackwardInterpreter<'ir, S, V, E, F, Lk, Sem> where S: StageMeta + StageQuery + InterpDispatch>, V: Clone + PartialEq + Lattice + HasBottom + DenseBackwardState, E: From, + Lk: Linker, Sem: DenseBackwardSemantic, F: Frame, F, Completion = DenseBackwardCompletion> + From>, @@ -732,22 +756,35 @@ where .map_err(E::from) } - /// 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, body: impl Into) -> Result<(), E> { - let body = body.into(); - let scope = (stage, body); - let blocks = self.direct_body_blocks(stage, body)?; + /// Resolve `callee`, seed every block selected by its CFG or Block body, + /// and drain the block-boundary worklist. Dependencies are discovered from + /// terminator edges; unsupported graph roots fail before solving. + pub fn analyze(&mut self, stage: CompileStage, callee: Callee) -> Result { + let (target, entry) = resolve_callable_root::< + DenseBackwardTransfer<'ir, S, V, E, F, Sem>, + _, + _, + >( + self.driver.inner().pipeline(), &self.linker, stage, &callee + )?; + let body = entry.body; + let scope = (target.stage, body); + let blocks = self.direct_body_blocks(target.stage, body)?; let owners: Vec> = blocks .iter() .copied() .map(|block| Scoped::new(scope, block)) .collect(); let pipeline = self.driver.inner().pipeline(); - self.driver = Self::new(pipeline).driver; + self.driver = StandardFixpointInterpreter::with_dependency_index( + DenseBackwardTransfer::new(pipeline), + FactStore::new(), + (), + BackwardSummaryDeps::new(), + ); let mut semantics = DenseBackwardSemantics; - self.driver.solve_many(&mut semantics, owners) + self.driver.solve_many(&mut semantics, owners)?; + Ok(scope) } } diff --git a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs index 52f931b805..9675eda605 100644 --- a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs @@ -53,12 +53,12 @@ use kirin_ir::{ SSAKind, SSAValue, StageMeta, Statement, }; -use crate::core::query; +use crate::core::{linker::resolve_callable as resolve_callable_root, query}; use crate::{ - AbstractInterpreter, Body, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, - InterpDispatch, InterpLocation, InterpreterError, OwnerSemantics, OwnerSummaryDeps, Scoped, - SparseBackwardSemantic, SparseStore, StageQuery, StandardFixpointInterpreter, StrongDemand, - Summary, SummaryEffect, TerminatorArgs, + AbstractInterpreter, Body, Callee, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, + InterpDispatch, InterpLocation, InterpreterError, Linker, OwnerSemantics, OwnerSummaryDeps, + SameStageLinker, Scoped, SparseBackwardSemantic, SparseStore, StageQuery, + StandardFixpointInterpreter, StrongDemand, Summary, SummaryEffect, TerminatorArgs, }; /// The scope a body-level backward analysis qualifies its facts with. @@ -532,19 +532,26 @@ where /// /// ```ignore /// let mut analysis = SparseBackwardInterpreter::::new(&pipeline); -/// analysis.analyze(stage, cfg)?; -/// let demanded = analysis.is_demanded(stage, cfg, value); +/// let scope = analysis.analyze(stage, callee)?; +/// let demanded = analysis.is_demanded(scope.0, scope.1, value); /// ``` -pub struct SparseBackwardInterpreter<'ir, S: StageMeta, V, E = InterpreterError, Sem = StrongDemand> -where +pub struct SparseBackwardInterpreter< + 'ir, + S: StageMeta, + V, + E = InterpreterError, + Lk = SameStageLinker, + Sem = StrongDemand, +> where V: Clone + PartialEq + Lattice, E: From, Sem: SparseBackwardSemantic, { driver: SparseBackwardDriver<'ir, S, V, E, Sem>, + linker: Lk, } -impl<'ir, S, V, E, Sem> SparseBackwardInterpreter<'ir, S, V, E, Sem> +impl<'ir, S, V, E, Sem> SparseBackwardInterpreter<'ir, S, V, E, SameStageLinker, Sem> where S: StageMeta, V: Clone + PartialEq + Lattice, @@ -559,6 +566,26 @@ where (), OwnerSummaryDeps::new(), ), + linker: SameStageLinker, + } + } +} + +impl<'ir, S, V, E, Lk, Sem> SparseBackwardInterpreter<'ir, S, V, E, Lk, Sem> +where + S: StageMeta, + V: Clone + PartialEq + Lattice, + E: From, + Sem: SparseBackwardSemantic, +{ + /// Replace the calling convention used by the canonical callable root. + pub fn with_linker( + self, + linker: Lk2, + ) -> SparseBackwardInterpreter<'ir, S, V, E, Lk2, Sem> { + SparseBackwardInterpreter { + driver: self.driver, + linker, } } @@ -603,23 +630,33 @@ where } } -impl<'ir, S, V, E, Sem> SparseBackwardInterpreter<'ir, S, V, E, Sem> +impl<'ir, S, V, E, Lk, Sem> SparseBackwardInterpreter<'ir, S, V, E, Lk, Sem> where S: StageMeta + StageQuery + InterpDispatch>, V: Clone + PartialEq + Lattice + HasBottom, E: From, + Lk: Linker, Sem: SparseBackwardSemantic, { - /// Run the demand fixpoint over `body` in `stage`. + /// Resolve `callee` and run the demand fixpoint over its callable body. /// /// **Prepass**: walk the body's containment hierarchy, running 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); + pub fn analyze(&mut self, stage: CompileStage, callee: Callee) -> Result { + let (target, entry) = resolve_callable_root::, _, _>( + self.driver.inner().pipeline(), + &self.linker, + stage, + &callee, + )?; + let body = entry.body; + if matches!(body, Body::DiGraph(_) | Body::UnGraph(_)) { + return Err(E::from(InterpreterError::NoDefaultWalker(body))); + } + let scope = (target.stage, body); *self.driver.store_mut() = BackwardAnalysisState { scope: Some(scope) }; let mut semantics = SparseBackwardSemantics; @@ -633,11 +670,12 @@ where if !visited.insert(body) { continue; } - let contents = query::body_contents(self.driver.inner().pipeline(), stage, body)?; + let contents = + query::body_contents(self.driver.inner().pipeline(), target.stage, body)?; bodies.extend(contents.children); for statement in contents.statements { let SparseBackwardEffect::Demands(demands) = - self.driver.run_statement(stage, statement)?; + self.driver.run_statement(target.stage, statement)?; seeds.extend(demands); } } @@ -650,7 +688,8 @@ where DemandSummary(fact), )?; } - self.driver.drain_worklist(&mut semantics) + self.driver.drain_worklist(&mut semantics)?; + Ok(scope) } /// `true` iff `value` carries a non-bottom demand fact under the scope. diff --git a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs index a0821ad934..9a34305826 100644 --- a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs @@ -40,7 +40,7 @@ use kirin_ir::{ StageMeta, Statement, Symbol, Widen, }; -use crate::core::query; +use crate::core::{linker::resolve_callable as resolve_callable_root, query}; use crate::{ AbstractBlockFrame, AbstractCompletion, AbstractDiGraphFrame, AbstractInterpreter, BlockQueries, Body, CFGQueries, CallEffect, CallServices, CallableBody, Callee, DiGraphQueries, @@ -149,7 +149,7 @@ impl Owner { #[derive(Clone)] pub struct FunctionSummary { /// `(stage, body)` — set when the owner is first seeded from a call site. - meta: Option<(CompileStage, Statement)>, + meta: Option<(CompileStage, Body)>, entry: Product, entry_joins: usize, ret: Option>, @@ -304,7 +304,7 @@ enum ForwardUpdate { FunctionEntry { key: K, stage: CompileStage, - definition: Statement, + body: Body, args: Product, }, /// Merge a return contribution into a function context's return (join); on @@ -626,31 +626,12 @@ where self.store.free(index).map_err(E::from) } - fn resolve_call(&self, stage: CompileStage, callee: &Callee) -> Result { - self.linker - .resolve(self.pipeline, stage, callee) - .map_err(E::from) - } - - fn enter_function( - &mut self, + fn resolve_callable( + &self, stage: CompileStage, - definition: Statement, - args: Product, - index: EnvIndex, - ) -> Result, E> { - let pipeline = self.pipeline; - let info = pipeline - .stage(stage) - .ok_or_else(|| E::from(InterpreterError::MissingStage(stage)))?; - let previous = self.location.replace(InterpLocation { - stage, - statement: definition, - index, - }); - let result = info.dispatch_function_entry(definition, args, self); - self.location = previous; - result + callee: &Callee, + ) -> Result<(FunctionTarget, CallableBody), E> { + resolve_callable_root::(self.pipeline, &self.linker, stage, callee) } } @@ -768,19 +749,12 @@ where self.inner_mut().free_env(index) } - fn resolve_call(&self, stage: CompileStage, callee: &Callee) -> Result { - self.inner().resolve_call(stage, callee) - } - - fn enter_function( - &mut self, + fn resolve_callable( + &self, stage: CompileStage, - definition: Statement, - args: Product, - index: EnvIndex, - ) -> Result, E> { - self.inner_mut() - .enter_function(stage, definition, args, index) + callee: &Callee, + ) -> Result<(FunctionTarget, CallableBody), E> { + self.inner().resolve_callable(stage, callee) } } @@ -908,13 +882,13 @@ where results, } = call; let resolve_stage = call_stage.unwrap_or(stage); - let target = self.inner().resolve_call(resolve_stage, &callee)?; + let (target, entry) = self.inner().resolve_callable(resolve_stage, &callee)?; let key = self.inner_mut().key(&target, &args); self.apply_update(ForwardUpdate::FunctionEntry { key: key.clone(), stage: target.stage, - definition: target.definition, + body: entry.body, args, })?; @@ -964,7 +938,7 @@ where ForwardUpdate::FunctionEntry { key, stage, - definition, + body, args, } => { let owner = Owner::Function(key.clone()); @@ -972,7 +946,7 @@ where self.summaries_mut().insert( owner.clone(), ForwardSummary::Function(FunctionSummary { - meta: Some((stage, definition)), + meta: Some((stage, body)), entry: args, entry_joins: 0, ret: None, @@ -1004,7 +978,7 @@ where changed }; if changed { - self.seed_entry_block(&key, stage, definition)?; + self.seed_entry_block(&key, stage, body)?; } Ok(()) } @@ -1133,23 +1107,18 @@ where &mut self, key: &

>::Key, stage: CompileStage, - definition: Statement, + body: Body, ) -> Result<(), E> { - let env = match self.store().env(key) { - Some(env) => env, - None => { - let env = self.alloc_env(); - self.store_mut().set_env(key.clone(), env); - env - } - }; + if self.store().env(key).is_none() { + let env = self.alloc_env(); + self.store_mut().set_env(key.clone(), env); + } let entry_args = self .summary(&Owner::Function(key.clone())) .and_then(|info| info.as_function()) .map(|function| function.entry.clone()) .expect("function summary present"); - let entry = self.enter_function(stage, definition, entry_args, env)?; - let owner = match entry.body { + let owner = match body { Body::CFG(cfg) => Owner::Block { function: key.clone(), block: self @@ -1182,7 +1151,7 @@ where } self.apply_update(ForwardUpdate::OwnerEntry { owner, - args: entry.args, + args: entry_args, }) } } @@ -1598,14 +1567,14 @@ where callee: Callee, args: impl IntoIterator, ) -> Result, E> { - let target = self.driver.inner().resolve_call(stage, &callee)?; + let (target, entry) = self.driver.inner().resolve_callable(stage, &callee)?; let args: Product = args.into_iter().collect(); let key = self.driver.inner_mut().key(&target, &args); self.driver.apply_update(ForwardUpdate::FunctionEntry { key: key.clone(), stage: target.stage, - definition: target.definition, + body: entry.body, args, })?; diff --git a/crates/kirin-liveness/Cargo.toml b/crates/kirin-liveness/Cargo.toml index 1f8272d5a9..27cc5e8546 100644 --- a/crates/kirin-liveness/Cargo.toml +++ b/crates/kirin-liveness/Cargo.toml @@ -12,6 +12,7 @@ kirin = { workspace = true } kirin-arith = { workspace = true } kirin-test-languages = { workspace = true, features = [ "arith-function-language", + "graph-function-language", "parser", "interpreter", ] } diff --git a/crates/kirin-liveness/src/lib.rs b/crates/kirin-liveness/src/lib.rs index 49229e44a2..e49abd11e2 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, cfg)?; +//! let result = kirin_liveness::analyze_demand(&pipeline, stage, callee)?; //! assert!(result.is_demanded(some_value)); //! ``` @@ -26,50 +26,53 @@ pub use live::{Live, LiveSet}; pub use result::{DemandResult, DenseLivenessResult}; use kirin_interpreter::{ - Body, DenseBackwardCompletion, DenseBackwardDriver, DenseBackwardInterpreter, + Callee, DenseBackwardCompletion, DenseBackwardDriver, DenseBackwardInterpreter, DenseBackwardTransfer, DenseBlockFrame, Frame, InterpDispatch, InterpreterError, - SparseBackwardDriver, SparseBackwardInterpreter, StageQuery, + SameStageLinker, SparseBackwardDriver, SparseBackwardInterpreter, StageQuery, }; use kirin_ir::{CompileStage, Pipeline, StageMeta}; /// The sparse backward demand engine instantiated at the [`Live`] lattice: /// strong liveness. -pub type Demand<'ir, S, E = InterpreterError> = SparseBackwardInterpreter<'ir, S, Live, E>; +pub type Demand<'ir, S, E = InterpreterError, Lk = SameStageLinker> = + SparseBackwardInterpreter<'ir, S, Live, E, Lk>; /// The dense backward engine instantiated at [`LiveSet`] point states: /// classic per-program-point liveness. A language with structured dialects /// supplies its own private stack-item `F` embedding the dialect's dense frames. -pub type DenseLiveness<'ir, S, E = InterpreterError, F = DenseBlockFrame> = - DenseBackwardInterpreter<'ir, S, LiveSet, E, F>; +pub type DenseLiveness< + 'ir, + S, + E = InterpreterError, + F = DenseBlockFrame, + Lk = SameStageLinker, +> = DenseBackwardInterpreter<'ir, S, LiveSet, E, F, Lk>; -/// Run strong liveness (sparse backward demand) over `body` in `stage`. TODO: -/// analyze() should accept Callee similar to concrete and constprop's -/// analyze(CompileStage, Callee, args) instead of Body, so that the caller can -/// select a specialization and pass its args. +/// Resolve `callee` and run strong liveness (sparse backward demand) over its +/// callable body. pub fn analyze_demand<'ir, S>( pipeline: &'ir Pipeline, stage: CompileStage, - body: impl Into, + callee: Callee, ) -> Result where S: StageMeta + StageQuery + InterpDispatch>, { - let body = body.into(); let mut engine = Demand::::new(pipeline); - engine.analyze(stage, body)?; - Ok(DemandResult::from_engine(&engine, stage, body)) + let scope = engine.analyze(stage, callee)?; + Ok(DemandResult::from_engine(&engine, scope)) } -/// Run classic per-point liveness (dense backward) over `body` in `stage`, +/// Resolve `callee` and run classic per-point liveness over its body, /// with the standard reverse block walker. Languages with structured dialects /// select their stack-item type through /// [`analyze_dense_with_frame`] instead. pub fn analyze_dense<'ir, S>( pipeline: &'ir Pipeline, stage: CompileStage, - body: impl Into, + callee: Callee, ) -> Result where S: StageMeta @@ -84,18 +87,20 @@ where >, >, { - analyze_dense_with_frame::>(pipeline, stage, body) + analyze_dense_with_frame::>( + pipeline, stage, callee, + ) } -/// Run classic per-point liveness (dense backward) over `body` in `stage` -/// with a caller-selected stack-item type `F` — the entry point for +/// Resolve `callee` and run classic per-point liveness over its body with a +/// caller-selected stack-item type `F` — the entry point for /// languages whose structured dialects require a language-specific private /// composition. 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, + callee: Callee, ) -> Result where S: StageMeta @@ -107,8 +112,7 @@ where Completion = DenseBackwardCompletion, > + From>, { - let body = body.into(); let mut engine = DenseLiveness::::new(pipeline); - engine.analyze(stage, body)?; - Ok(DenseLivenessResult::from_engine(&engine)) + let scope = engine.analyze(stage, callee)?; + Ok(DenseLivenessResult::from_engine(&engine, scope)) } diff --git a/crates/kirin-liveness/src/result.rs b/crates/kirin-liveness/src/result.rs index 6d7dbc42d8..a1581efc32 100644 --- a/crates/kirin-liveness/src/result.rs +++ b/crates/kirin-liveness/src/result.rs @@ -2,34 +2,42 @@ //! per-point sets (classic liveness), plus their composition. use kirin_interpreter::{ - Body, BodyScope, DenseBackwardInterpreter, FactStore, InterpreterError, ProgramPoint, Scoped, + BodyScope, DenseBackwardInterpreter, FactStore, InterpreterError, ProgramPoint, Scoped, SparseBackwardInterpreter, }; -use kirin_ir::{CompileStage, Lattice, SSAValue, StageMeta}; +use kirin_ir::{Lattice, SSAValue, StageMeta}; use crate::live::{Live, LiveSet}; /// The result of [`analyze_demand`](crate::analyze_demand): the demanded SSA /// values. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct DemandResult { + root_scope: BodyScope, demanded: LiveSet, } impl DemandResult { - pub(crate) fn from_engine( - engine: &SparseBackwardInterpreter<'_, S, Live, InterpreterError>, - stage: CompileStage, - body: impl Into, + pub(crate) fn from_engine( + engine: &SparseBackwardInterpreter<'_, S, Live, InterpreterError, Lk>, + root_scope: BodyScope, ) -> Self { // The engine's sparse fact view; the demand set is its live support. - let facts = engine.fact_store(stage, body); + let facts = engine.fact_store(root_scope.0, root_scope.1); let demanded = facts .iter() .filter(|(_, fact)| fact.is_live()) .map(|(value, _)| value) .collect(); - Self { demanded } + Self { + root_scope, + demanded, + } + } + + /// The resolved `(target stage, callable body)` analyzed by this result. + pub fn root_scope(&self) -> BodyScope { + self.root_scope } /// The demanded SSA values (the strong-liveness fact). @@ -52,22 +60,30 @@ impl DemandResult { /// intersected with the demand set. #[derive(Clone, Debug)] pub struct DenseLivenessResult { + root_scope: BodyScope, facts: FactStore, LiveSet>, } impl DenseLivenessResult { /// Copy the facts recorded by a converged dense engine. - pub fn from_engine<'ir, S, F>( - engine: &DenseBackwardInterpreter<'ir, S, LiveSet, InterpreterError, F>, + pub fn from_engine<'ir, S, F, Lk>( + engine: &DenseBackwardInterpreter<'ir, S, LiveSet, InterpreterError, F, Lk>, + root_scope: BodyScope, ) -> Self where S: StageMeta, { Self { + root_scope, facts: engine.facts(), } } + /// The resolved `(target stage, callable body)` analyzed by this result. + pub fn root_scope(&self) -> BodyScope { + self.root_scope + } + /// The liveness fact recorded at `point`. pub fn point_facts(&self, point: Scoped) -> Option<&LiveSet> { self.facts.get(point) diff --git a/crates/kirin-liveness/tests/callable_root.rs b/crates/kirin-liveness/tests/callable_root.rs new file mode 100644 index 0000000000..8d42398ff8 --- /dev/null +++ b/crates/kirin-liveness/tests/callable_root.rs @@ -0,0 +1,296 @@ +//! Acceptance tests for the common callable-root protocol used by both +//! backward engines. + +use kirin::prelude::*; +use kirin_interpreter::{ + Body, Callee, CrossStageLinker, FunctionTarget, InterpreterError, Linker, SameStageLinker, +}; +use kirin_liveness::{Demand, DenseLiveness}; +use kirin_test_languages::GraphFunctionLanguage; + +type TestStage = StageInfo; + +const BLOCK_PROGRAM: &str = r#" +stage @test fn @linear(i64) -> i64; +stage @test fn @main(i64) -> i64; + +specialize @test fn @linear(i64) -> i64 ^body(%x: i64) { + %y = neg %x -> i64; + ret %y; +} + +specialize @test fn @main(i64) -> i64 { + ^entry(%x: i64) { + %y = call.named @linear(%x) -> i64; + ret %y; + } +} +"#; + +const CROSS_STAGE_PROGRAM: &str = r#" +stage @source fn @linear(i64) -> i64; +stage @lowered fn @linear(i64) -> i64; + +specialize @lowered fn @linear(i64) -> i64 ^body(%x: i64) { + ret %x; +} +"#; + +const GRAPH_PROGRAM: &str = r#" +stage @test fn @directed(i64) -> i64; +stage @test fn @undirected(i64) -> i64; + +specialize @test fn @directed(i64) -> i64 digraph ^graph(%x: i64) { + %y = neg %x -> i64; + yield %y; +} + +specialize @test fn @undirected(i64) -> i64 ungraph ^graph(%x: i64) { + %y = neg %x -> i64; +} +"#; + +const AMBIGUOUS_PROGRAM: &str = r#" +stage @test fn @ambiguous(i64, i64) -> i64; + +specialize @test fn @ambiguous(i64) -> i64 ^first(%x: i64) { + ret %x; +} + +specialize @test fn @ambiguous(i64, i64) -> i64 ^second(%x: i64, %y: i64) { + ret %x; +} +"#; + +fn parse(program: &str) -> Pipeline { + let mut pipeline = Pipeline::new(); + ParsePipelineText::parse(&mut pipeline, program).expect("program parses"); + pipeline +} + +fn function_callee(pipeline: &Pipeline, name: &str) -> (CompileStage, Callee) { + let stage = pipeline.stage_by_name("test").expect("stage exists"); + let function = pipeline + .lookup_function_by_name(name) + .expect("function exists"); + (stage, Callee::Function(function)) +} + +fn all_callee_variants(pipeline: &Pipeline) -> (CompileStage, [Callee; 4]) { + let stage = pipeline.stage_by_name("test").expect("stage exists"); + let info = pipeline.stage(stage).expect("stage info exists"); + let symbol = info.symbol_table().lookup("linear").expect("symbol exists"); + let function = pipeline + .lookup_function_by_name("linear") + .expect("function exists"); + let staged = pipeline + .resolve_staged_function("linear", stage) + .expect("staged function exists"); + let specialized = staged + .get_info(info) + .expect("staged function info exists") + .unique_live_specialization() + .expect("one live specialization"); + ( + stage, + [ + Callee::Named(symbol), + Callee::Function(function), + Callee::Staged(staged), + Callee::Specialized(specialized), + ], + ) +} + +#[test] +fn every_callee_variant_uses_the_same_backward_root_protocol() { + let pipeline = parse(BLOCK_PROGRAM); + let (stage, callees) = all_callee_variants(&pipeline); + + for callee in callees { + let mut demand = Demand::::new(&pipeline); + let demand_scope = demand.analyze(stage, callee).expect("demand succeeds"); + assert!(matches!(demand_scope, (_, Body::Block(_)))); + + let mut dense = DenseLiveness::::new(&pipeline); + let dense_scope = dense.analyze(stage, callee).expect("dense succeeds"); + assert_eq!(dense_scope, demand_scope); + } +} + +#[derive(Clone, Copy)] +struct RejectingLinker; + +impl Linker for RejectingLinker { + fn resolve( + &self, + _pipeline: &Pipeline, + _caller_stage: CompileStage, + _callee: &Callee, + ) -> Result { + Err(InterpreterError::Custom("rejecting linker reached")) + } +} + +#[test] +fn no_callee_variant_bypasses_the_configured_linker() { + let pipeline = parse(BLOCK_PROGRAM); + let (stage, callees) = all_callee_variants(&pipeline); + + for callee in callees { + let demand_error = Demand::::new(&pipeline) + .with_linker(RejectingLinker) + .analyze(stage, callee) + .expect_err("demand must use the linker"); + assert_eq!( + demand_error, + InterpreterError::Custom("rejecting linker reached") + ); + + let dense_error = DenseLiveness::::new(&pipeline) + .with_linker(RejectingLinker) + .analyze(stage, callee) + .expect_err("dense must use the linker"); + assert_eq!( + dense_error, + InterpreterError::Custom("rejecting linker reached") + ); + } +} + +#[test] +fn cross_stage_linking_discovers_the_body_at_the_target_stage() { + let pipeline = parse(CROSS_STAGE_PROGRAM); + let source = pipeline.stage_by_name("source").expect("source exists"); + let lowered = pipeline.stage_by_name("lowered").expect("lowered exists"); + let function = pipeline + .lookup_function_by_name("linear") + .expect("function exists"); + let callee = Callee::Function(function); + + assert!(matches!( + Demand::::new(&pipeline).analyze(source, callee), + Err(InterpreterError::MissingSpecialization(_)) + )); + assert!(matches!( + DenseLiveness::::new(&pipeline).analyze(source, callee), + Err(InterpreterError::MissingSpecialization(_)) + )); + + let demand_scope = Demand::::new(&pipeline) + .with_linker(CrossStageLinker) + .analyze(source, callee) + .expect("cross-stage demand succeeds"); + let mut dense = DenseLiveness::::new(&pipeline).with_linker(CrossStageLinker); + let dense_scope = dense + .analyze(source, callee) + .expect("cross-stage dense succeeds"); + assert_eq!(demand_scope.0, lowered); + assert!(matches!(demand_scope.1, Body::Block(_))); + assert_eq!(dense_scope, demand_scope); + + // Dense analysis rebuilds solver state between roots; configuration state + // must survive that reset. + assert_eq!( + dense + .analyze(source, callee) + .expect("custom linker survives dense reset"), + dense_scope + ); +} + +#[derive(Clone, Copy)] +struct FixedTargetLinker(FunctionTarget); + +impl Linker for FixedTargetLinker { + fn resolve( + &self, + _pipeline: &Pipeline, + _caller_stage: CompileStage, + _callee: &Callee, + ) -> Result { + Ok(self.0) + } +} + +#[test] +fn a_resolved_non_callable_definition_is_reported_by_shared_body_discovery() { + let pipeline = parse(BLOCK_PROGRAM); + let (stage, callee) = function_callee(&pipeline, "linear"); + let mut target = SameStageLinker + .resolve(&pipeline, stage, &callee) + .expect("function resolves"); + let info = pipeline.stage(stage).expect("stage info exists"); + let body = match target.definition.definition(info) { + GraphFunctionLanguage::LinearFunction { body, .. } => *body, + other => panic!("expected linear function, got {other:?}"), + }; + let non_callable = body + .statements(info) + .next() + .expect("body contains an arithmetic statement"); + target.definition = non_callable; + + let demand_error = Demand::::new(&pipeline) + .with_linker(FixedTargetLinker(target)) + .analyze(stage, callee) + .expect_err("non-callable definition must fail"); + assert_eq!(demand_error, InterpreterError::NotCallable(non_callable)); + + let dense_error = DenseLiveness::::new(&pipeline) + .with_linker(FixedTargetLinker(target)) + .analyze(stage, callee) + .expect_err("non-callable definition must fail"); + assert_eq!(dense_error, InterpreterError::NotCallable(non_callable)); +} + +#[test] +fn unsupported_graph_roots_fail_instead_of_producing_empty_facts() { + let pipeline = parse(GRAPH_PROGRAM); + + for name in ["directed", "undirected"] { + let (stage, callee) = function_callee(&pipeline, name); + let demand_error = Demand::::new(&pipeline) + .analyze(stage, callee) + .expect_err("graph demand is not implemented"); + assert!(matches!( + demand_error, + InterpreterError::NoDefaultWalker(Body::DiGraph(_) | Body::UnGraph(_)) + )); + + let dense_error = DenseLiveness::::new(&pipeline) + .analyze(stage, callee) + .expect_err("graph liveness is not implemented"); + assert!(matches!( + dense_error, + InterpreterError::NoDefaultWalker(Body::DiGraph(_) | Body::UnGraph(_)) + )); + } +} + +#[test] +fn linker_resolution_errors_propagate_from_both_backward_engines() { + let pipeline = parse(BLOCK_PROGRAM); + let stage = pipeline.stage_by_name("test").expect("stage exists"); + let missing = Callee::Named(Symbol::from(usize::MAX)); + + assert!(matches!( + Demand::::new(&pipeline).analyze(stage, missing), + Err(InterpreterError::MissingCallSymbol(_)) + )); + assert!(matches!( + DenseLiveness::::new(&pipeline).analyze(stage, missing), + Err(InterpreterError::MissingCallSymbol(_)) + )); + + let ambiguous_pipeline = parse(AMBIGUOUS_PROGRAM); + let (ambiguous_stage, ambiguous) = function_callee(&ambiguous_pipeline, "ambiguous"); + assert!(matches!( + Demand::::new(&ambiguous_pipeline).analyze(ambiguous_stage, ambiguous), + Err(InterpreterError::AmbiguousSpecialization { count: 2, .. }) + )); + assert!(matches!( + DenseLiveness::::new(&ambiguous_pipeline).analyze(ambiguous_stage, ambiguous), + Err(InterpreterError::AmbiguousSpecialization { count: 2, .. }) + )); +} diff --git a/crates/kirin-liveness/tests/cfg.rs b/crates/kirin-liveness/tests/cfg.rs index 65615d9c89..58e805a3fa 100644 --- a/crates/kirin-liveness/tests/cfg.rs +++ b/crates/kirin-liveness/tests/cfg.rs @@ -4,7 +4,7 @@ use kirin::prelude::{GetInfo, ParsePipelineText, Pipeline, SSAValue, StageInfo}; use kirin_arith::Arith; -use kirin_interpreter::{Body, InterpreterError, ProgramPoint, Scoped}; +use kirin_interpreter::{Body, Callee, InterpreterError, ProgramPoint, Scoped}; use kirin_liveness::{DenseLiveness, analyze_demand}; use kirin_test_languages::ArithFunctionLanguage; @@ -42,33 +42,40 @@ specialize @test fn @main(i64, i64, i64) -> i64 { } "#; +const ENGINE_REUSE_PROGRAM: &str = r#" +stage @test fn @first(i64) -> i64; +stage @test fn @second(i64) -> i64; + +specialize @test fn @first(i64) -> i64 { + ^entry(%x: i64) { + ret %x; + } +} + +specialize @test fn @second(i64) -> i64 { + ^entry(%y: i64) { + %neg = neg %y -> i64; + ret %neg; + } +} +"#; + fn parse(program: &str) -> Pipeline> { let mut pipeline: Pipeline> = Pipeline::new(); ParsePipelineText::parse(&mut pipeline, program).expect("program parses"); pipeline } -/// The finalized stage id and the body cfg of `@main`. -// TODO: `analyze` method is wrong, calling demand analysis. -// TODO: `analyze` should use the same entry point. i.e. Callee not CFG/Body. -fn main_cfg( +/// The caller stage and canonical callable root for `function_name`. +fn function_root( pipeline: &Pipeline>, -) -> (kirin::prelude::CompileStage, kirin_ir::CFG) { + function_name: &str, +) -> (kirin::prelude::CompileStage, Callee) { let stage_id = pipeline.stage_by_name("test").expect("stage @test exists"); - let stage = pipeline.stage(stage_id).expect("stage info"); - - let sf = pipeline - .resolve_staged_function("main", stage_id) - .expect("@main is staged at @test"); - let sf_info = sf.get_info(stage).expect("staged function info"); - let spec = &sf_info.specializations()[0]; - let definition = *spec.definition(); - - let cfg = match definition.definition(stage) { - ArithFunctionLanguage::Function { body, .. } => *body, - other => panic!("expected a function definition, got {other:?}"), - }; - (stage_id, cfg) + let function = pipeline + .lookup_function_by_name(function_name) + .unwrap_or_else(|| panic!("function @{function_name} exists")); + (stage_id, Callee::Function(function)) } /// The parameters of the `index`-th block of `cfg`, as SSA values. @@ -113,8 +120,11 @@ fn find_arith( #[test] fn strong_liveness_over_branching_function() { let pipeline = parse(PROGRAM); - let (stage, cfg) = main_cfg(&pipeline); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = function_root(&pipeline, "main"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, Body::CFG(cfg)) = result.root_scope() else { + panic!("@main resolves to a CFG body") + }; let entry_params = block_params(&pipeline, cfg, 0); let (x, cond) = (entry_params[0], entry_params[1]); @@ -153,8 +163,11 @@ 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, cfg) = main_cfg(&pipeline); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = function_root(&pipeline, "main"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, Body::CFG(cfg)) = result.root_scope() else { + panic!("@main resolves to a CFG body") + }; let entry_params = block_params(&pipeline, cfg, 0); let (live, dead, cond) = (entry_params[0], entry_params[1], entry_params[2]); @@ -213,8 +226,11 @@ specialize @test fn @main(i64, i64) -> i64 { #[test] fn terminator_operands_become_demanded() { let pipeline = parse(RET_PARAM_PROGRAM); - let (stage, cfg) = main_cfg(&pipeline); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = function_root(&pipeline, "main"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, Body::CFG(cfg)) = result.root_scope() else { + panic!("@main resolves to a CFG body") + }; let x = block_params(&pipeline, cfg, 0)[0]; assert!(result.is_demanded(x)); @@ -223,8 +239,11 @@ fn terminator_operands_become_demanded() { #[test] fn demanded_result_marks_operands_demanded() { let pipeline = parse(DEMANDED_RESULT_PROGRAM); - let (stage, cfg) = main_cfg(&pipeline); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = function_root(&pipeline, "main"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, Body::CFG(cfg)) = result.root_scope() else { + panic!("@main resolves to a CFG body") + }; let params = block_params(&pipeline, cfg, 0); let sum = find_arith(&pipeline, cfg, |op| match op { @@ -239,8 +258,11 @@ fn demanded_result_marks_operands_demanded() { #[test] fn dead_result_leaves_operands_dead() { let pipeline = parse(DEAD_RESULT_PROGRAM); - let (stage, cfg) = main_cfg(&pipeline); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = function_root(&pipeline, "main"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, Body::CFG(cfg)) = result.root_scope() else { + panic!("@main resolves to a CFG body") + }; let params = block_params(&pipeline, cfg, 0); let sum = find_arith(&pipeline, cfg, |op| match op { @@ -305,8 +327,12 @@ fn live_set(values: &[SSAValue]) -> LiveSet { #[test] fn classic_liveness_boundary_sets() { let pipeline = parse(PROGRAM); - let (stage, cfg) = main_cfg(&pipeline); - let result = analyze_dense(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = function_root(&pipeline, "main"); + let result = analyze_dense(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let scope = result.root_scope(); + let (stage, Body::CFG(cfg)) = scope else { + panic!("@main resolves to a CFG body") + }; let entry_params = block_params(&pipeline, cfg, 0); let (x, cond) = (entry_params[0], entry_params[1]); @@ -315,7 +341,6 @@ fn classic_liveness_boundary_sets() { let entry = nth_block(&pipeline, cfg, 0); let then_block = nth_block(&pipeline, cfg, 1); let else_block = nth_block(&pipeline, cfg, 2); - let scope = (stage, Body::CFG(cfg)); let point = |item| Scoped::new(scope, item); // live_in(entry): %x (used by add and both edges) and %cond (branch use). @@ -358,29 +383,38 @@ fn classic_liveness_boundary_sets() { #[test] fn reusing_dense_engine_replaces_the_previous_scoped_result() { - let pipeline = parse(PROGRAM); - let (stage, cfg) = main_cfg(&pipeline); - let entry = nth_block(&pipeline, cfg, 0); - let then_block = nth_block(&pipeline, cfg, 1); + let pipeline = parse(ENGINE_REUSE_PROGRAM); + let (stage, first) = function_root(&pipeline, "first"); + let (_, second) = function_root(&pipeline, "second"); let mut engine = DenseLiveness::<_, InterpreterError>::new(&pipeline); - engine.analyze(stage, cfg).expect("CFG analysis succeeds"); + let first_scope = engine + .analyze(stage, first) + .expect("first callable analysis succeeds"); + let (_, Body::CFG(first_cfg)) = first_scope else { + panic!("@first resolves to a CFG body") + }; + let first_entry = nth_block(&pipeline, first_cfg, 0); assert!( engine .point_facts(Scoped::new( - (stage, Body::CFG(cfg)), - ProgramPoint::BlockEntry(entry), + first_scope, + ProgramPoint::BlockEntry(first_entry), )) .is_some() ); - engine - .analyze(stage, then_block) - .expect("block analysis succeeds"); + let second_scope = engine + .analyze(stage, second) + .expect("second callable analysis succeeds"); + let (_, Body::CFG(second_cfg)) = second_scope else { + panic!("@second resolves to a CFG body") + }; + let second_entry = nth_block(&pipeline, second_cfg, 0); assert_eq!( engine.point_facts(Scoped::new( - (stage, Body::CFG(cfg)), - ProgramPoint::BlockEntry(entry), + first_scope, + ProgramPoint::BlockEntry(first_entry), )), None, "facts from the previous analysis are not retained" @@ -388,8 +422,8 @@ fn reusing_dense_engine_replaces_the_previous_scoped_result() { assert!( engine .point_facts(Scoped::new( - (stage, Body::Block(then_block)), - ProgramPoint::BlockEntry(then_block), + second_scope, + ProgramPoint::BlockEntry(second_entry), )) .is_some() ); @@ -398,16 +432,18 @@ fn reusing_dense_engine_replaces_the_previous_scoped_result() { #[test] fn classic_per_point_sets_gen_dead_uses() { let pipeline = parse(DEAD_RESULT_PROGRAM); - let (stage, cfg) = main_cfg(&pipeline); - let result = analyze_dense(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = function_root(&pipeline, "main"); + let result = analyze_dense(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let scope = result.root_scope(); + let (_, Body::CFG(cfg)) = scope else { + panic!("@main resolves to a CFG body") + }; let params = block_params(&pipeline, cfg, 0); let (a, b) = (params[0], params[1]); let add = find_stmt(&pipeline, cfg, |definition| { matches!(definition, ArithFunctionLanguage::Arith(Arith::Add { .. })) }); - let scope = (stage, Body::CFG(cfg)); - // Classic semantics: the dead add still GENS its operands, so %b is live // before it — this is the conventional per-point meaning (the old strong // expectations were demand projections, not dense liveness). @@ -424,9 +460,14 @@ 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, 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 (caller_stage, callee) = function_root(&pipeline, "main"); + let dense = analyze_dense(&pipeline, caller_stage, callee).expect("dense analysis succeeds"); + let demand = analyze_demand(&pipeline, caller_stage, callee).expect("demand analysis succeeds"); + let scope = dense.root_scope(); + assert_eq!(scope, demand.root_scope()); + let (_, Body::CFG(cfg)) = scope else { + panic!("@main resolves to a CFG body") + }; let params = block_params(&pipeline, cfg, 0); let (a, b) = (params[0], params[1]); @@ -437,10 +478,7 @@ fn strong_per_point_sets_are_classic_intersect_demanded() { // The composition recovers the strong (needed) per-point view: %b is // classically live before the dead add but not demanded, so it drops out. let strong = dense - .strong_point_facts( - Scoped::new((stage, Body::CFG(cfg)), ProgramPoint::Before(add)), - &demand, - ) + .strong_point_facts(Scoped::new(scope, ProgramPoint::Before(add)), &demand) .expect("point reconstructed"); assert_eq!(strong, live_set(&[a])); assert!(!strong.contains(b)); diff --git a/crates/kirin-test-languages/src/arith_function_language.rs b/crates/kirin-test-languages/src/arith_function_language.rs index 92b27d63af..6e2cb8e1db 100644 --- a/crates/kirin-test-languages/src/arith_function_language.rs +++ b/crates/kirin-test-languages/src/arith_function_language.rs @@ -35,9 +35,9 @@ pub enum ArithFunctionLanguage { mod interpreter { use kirin_interpreter::dialect::{ CallableBody, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, - FunctionEntry, Interp, Interpretable, InterpreterError, StrongDemand, + FunctionEntry, Interpretable, StrongDemand, }; - use kirin_ir::{HasBottom, Product}; + use kirin_ir::HasBottom; use super::ArithFunctionLanguage; @@ -74,19 +74,11 @@ mod interpreter { } } - impl FunctionEntry for ArithFunctionLanguage { - fn function_entry( - &self, - args: Product, - interp: &mut I, - ) -> Result, I::Error> { + impl FunctionEntry for ArithFunctionLanguage { + fn function_entry(&self) -> Option { match self { - ArithFunctionLanguage::Function { body, .. } => { - Ok(CallableBody::new(*body).args(args)) - } - _ => Err(I::Error::from(InterpreterError::NotCallable( - interp.statement(), - ))), + ArithFunctionLanguage::Function { body, .. } => Some(CallableBody::new(*body)), + _ => None, } } } diff --git a/crates/kirin-test-languages/src/graph_function_language.rs b/crates/kirin-test-languages/src/graph_function_language.rs index c192349706..9ac73f1442 100644 --- a/crates/kirin-test-languages/src/graph_function_language.rs +++ b/crates/kirin-test-languages/src/graph_function_language.rs @@ -88,10 +88,11 @@ mod interpreter { use kirin_arith::{ArithValue, CheckedDiv, CheckedRem, interpreter::DivisionByZero}; use kirin_interpreter::BranchCondition; use kirin_interpreter::{ - CallableBody, DiGraphFrame, ForwardEval, FunctionEntry, Interp, Interpretable, - InterpreterError, SparseForwardEffect, SparseForwardInterp, + CallableBody, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, + DiGraphFrame, ForwardEval, FunctionEntry, Interpretable, SparseForwardEffect, + SparseForwardInterp, StrongDemand, }; - use kirin_ir::{Product, SSAValue}; + use kirin_ir::{HasBottom, Product, SSAValue}; use super::GraphFunctionLanguage; @@ -142,28 +143,59 @@ mod interpreter { } } - impl FunctionEntry for GraphFunctionLanguage { - fn function_entry( - &self, - args: Product, - interp: &mut I, - ) -> Result, I::Error> { + impl Interpretable for GraphFunctionLanguage + where + I: DemandInterp, + I::Value: HasBottom + PartialEq, + { + fn interpret(&self, interp: &mut I) -> Result { match self { - GraphFunctionLanguage::Function { body, .. } => { - Ok(CallableBody::new(*body).args(args)) - } - GraphFunctionLanguage::GraphFunction { body, .. } => { - Ok(CallableBody::new(*body).args(args)) - } + GraphFunctionLanguage::Function { .. } + | GraphFunctionLanguage::GraphFunction { .. } + | GraphFunctionLanguage::LinearFunction { .. } + | GraphFunctionLanguage::UnGraphFunction { .. } => Ok(interp.effect()), + GraphFunctionLanguage::GraphEval { .. } => interp.demand_uses_if_observable(self), + 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 Interpretable for GraphFunctionLanguage + where + I: ClassicLivenessInterp, + { + fn interpret(&self, interp: &mut I) -> Result { + match self { + GraphFunctionLanguage::Function { .. } + | GraphFunctionLanguage::GraphFunction { .. } + | GraphFunctionLanguage::LinearFunction { .. } + | GraphFunctionLanguage::UnGraphFunction { .. } => Ok(DenseBackwardEffect::Next), + GraphFunctionLanguage::GraphEval { .. } => interp.gen_uses_kill_defs(self), + 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) -> Option { + match self { + GraphFunctionLanguage::Function { body, .. } => Some(CallableBody::new(*body)), + GraphFunctionLanguage::GraphFunction { body, .. } => Some(CallableBody::new(*body)), GraphFunctionLanguage::LinearFunction { body, .. } => { - Ok(CallableBody::new(*body).args(args)) + Some(CallableBody::new(*body)) } GraphFunctionLanguage::UnGraphFunction { body, .. } => { - Ok(CallableBody::new(*body).args(args)) + Some(CallableBody::new(*body)) } - _ => Err(I::Error::from(InterpreterError::NotCallable( - interp.statement(), - ))), + _ => None, } } } diff --git a/docs/.DS_Store b/docs/.DS_Store deleted file mode 100644 index 5d56e9d7e4999b4eea93fc8e20aa9a7f0581d12d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKI|>3Z5S>vG!N$@uSMUZw^aNhOLJ>h$P_*94b9pr1d>UQtw2?P3dC6p6LSC`6 zBO*G#Y-S=85gEY^i0p zwti-h{ zq5pp-aYY5Fz+Wk#gT-nw$CI+Qb{=Q7w!qhL%elkNFn0c7o_bQ`6`Nzf VCbof2N8IT^{tTEdG%E0G1s=-46_x-1 diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index 87f28e5aac..c2a0d00e07 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -134,7 +134,7 @@ shape without colliding. `Interp` is the interpreter/analysis **driver**: it exposes the value domain, the error type, the per-statement effect, the semantic key `Semantics`, and the current statement location (`stage()`/`statement()`/`index()`). The engine stashes the -location before dispatching each rule (`run_statement`/`enter_function`) and +location before dispatching each statement rule (`run_statement`) and restores it afterward, so a rule can read it back without a separate context object. A rule produces `I::Effect` — the **analysis-specific** effect algebra — not a single universal enum. Forward rules bound `I: SparseForwardInterp`, the @@ -243,23 +243,40 @@ of its variants. The abstract equivalents use the same narrow and exhaustive dispatch. Future structured dialects would follow the same ownership rule; only the existing SCF operations are implemented. -### `FunctionEntry` — callable statements +### `FunctionEntry` — value-independent callable statements ```rust -pub trait FunctionEntry: Dialect { - fn function_entry(&self, args: Product, interp: &mut I) - -> Result, I::Error>; +pub trait FunctionEntry: Dialect { + fn function_entry(&self) -> Option; } ``` -Like `Interpretable`, it receives the engine `interp` directly (function entry is -forward-only, so there is no `Semantics` parameter). - Statements that define function bodies (e.g. `kirin_function::Function`) -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`. +return the `CallableBody { body }` to enter on invocation (the function-call +entry descriptor — not a structured-control abstraction). Discovery is a +structural operation: it never receives concrete values, abstract values, or a +backward-analysis fact domain. Concrete execution and forward analysis retain +their own argument products and bind them only after discovery; backward +analyses manufacture no placeholder product. On language enums the trait is +derived; `#[callable]` marks the variants that forward, while dispatch maps a +non-callable variant to `NotCallable` with the actual definition statement. + +Every engine root follows the same validated prefix: + +```text +caller stage + Callee + -> Linker::resolve + -> FunctionTarget + -> FunctionEntry dispatch at FunctionTarget.stage + -> CallableBody + -> engine-specific boundary initialization +``` + +The boundary input is intentionally not unified: concrete calls and forward +abstract analysis accept values, while the current backward roots accept only +`(stage, Callee)` until their analysis-specific boundary policies are added. +Both backward `analyze` methods return the resolved `(target stage, Body)` scope +used to qualify their facts. ## Compiler-author surface @@ -481,7 +498,7 @@ surface still runs the frames it can support. | `BlockQueries: Interp` | `block_params`/`first_statement`/`next_statement` | `BlockCursor`, `BlockFrame`, `AbstractBlockFrame`, dialect block walkers | | `CFGQueries: BlockQueries` | `cfg_entry` | `CFGFrame` | | `DiGraphQueries: Interp` | `digraph_walk_plan` (default: `NoDefaultWalker`) | `DiGraphFrame`, `AbstractDiGraphFrame` | -| `CallServices: Env` | `alloc_env`/`free_env`/`resolve_call`/`enter_function` | `CallFrame` | +| `CallServices: Env` | `alloc_env`/`free_env`/`resolve_callable` | `CallFrame` | **The `*Queries` traits are read-only, and only require `Interp`** — so nothing on them can touch SSA storage, and their names cannot hide a store mutation. The @@ -495,8 +512,10 @@ alone and `::write_child_results` takes `Env` alone. `CallServices` names *services*, not a convention: **`CallFrame` still owns the calling convention** — the operation order, which completions are legal, and freeing the activation exactly once — and this trait only supplies the -primitives. It is deliberately **not** split further: the standard `CallFrame` -consumes all four together, and their pairing is a safety property (an +primitives. `resolve_callable` is the common linker-plus-target-stage body +discovery protocol; it carries no value product. The trait is deliberately +**not** split further: the standard `CallFrame` consumes all three services +together, and their pairing is a safety property (an `alloc_env` without its `free_env` leaks; a second `free_env` double-frees), so no engine should be able to offer half a call convention. @@ -533,8 +552,8 @@ An abstract engine therefore **no longer inherits the concrete call lifecycle**. That follows the semantics: forward abstract interpretation *summarizes* a call (`summarize_call` → `AbstractCallFrame`) rather than descending into it, and reaches a callable body's entry block through `Owner` seeding in the fixpoint -driver rather than `cfg_entry`. Requiring it to expose `alloc_env`, `free_env`, -`enter_function`, `resolve_call`, and `cfg_entry` was demanding a call +driver rather than `cfg_entry`. Requiring its frame universe to expose +`alloc_env`, `free_env`, `resolve_callable`, and `cfg_entry` was demanding a call convention it never performs. `tests/frame_engine_capabilities.rs` pins this down with deliberately incomplete mock engines whose ability to compile *is* the regression test. @@ -576,7 +595,7 @@ pub trait StatementDispatch: Interp { /* run_statement */ } pub trait BlockQueries: Interp { /* read-only block queries */ } pub trait CFGQueries: BlockQueries { /* cfg_entry */ } pub trait DiGraphQueries: Interp { /* digraph_walk_plan */ } -pub trait CallServices: Env { /* alloc/free env, resolve_call, enter_function */ } +pub trait CallServices: Env { /* alloc/free env, resolve_callable */ } pub(crate) trait BlockBinding: Env + BlockQueries { /* bind_block_args */ } ``` @@ -661,7 +680,7 @@ configurable: | Concern | Where | Configurable? | |---|---|---| -| **call convention** — resolve the callee, allocate its activation, ask `FunctionEntry` for the body, suspend, validate the completion kind, free the activation *exactly once*, bind results | `CallFrame` itself | **no** — this is where double-frees would live | +| **call convention** — resolve the callee and body through the common callable-root protocol, allocate its activation, bind concrete boundary arguments, suspend, validate the completion kind, free the activation *exactly once*, bind results | `CallFrame` itself | **no** — this is where double-frees would live | | **walker choice** — which child enters the callee body | `CallBodyTraversal` | **yes** | `Body` stays a closed vocabulary, so `CallFrame::step_into` still matches it diff --git a/example/toy-lang/src/interpreter/mod.rs b/example/toy-lang/src/interpreter/mod.rs index 7cc35d0bcc..c2934335bc 100644 --- a/example/toy-lang/src/interpreter/mod.rs +++ b/example/toy-lang/src/interpreter/mod.rs @@ -16,17 +16,15 @@ pub use frame::ToyAbstractFrame; pub(crate) use frame::ToyDenseBackwardFrame; use frame::ToyFrame; -use kirin::prelude::{CFG, CompileStage, GetInfo, Pipeline, UniqueLiveSpecializationError}; +use kirin::prelude::{CFG, CompileStage, Pipeline}; use kirin_constprop::{ConstPropContext, ConstPropValue}; -use kirin_function::{Lexical, Lifted}; -use kirin_interpreter::InterpreterError; use kirin_interpreter::engine::{ CallContext, ConcreteInterpreterCore, CrossStageLinker, Linker, SameStageLinker, SparseForwardInterpreter, expect_single, }; -use kirin_liveness::{DenseLivenessResult, LiveSet}; +use kirin_interpreter::{Body, Callee, InterpreterError}; +use kirin_liveness::{DenseLiveness, DenseLivenessResult, LiveSet}; -use crate::language::{HighLevel, LowLevel}; use crate::stage::Stage; /// Summary key of the constant-propagation analysis policy. @@ -137,83 +135,6 @@ pub fn analyze_constprop( expect_single(analysis.analyze_by_name(stage_name, function_name, args.iter().cloned())?) } -/// The body cfg of `function_name`'s specialization at `stage_name`. -fn function_cfg( - pipeline: &Pipeline, - stage_name: &str, - function_name: &str, -) -> Result<(CompileStage, CFG), InterpreterError> { - let stage_id = pipeline - .stage_by_name(stage_name) - .ok_or_else(|| InterpreterError::MissingStageName(stage_name.into()))?; - let staged = pipeline - .resolve_staged_function(function_name, stage_id) - .ok_or_else(|| InterpreterError::MissingFunctionName(function_name.into()))?; - let stage = pipeline - .stage(stage_id) - .ok_or(InterpreterError::MissingStage(stage_id))?; - - let cfg = match stage { - Stage::Source(info) => { - let staged_info = staged - .get_info(info) - .ok_or(InterpreterError::MissingSpecialization(staged))?; - let spec = match staged_info.unique_live_specialization() { - Ok(spec) => spec, - Err(UniqueLiveSpecializationError::NoSpecialization) => { - return Err(InterpreterError::MissingSpecialization(staged)); - } - Err(UniqueLiveSpecializationError::Ambiguous { count }) => { - return Err(InterpreterError::AmbiguousSpecialization { - function: staged, - count, - }); - } - }; - let spec_info = spec.get_info(info).ok_or(InterpreterError::Custom( - "specialized function has no definition", - ))?; - let definition = *spec_info.definition(); - match definition.definition(info) { - HighLevel::Lexical(Lexical::Function(function)) => { - use kirin::prelude::HasCFGBody; - *function.cfg() - } - _ => return Err(InterpreterError::Custom("expected a function definition")), - } - } - Stage::Lowered(info) => { - let staged_info = staged - .get_info(info) - .ok_or(InterpreterError::MissingSpecialization(staged))?; - let spec = match staged_info.unique_live_specialization() { - Ok(spec) => spec, - Err(UniqueLiveSpecializationError::NoSpecialization) => { - return Err(InterpreterError::MissingSpecialization(staged)); - } - Err(UniqueLiveSpecializationError::Ambiguous { count }) => { - return Err(InterpreterError::AmbiguousSpecialization { - function: staged, - count, - }); - } - }; - let spec_info = spec.get_info(info).ok_or(InterpreterError::Custom( - "specialized function has no definition", - ))?; - let definition = *spec_info.definition(); - match definition.definition(info) { - LowLevel::Lifted(Lifted::Function(function)) => { - use kirin::prelude::HasCFGBody; - *function.cfg() - } - _ => return Err(InterpreterError::Custom("expected a function definition")), - } - } - }; - Ok((stage_id, cfg)) -} - /// 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 @@ -224,10 +145,25 @@ pub fn analyze_classic_liveness( stage_name: &str, function_name: &str, ) -> Result<(CompileStage, CFG, DenseLivenessResult), InterpreterError> { - let (stage, cfg) = function_cfg(pipeline, stage_name, function_name)?; - let result = kirin_liveness::analyze_dense_with_frame::< - _, + let caller_stage = pipeline + .stage_by_name(stage_name) + .ok_or_else(|| InterpreterError::MissingStageName(stage_name.into()))?; + let function = pipeline + .lookup_function_by_name(function_name) + .ok_or_else(|| InterpreterError::MissingFunctionName(function_name.into()))?; + let mut engine: DenseLiveness< + '_, + Stage, + InterpreterError, ToyDenseBackwardFrame, - >(pipeline, stage, cfg)?; + CrossStageLinker, + > = DenseLiveness::new(pipeline).with_linker(CrossStageLinker); + let scope = engine.analyze(caller_stage, Callee::Function(function))?; + let result = DenseLivenessResult::from_engine(&engine, scope); + let (stage, Body::CFG(cfg)) = scope else { + return Err(InterpreterError::Custom( + "classic liveness target is not a CFG function", + )); + }; Ok((stage, cfg, result)) } diff --git a/example/toy-lang/src/interpreter/tests.rs b/example/toy-lang/src/interpreter/tests.rs index d116bfa4fa..e50800f1c8 100644 --- a/example/toy-lang/src/interpreter/tests.rs +++ b/example/toy-lang/src/interpreter/tests.rs @@ -597,12 +597,12 @@ mod demand { use std::collections::HashSet; use kirin::prelude::{ - CFG, CompileStage, GetInfo, HasBlocks, HasCFG, HasCFGBody, HasDigraphs, HasResults, - HasUngraphs, ParsePipelineText, Pipeline, SSAValue, Statement, + CFG, CompileStage, GetInfo, HasBlocks, HasCFG, HasDigraphs, HasResults, HasUngraphs, + ParsePipelineText, Pipeline, SSAValue, Statement, }; use kirin_arith::{Arith, ArithValue}; use kirin_function::Lexical; - use kirin_interpreter::Body; + use kirin_interpreter::{Body, BodyScope, Callee}; use kirin_liveness::analyze_demand; use crate::language::HighLevel; @@ -614,22 +614,23 @@ mod demand { pipeline } - /// The source stage id, its info, and the body cfg of `name`. - pub(super) fn source_cfg(pipeline: &Pipeline, name: &str) -> (CompileStage, CFG) { + /// A source-stage callable root. Body discovery belongs to the interpreter, + /// so this helper deliberately stops at the pipeline-level function handle. + pub(super) fn source_root(pipeline: &Pipeline, name: &str) -> (CompileStage, Callee) { 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 sf = pipeline - .resolve_staged_function(name, stage_id) - .expect("staged function"); - let sf_info = sf.get_info(info).expect("staged function info"); - let definition = *sf_info.specializations()[0].definition(); - let cfg = match definition.definition(info) { - HighLevel::Lexical(Lexical::Function(function)) => *function.cfg(), - other => panic!("expected a function definition, got {other:?}"), + let function = pipeline + .lookup_function_by_name(name) + .expect("pipeline function"); + (stage_id, Callee::Function(function)) + } + + /// Extract a CFG only after the interpreter has resolved the callable and + /// reported the target scope it actually analyzed. + pub(super) fn cfg_scope(scope: BodyScope) -> (CompileStage, CFG) { + let (stage, Body::CFG(cfg)) = scope else { + panic!("expected analysis root to resolve to a CFG, got {scope:?}"); }; - (stage_id, cfg) + (stage, cfg) } /// The parameters of the CFG's entry block. @@ -761,8 +762,9 @@ specialize @source fn @if_body(i64) -> i64 { #[test] fn scf_if_body_demand_follows_result_demand() { let pipeline = parse(IF_BODY_DEMAND); - let (stage, cfg) = source_cfg(&pipeline, "if_body"); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = source_root(&pipeline, "if_body"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, cfg) = cfg_scope(result.root_scope()); let cond = entry_params(&pipeline, cfg)[0]; let if_result = find_value(&pipeline, cfg, |definition| match definition { @@ -810,8 +812,9 @@ specialize @source fn @if_dead(i64) -> i64 { #[test] fn scf_if_dead_result_keeps_only_condition() { let pipeline = parse(IF_DEAD_RESULT); - let (stage, cfg) = source_cfg(&pipeline, "if_dead"); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = source_root(&pipeline, "if_dead"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, cfg) = cfg_scope(result.root_scope()); let cond = entry_params(&pipeline, cfg)[0]; let if_result = find_value(&pipeline, cfg, |definition| match definition { @@ -850,8 +853,9 @@ 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, cfg) = source_cfg(&pipeline, "loop_sum"); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = source_root(&pipeline, "loop_sum"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, cfg) = cfg_scope(result.root_scope()); let params = entry_params(&pipeline, cfg); let (lo, hi, step) = (params[0], params[1], params[2]); @@ -918,8 +922,9 @@ 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, cfg) = source_cfg(&pipeline, "loop_dead"); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = source_root(&pipeline, "loop_dead"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, cfg) = cfg_scope(result.root_scope()); let params = entry_params(&pipeline, cfg); let next = find_value(&pipeline, cfg, |definition| match definition { @@ -968,8 +973,9 @@ specialize @source fn @main(i64, i64) -> i64 { #[test] fn call_arguments_are_demand_roots() { let pipeline = parse(CALL_PURITY); - let (stage, cfg) = source_cfg(&pipeline, "main"); - let result = analyze_demand(&pipeline, stage, cfg).expect("analysis succeeds"); + let (caller_stage, callee) = source_root(&pipeline, "main"); + let result = analyze_demand(&pipeline, caller_stage, callee).expect("analysis succeeds"); + let (_, cfg) = cfg_scope(result.root_scope()); let params = entry_params(&pipeline, cfg); let (x, y) = (params[0], params[1]); @@ -1000,15 +1006,15 @@ specialize @source fn @main(i64, i64) -> i64 { // =========================================================================== mod dense { - use kirin::prelude::{CFG, CompileStage, Pipeline, SSAValue}; + use kirin::prelude::{CompileStage, Pipeline, SSAValue}; use kirin_arith::{Arith, ArithValue}; - use kirin_interpreter::{Body, InterpreterError, ProgramPoint, Scoped}; - use kirin_liveness::{DenseLivenessResult, LiveSet}; + use kirin_interpreter::{Callee, InterpreterError, ProgramPoint, Scoped}; + use kirin_liveness::{DenseLiveness, DenseLivenessResult, LiveSet}; use kirin_scf::StructuredControlFlow; use super::demand::{FOR_CARRIED_DEMAND, IF_DEAD_RESULT}; use super::demand::{ - constant_result, entry_params, find_statement, find_value, parse, source_cfg, + cfg_scope, constant_result, entry_params, find_statement, find_value, parse, source_root, }; use crate::interpreter::ToyDenseBackwardFrame; use crate::language::HighLevel; @@ -1017,14 +1023,19 @@ mod dense { /// Run classic dense liveness with the toy total frame. fn analyze_dense_toy( pipeline: &Pipeline, - stage: CompileStage, - cfg: CFG, + caller_stage: CompileStage, + callee: Callee, ) -> DenseLivenessResult { - kirin_liveness::analyze_dense_with_frame::< - _, + let mut engine: DenseLiveness< + '_, + Stage, + InterpreterError, ToyDenseBackwardFrame, - >(pipeline, stage, cfg) - .expect("analysis succeeds") + > = DenseLiveness::new(pipeline); + let scope = engine + .analyze(caller_stage, callee) + .expect("analysis succeeds"); + DenseLivenessResult::from_engine(&engine, scope) } fn live_set(values: &[SSAValue]) -> LiveSet { @@ -1038,9 +1049,10 @@ mod dense { #[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 scope = (stage, Body::CFG(cfg)); + let (caller_stage, callee) = source_root(&pipeline, "if_dead"); + let dense = analyze_dense_toy(&pipeline, caller_stage, callee); + let (_, cfg) = cfg_scope(dense.root_scope()); + let scope = dense.root_scope(); let point = |item| Scoped::new(scope, item); let cond = entry_params(&pipeline, cfg)[0]; @@ -1106,9 +1118,10 @@ specialize @source fn @if_arms(i64, i64, i64) -> i64 { #[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 scope = (stage, Body::CFG(cfg)); + let (caller_stage, callee) = source_root(&pipeline, "if_arms"); + let dense = analyze_dense_toy(&pipeline, caller_stage, callee); + let (_, cfg) = cfg_scope(dense.root_scope()); + let scope = dense.root_scope(); let params = entry_params(&pipeline, cfg); let (cond, x, y) = (params[0], params[1], params[2]); @@ -1142,9 +1155,10 @@ specialize @source fn @if_arms(i64, i64, i64) -> i64 { #[test] fn dense_loop_carried_fixpoint() { let pipeline = parse(FOR_CARRIED_DEMAND); - let (stage, cfg) = source_cfg(&pipeline, "loop_sum"); - let dense = analyze_dense_toy(&pipeline, stage, cfg); - let scope = (stage, Body::CFG(cfg)); + let (caller_stage, callee) = source_root(&pipeline, "loop_sum"); + let dense = analyze_dense_toy(&pipeline, caller_stage, callee); + let (_, cfg) = cfg_scope(dense.root_scope()); + let scope = dense.root_scope(); let params = entry_params(&pipeline, cfg); let (lo, hi, step) = (params[0], params[1], params[2]); diff --git a/example/toy-lang/src/main.rs b/example/toy-lang/src/main.rs index 7c29f843d6..2f63304d63 100644 --- a/example/toy-lang/src/main.rs +++ b/example/toy-lang/src/main.rs @@ -5,7 +5,7 @@ mod stage; use clap::{Parser, Subcommand}; use kirin::prelude::*; use kirin::pretty::PipelinePrintExt; -use kirin_interpreter::{Body, ProgramPoint, Scoped}; +use kirin_interpreter::{ProgramPoint, Scoped}; use stage::Stage; @@ -118,7 +118,7 @@ fn run_program( Stage::Source(info) => cfg.blocks(info).collect(), Stage::Lowered(info) => cfg.blocks(info).collect(), }; - let scope = (stage, Body::CFG(cfg)); + let scope = dense.root_scope(); let mut boundaries: Vec<_> = blocks .into_iter() .filter_map(|block| { diff --git a/tests/frame_engine_capabilities.rs b/tests/frame_engine_capabilities.rs index 6694a0941e..ecedd9be0d 100644 --- a/tests/frame_engine_capabilities.rs +++ b/tests/frame_engine_capabilities.rs @@ -8,8 +8,8 @@ //! //! Before the split there was one monolithic capability trait carrying every //! operation, so *none* of these four engines could exist: running a block -//! walker meant also supplying `alloc_env`/`free_env`/`resolve_call`/ -//! `enter_function`/`cfg_entry`/`digraph_walk_plan`, and an abstract dataflow +//! walker meant also supplying `alloc_env`/`free_env`/`resolve_callable`/ +//! `cfg_entry`/`digraph_walk_plan`, and an abstract dataflow //! engine had to expose a concrete call convention it never performs. //! //! | mock engine | pins | @@ -108,7 +108,7 @@ struct MockStore(HashMap<(usize, SSAValue), i64>); /// Implements: [`Interp`], [`Env`], [`StatementDispatch`], [`BlockQueries`]. /// /// **Deliberately omits**: [`CallServices`] (no `alloc_env`/`free_env`/ -/// `resolve_call`/`enter_function`), [`CFGQueries`] (no +/// `resolve_callable`), [`CFGQueries`] (no /// `cfg_entry`), and [`DiGraphQueries`] (no `digraph_walk_plan`). /// /// So this engine cannot enter a function, cannot find a CFG's entry block, and @@ -260,20 +260,11 @@ impl CallServices for CallOnlyEngine { fn free_env(&mut self, _index: EnvIndex) -> Result<(), InterpreterError> { unimplemented!("type-level mock") } - fn resolve_call( + fn resolve_callable( &self, _stage: CompileStage, _callee: &Callee, - ) -> Result { - unimplemented!("type-level mock") - } - fn enter_function( - &mut self, - _stage: CompileStage, - _definition: Statement, - _args: Product, - _index: EnvIndex, - ) -> Result, InterpreterError> { + ) -> Result<(FunctionTarget, CallableBody), InterpreterError> { unimplemented!("type-level mock") } } @@ -298,7 +289,7 @@ fn call_frame_runs_on_an_engine_with_only_call_services() { /// *summarizes* a call ([`ForwardDataflowFrameEngine::summarize_call`]) instead /// of descending into it, and reaches a callable body's entry block through /// owner seeding rather than `cfg_entry` — so it should not have to expose -/// activation allocation, activation cleanup, `enter_function`, `resolve_call`, +/// activation allocation, activation cleanup, `resolve_callable`, /// or `cfg_entry` merely to be an abstract dataflow engine. Before the split it /// did. #[derive(Default)] From d405a182f40d58be664f2b5fd80f8353777c5eb0 Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Fri, 11 Sep 2026 14:10:58 -0400 Subject: [PATCH 3/4] refactor(linker): rename resolve_callable to link_and_discover_callable for clarity --- crates/kirin-interpreter/src/core/linker.rs | 4 ++-- .../src/engines/concrete/interp.rs | 4 ++-- .../src/engines/dense_backward/interp.rs | 4 ++-- .../src/engines/sparse_backward/interp.rs | 13 +++++++------ .../src/engines/sparse_forward/interp.rs | 4 ++-- docs/design/interpreter/index.md | 8 ++++++-- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/kirin-interpreter/src/core/linker.rs b/crates/kirin-interpreter/src/core/linker.rs index dae3f4ae16..5b0551c323 100644 --- a/crates/kirin-interpreter/src/core/linker.rs +++ b/crates/kirin-interpreter/src/core/linker.rs @@ -28,13 +28,13 @@ pub trait Linker { ) -> Result; } -/// Run the framework's common callable-root protocol. +/// Link a callee and discover its body for root entry or a nested call. /// /// Linking selects a concrete target; callable-entry dispatch then discovers /// its body in the target's stage. Engines invoke this operation before /// applying their own boundary inputs (runtime arguments, abstract arguments, /// or analysis-specific seeds). -pub(crate) fn resolve_callable( +pub(crate) fn link_and_discover_callable( pipeline: &Pipeline, linker: &Lk, caller_stage: CompileStage, diff --git a/crates/kirin-interpreter/src/engines/concrete/interp.rs b/crates/kirin-interpreter/src/engines/concrete/interp.rs index e6925ad085..8350a800f7 100644 --- a/crates/kirin-interpreter/src/engines/concrete/interp.rs +++ b/crates/kirin-interpreter/src/engines/concrete/interp.rs @@ -4,7 +4,7 @@ use kirin_ir::{ Block, CFG, CompileStage, Pipeline, Product, SSAValue, StageMeta, Statement, Symbol, }; -use crate::core::{linker::resolve_callable, query}; +use crate::core::{linker::link_and_discover_callable, query}; use crate::{ BlockQueries, CFGQueries, CallServices, CallableBody, Callee, Completion, DiGraphQueries, Env, EnvIndex, EnvStackStore, ForwardEval, Frame, FunctionTarget, Interp, InterpDispatch, @@ -158,7 +158,7 @@ where stage: CompileStage, callee: &Callee, ) -> Result<(FunctionTarget, CallableBody), E> { - resolve_callable::(self.pipeline, &self.linker, stage, callee) + link_and_discover_callable::(self.pipeline, &self.linker, stage, callee) } } diff --git a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs index f28b2a7b47..1b3f79c319 100644 --- a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs @@ -54,7 +54,7 @@ use kirin_ir::{ use super::frames::DenseBlockFrame; use crate::Body; -use crate::core::{linker::resolve_callable as resolve_callable_root, query}; +use crate::core::{linker::link_and_discover_callable, query}; use crate::engines::sparse_backward::BodyScope; use crate::{ AbstractInterpreter, BackwardSummaryDeps, Callee, ClassicLiveness, DenseBackwardSemantic, @@ -760,7 +760,7 @@ where /// and drain the block-boundary worklist. Dependencies are discovered from /// terminator edges; unsupported graph roots fail before solving. pub fn analyze(&mut self, stage: CompileStage, callee: Callee) -> Result { - let (target, entry) = resolve_callable_root::< + let (target, entry) = link_and_discover_callable::< DenseBackwardTransfer<'ir, S, V, E, F, Sem>, _, _, diff --git a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs index 9675eda605..6eaba3286b 100644 --- a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs @@ -53,7 +53,7 @@ use kirin_ir::{ SSAKind, SSAValue, StageMeta, Statement, }; -use crate::core::{linker::resolve_callable as resolve_callable_root, query}; +use crate::core::{linker::link_and_discover_callable, query}; use crate::{ AbstractInterpreter, Body, Callee, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, InterpDispatch, InterpLocation, InterpreterError, Linker, OwnerSemantics, OwnerSummaryDeps, @@ -646,11 +646,12 @@ where /// **Propagation**: drain the value worklist; each risen value dispatches /// the rules that translate its demand. pub fn analyze(&mut self, stage: CompileStage, callee: Callee) -> Result { - let (target, entry) = resolve_callable_root::, _, _>( - self.driver.inner().pipeline(), - &self.linker, - stage, - &callee, + let (target, entry) = link_and_discover_callable::< + SparseBackwardDriver<'ir, S, V, E, Sem>, + _, + _, + >( + self.driver.inner().pipeline(), &self.linker, stage, &callee )?; let body = entry.body; if matches!(body, Body::DiGraph(_) | Body::UnGraph(_)) { diff --git a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs index 9a34305826..618c6ed535 100644 --- a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs @@ -40,7 +40,7 @@ use kirin_ir::{ StageMeta, Statement, Symbol, Widen, }; -use crate::core::{linker::resolve_callable as resolve_callable_root, query}; +use crate::core::{linker::link_and_discover_callable, query}; use crate::{ AbstractBlockFrame, AbstractCompletion, AbstractDiGraphFrame, AbstractInterpreter, BlockQueries, Body, CFGQueries, CallEffect, CallServices, CallableBody, Callee, DiGraphQueries, @@ -631,7 +631,7 @@ where stage: CompileStage, callee: &Callee, ) -> Result<(FunctionTarget, CallableBody), E> { - resolve_callable_root::(self.pipeline, &self.linker, stage, callee) + link_and_discover_callable::(self.pipeline, &self.linker, stage, callee) } } diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index c2a0d00e07..8fd3617b2a 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -512,8 +512,12 @@ alone and `::write_child_results` takes `Env` alone. `CallServices` names *services*, not a convention: **`CallFrame` still owns the calling convention** — the operation order, which completions are legal, and freeing the activation exactly once — and this trait only supplies the -primitives. `resolve_callable` is the common linker-plus-target-stage body -discovery protocol; it carries no value product. The trait is deliberately +primitives. The public `CallServices::resolve_callable` method exposes +linker-plus-target-stage body discovery using the engine's configured pipeline +and linker; it carries no value product. The built-in engines share the +crate-private `link_and_discover_callable` helper for root entry and nested +calls. Compiler authors configure resolution policy through `.with_linker(...)`. +The trait is deliberately **not** split further: the standard `CallFrame` consumes all three services together, and their pairing is a safety property (an `alloc_env` without its `free_env` leaks; a second `free_env` double-frees), so From 74bba46bc410ba310e0ac6295735055384408cfe Mon Sep 17 00:00:00 2001 From: Dennis Liew Date: Fri, 11 Sep 2026 16:27:00 -0400 Subject: [PATCH 4/4] feat(interpreter): backward interpreters were missing analyze_by_name and analyze_by_symbol entries similar to forward interpreters --- .../src/engines/dense_backward/interp.rs | 32 ++++++++++++++++++- .../src/engines/sparse_backward/interp.rs | 32 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs index 1b3f79c319..b218ea9e34 100644 --- a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs @@ -49,7 +49,7 @@ use std::marker::PhantomData; use kirin_ir::{ Block, CompileStage, HasArguments, HasBottom, HasResults, Lattice, Pipeline, SSAValue, - StageMeta, Statement, + StageMeta, Statement, Symbol, }; use super::frames::DenseBlockFrame; @@ -756,6 +756,36 @@ where .map_err(E::from) } + /// Resolve a stage and function by name, then seed and solve its blocks. + pub fn analyze_by_name( + &mut self, + stage_name: &str, + function_name: &str, + ) -> Result { + let stage = self + .driver + .inner() + .pipeline() + .stage_by_name(stage_name) + .ok_or_else(|| E::from(InterpreterError::MissingStageName(stage_name.into())))?; + let function = self + .driver + .inner() + .pipeline() + .lookup_function_by_name(function_name) + .ok_or_else(|| E::from(InterpreterError::MissingFunctionName(function_name.into())))?; + self.analyze(stage, Callee::Function(function)) + } + + /// Seed and solve a callable body named by a stage-local symbol. + pub fn analyze_by_symbol( + &mut self, + stage: CompileStage, + symbol: Symbol, + ) -> Result { + self.analyze(stage, symbol.into()) + } + /// Resolve `callee`, seed every block selected by its CFG or Block body, /// and drain the block-boundary worklist. Dependencies are discovered from /// terminator edges; unsupported graph roots fail before solving. diff --git a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs index 6eaba3286b..aed93d7287 100644 --- a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs @@ -50,7 +50,7 @@ use std::mem; use kirin_ir::{ Block, CompileStage, HasArguments, HasBottom, HasResults, HasTop, IsPure, Lattice, Pipeline, - SSAKind, SSAValue, StageMeta, Statement, + SSAKind, SSAValue, StageMeta, Statement, Symbol, }; use crate::core::{linker::link_and_discover_callable, query}; @@ -638,6 +638,36 @@ where Lk: Linker, Sem: SparseBackwardSemantic, { + /// Resolve a stage and function by name, then run the demand fixpoint. + pub fn analyze_by_name( + &mut self, + stage_name: &str, + function_name: &str, + ) -> Result { + let stage = self + .driver + .inner() + .pipeline() + .stage_by_name(stage_name) + .ok_or_else(|| E::from(InterpreterError::MissingStageName(stage_name.into())))?; + let function = self + .driver + .inner() + .pipeline() + .lookup_function_by_name(function_name) + .ok_or_else(|| E::from(InterpreterError::MissingFunctionName(function_name.into())))?; + self.analyze(stage, Callee::Function(function)) + } + + /// Run the demand fixpoint from a stage-local symbol. + pub fn analyze_by_symbol( + &mut self, + stage: CompileStage, + symbol: Symbol, + ) -> Result { + self.analyze(stage, symbol.into()) + } + /// Resolve `callee` and run the demand fixpoint over its callable body. /// /// **Prepass**: walk the body's containment hierarchy, running every