diff --git a/AGENTS.md b/AGENTS.md index 89a0f1f7df..e87979af51 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -101,7 +101,7 @@ For user-defined dialects not in this table, ask the user for domain context dur **Derive Infrastructure:** - `kirin-derive-toolkit` — Shared derive utilities (IR model, darling re-export, template system) - `kirin-derive-ir` — `#[derive(Dialect, StageMeta)]` and IR property traits -- `kirin-derive-interpreter` — `kirin-interpreter` derive proc macros (`#[derive(Interpretable)]`, `#[derive(FunctionEntry)]`, `#[derive(InterpDispatch)]`) +- `kirin-derive-interpreter` — `kirin-interpreter` derive proc macros (`#[derive(Interpretable)]`, `#[derive(InterpDispatch)]`) - `kirin-derive-prettyless` — `#[derive(RenderDispatch)]` (proc-macro) **Analysis:** @@ -117,11 +117,11 @@ For user-defined dialects not in this table, ask the user for domain context dur - **Darling re-export rule**: Derive crates that depend on `kirin-derive-toolkit` must use `kirin_derive_toolkit::prelude::darling` — never import `darling` directly. The workspace has multiple darling versions (0.20 via `bon`, 0.23 via `kirin-derive-toolkit`); a direct import may resolve to the wrong version. -- **Helper attribute pattern**: `#[wraps]` and `#[callable]` are intentionally separate from `#[kirin(...)]` for composability. `#[kirin(...)]` is the carry attribute for dialect-specific options (parsed by darling). `#[wraps]` is a generic helper for delegation/wrapper patterns, and `#[callable]` is interpreter-specific. Keeping them as bare attributes lets different derive macros compose independently — e.g. a type can use `#[wraps]` with both `#[derive(Dialect)]` and `#[derive(Interpretable)]` without coupling those derives. Since darling's `#[darling(attributes(...))]` only supports `#[attr(key = val)]` form, bare flag attributes are parsed manually via `attrs.iter().any(|a| a.path().is_ident("name"))`. +- **Helper attribute pattern**: `#[wraps]` is a generic, bare helper for delegation and is shared by `Dialect` and `Interpretable`. `#[kirin(...)]` carries IR structural options, including `#[kirin(callable_body)]` on a direct definition's distinguished body field. `Dialect` derives `HasCallableBody` for every language: zero markers returns `None`, one `Block`/`CFG`/`DiGraph`/`UnGraph` marker selects that body, and wrappers delegate automatically. Do not infer callability from body ownership or require callable wrapper markers. - **`#[wraps]` and `#[kirin(terminator)]` interaction**: When `#[wraps]` is per-variant, `is_terminator()` is automatically delegated to the inner type — no `#[kirin(terminator)]` needed. When `#[wraps]` is at enum level (all variants wrap), you still need explicit `#[kirin(terminator)]` on terminator variants. See `ArithFunctionLanguage` (per-variant, no terminator annotations) vs the inline `NumericLanguage` in `tests/roundtrip/arith.rs` (enum-level, explicit annotations). -- **Custom Layout for derive-specific attributes**: When a derive macro needs attributes beyond `StandardLayout` (which has `()` for all extras), define a custom `Layout` impl in that derive module. This keeps derive-specific attributes out of the core IR. See `EvalCallLayout` in `kirin-derive-interpreter` as an example. +- **Custom Layout for derive-specific attributes**: When a derive macro needs attributes beyond `StandardLayout` (which has `()` for all extras), define a custom `Layout` impl in that derive module. This keeps derive-specific attributes out of the core IR. Callable-body designation is shared IR structure, so it belongs in the common field metadata rather than an interpreter-specific layout. - **Downstream crate path (`HasCratePath`)**: Each derive macro has its own crate path attribute — `#[kirin(crate = ...)]` is the IR crate, `#[chumsky(crate = ...)]` is the parser crate, `#[pretty(crate = ...)]` is the printer crate. These are independent. Implement `HasCratePath` on your `ExtraGlobalAttrs` and use `Input::extra_crate_path()` to resolve with a default. @@ -143,7 +143,7 @@ For user-defined dialects not in this table, ask the user for domain context dur - **`Interp` is the engine driver; `Interpretable` is the dialect trait**: `Interp` exposes `Value`, `Error`, `Effect`, `Semantics` (a `SemanticKey`), and the current statement location (`stage()`/`statement()`/`index()`). Dialect rules receive the engine `interp` directly and are selected by a compile-time semantic key (`ForwardEval`, `StrongDemand`, `ClassicLiveness`, downstream keys), not by a runtime context object and never by a raw solver shape. One dialect type carries one rule per key without coherence conflicts — including two keys on the same shape; a new analysis adds a new key (declaring its shape) + effect algebra instead of adding cases to an existing effect. -- **Two-persona contract**: Dialect authors implement `Interpretable` (and `FunctionEntry` for callable statements). A rule receives the engine `interp: &mut I` directly and uses the `interp.read`/`interp.write` helpers, which are **default methods on `SparseForwardInterp`** operating on the engine's current activation (`interp.index()`); forward rules bound `I: SparseForwardInterp` so they can return `SparseForwardEffect` as `I::Effect`, plus value-domain bounds on `I::Value`. Compiler authors compose language enums with derives, pick a value type, error type, engine, and linker; when needed, they can also opt into custom frame types or custom abstract policies. Imports come from `kirin_interpreter::dialect` and `kirin_interpreter::engine` respectively. Customizing traversal is part of the compiler-author surface, not a separate persona. +- **Two-persona contract**: Dialect authors implement `Interpretable` and designate a callable definition's body with `#[kirin(callable_body)]`. A rule receives the engine `interp: &mut I` directly and uses the `interp.read`/`interp.write` helpers, which are **default methods on `SparseForwardInterp`** operating on the engine's current activation (`interp.index()`); forward rules bound `I: SparseForwardInterp` so they can return `SparseForwardEffect` as `I::Effect`, plus value-domain bounds on `I::Value`. Compiler authors compose language enums with derives, pick a value type, error type, engine, and linker; when needed, they can also opt into custom frame types or custom abstract policies. Imports come from `kirin_interpreter::dialect` and `kirin_interpreter::engine` respectively. Customizing traversal is part of the compiler-author surface, not a separate persona. - **Statement dispatch**: Dialect statements implement `Interpretable` — specialized on the engine `I` and the `ForwardEval` semantic key. A forward rule (`I: SparseForwardInterp`) reads/writes SSA state through the `SparseForwardInterp` **default-method** helpers (`interp.read`, `interp.write`, `interp.read_many`, `interp.write_results` — which delegate to the engine's `Env` storage access at `interp.index()`; there is no `ForwardCtx`/`ValueContext` type) and returns `Result`, building `SparseForwardEffect`: atomic ops return `SparseForwardEffect::Next`; control ops return `Jump`/`Branch` (CFG edges), `Call`, `Yield`/`Return` (completions), or `Push` (run a sub-computation by pushing a dialect-owned frame, then bind its results). There is **no** framework "scope" type and no framework "explore alternatives" effect. @@ -153,13 +153,13 @@ 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, 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. +- **Calling conventions are linkers**: `Linker` resolves `Callee` relative to `lookup_stage` into `LinkTarget { stage, specialization }` 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. Body discovery then reads the specialization's authoritative definition at `target.stage` through IR's `HasCallableBody`, yielding `ResolvedCallable { target, body }`. Linking selects identity; discovery reads structure; engines initialize their own boundary inputs. 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`). - **Call-body traversal is concrete configuration**: `CallFrame` owns the call convention (resolve, allocate the activation, enter, suspend, validate the completion, free exactly once, bind results) and delegates *only* which child continuation enters the callee body to `CallBodyTraversal`. The private composition root selects the traversal as `T` in its `CallFrame` stack-item variant; `CallFrame` means `CallFrame`. `CallRequest` carries no `T`; converting it into the configured stack-item attaches that compile-time choice. **Concrete execution only** — forward abstract interpretation summarizes calls (`AbstractCallFrame`) and maps a callable body to an `Owner` instead of descending, and the backward engines never walk callable bodies through a call frame. This traversal is not consulted for nested bodies: `scf.if`/`scf.for` and other structured operations keep choosing their own dialect continuations. -- **Engine capabilities are per-frame, not per-engine**: `core/frame.rs` splits the forward engine surface into component traits named after the *capability* they supply — `StatementDispatch: Interp` (dispatch a statement), `BlockQueries: Interp` (read-only block queries), `CFGQueries: BlockQueries` (`cfg_entry`), `DiGraphQueries: Interp` (`digraph_walk_plan`), `CallServices: Env` (activation storage, linking, callable-entry dispatch; kept whole because `CallFrame` consumes all four and their pairing is a safety property — `CallFrame` still owns the *convention*). **A member frame bounds only what it consumes** (`ScfIfFrame` needs just `FrameEngine`; `ScfForFrame` just `Env`). A stack-item composition's `Frame` implementation requires the union of its variants' member bounds, because one engine must be able to run every continuation admitted to that stack. `ForwardDataflowFrameEngine` extends only `Env + StatementDispatch + BlockQueries + DiGraphQueries` — an abstract engine summarizes calls and seeds owners, so it must **not** be made to inherit `CallServices` or `CFGQueries`. `tests/frame_engine_capabilities.rs` holds mock engines whose ability to compile is the regression test; widening a member frame's bound breaks it. +- **Engine capabilities are per-frame, not per-engine**: `core/frame.rs` splits the forward engine surface into component traits named after the *capability* they supply — `StatementDispatch: Interp` (dispatch a statement), `BlockQueries: Interp` (read-only block queries), `CFGQueries: BlockQueries` (`cfg_entry`), `DiGraphQueries: Interp` (`digraph_walk_plan`), `CallServices: Env` (activation allocation/freeing and resolved-callable discovery; kept whole because `CallFrame` consumes their pairing as a safety property — `CallFrame` still owns the *convention*). **A member frame bounds only what it consumes** (`ScfIfFrame` needs just `FrameEngine`; `ScfForFrame` just `Env`). A stack-item composition's `Frame` implementation requires the union of its variants' member bounds, because one engine must be able to run every continuation admitted to that stack. `ForwardDataflowFrameEngine` extends only `Env + StatementDispatch + BlockQueries + DiGraphQueries` — an abstract engine summarizes calls and seeds owners, so it must **not** be made to inherit `CallServices` or `CFGQueries`. `tests/frame_engine_capabilities.rs` holds mock engines whose ability to compile is the regression test; widening a member frame's bound breaks it. - **Naming rule for these traits**: `drive_frames` is the only *driver* at this layer (the frame-stack loop); `ForwardDriver`/`DenseBackwardDriver` are fixpoint-driver structs. Capability traits are named for what they supply, never `*FrameDriver` — do not reintroduce that suffix, and do not add compatibility aliases for it. `FrameEngine` = minimal contract for the generic frame stack; `ForwardFrameEngine`/`ForwardDataflowFrameEngine`/`DenseBackwardFrameEngine` = capability sets required by their corresponding families of computations. The `*Queries` traits must stay **read-only** and require only `Interp`: block-entry binding lives on the crate-private `BlockBinding: Env + BlockQueries` so no query trait's name hides a store mutation. Binding into an explicitly named activation is `Env::bind_values(index, slots, values)`; `SparseForwardInterp::write_results` is the dialect-facing current-activation helper. Names describe the operation, not the `Product` container. @@ -167,13 +167,13 @@ For user-defined dialects not in this table, ask the user for domain context dur - **Customizing traversal**: Every member continuation and stack-item composition implements the same three methods (`step_into`/`resume_done_into`/`resume_into`). A reusable member returns `FrameEffect`, where `F` is its configured child representation; a stack-item enum dispatches to the member and maps `Self` into the matching variant. Concrete and language-specific compositions list their admitted framework/dialect continuations explicitly, provide narrow conversions from the member continuations or entry requests they accept, and keep the stack-item enum private where the public engine API permits. Callable-body walker selection remains the separate `CallBodyTraversal` configuration seam; structured dialects may push dialect-owned frames through `SparseForwardEffect::Push`; ordinary dialects never name frame types. Abstract summary keying and join/widen policy stay in `CallContext`/`WideningStrategy`. -- **Stage dispatch**: stage enums add `#[derive(InterpDispatch)]` next to `StageMeta`/`ParseDispatch`; single-language pipelines get a blanket impl. `InterpDispatch` is keyed on the engine alone; the dispatched key is always `I::Semantics`. The engine sets its current location then passes itself to dispatch, which forwards to the matching `Interpretable`/`FunctionEntry` rule. Engine-internal IR queries go through `StageQuery`. +- **Stage dispatch**: stage enums add `#[derive(InterpDispatch)]` next to `StageMeta`/`ParseDispatch`; single-language pipelines get a blanket impl. `InterpDispatch` is keyed on the engine alone; the dispatched key is always `I::Semantics`. The engine sets its current location then passes itself to dispatch, which forwards to the matching `Interpretable` rule. Engine-independent structural queries, including callable-body discovery, go through `StageQuery`; derived dialects with no callables satisfy it too. - **Products and multi-result**: `kirin_ir::Product` is the framework packet for call/block/branch arguments, function returns, and SCF yields. `HasProductValue` is only for value domains that expose an explicit tuple runtime value (the tuple dialect); it is not needed for ordinary multi-result plumbing. -- **Derive naming rule**: every interpreter derive is named after the trait it implements (`Interpretable`, `FunctionEntry`, `InterpDispatch`). Do not add derives whose names are not trait names. +- **Derive naming rule**: every interpreter derive is named after the trait it implements (`Interpretable`, `InterpDispatch`). Do not add derives whose names are not trait names. -- **Function dialect naming**: `kirin_function::Function` is the standard function statement. New code should use `Function` with `FunctionEntry` and `SparseForwardEffect::Call`/`SparseForwardEffect::Return`. +- **Function dialect naming**: `kirin_function::Function` is the standard function statement. New code should use `Function` with its derived `HasCallableBody` and `SparseForwardEffect::Call`/`SparseForwardEffect::Return`. - **Backward analyses (implemented)**: liveness ships as **two** analyses in `kirin-liveness`, both real framework clients. *Strong liveness* (`analyze_demand`, `StrongDemand`) is per-SSA-value demand: summary owners ARE scope-qualified SSA values (`Scoped<(CompileStage, CFG), SSAValue>`), the driver's default self-dependent index is the demand worklist, ordinary dialects are a one-liner (`interp.demand_uses_if_observable(self)` on `DemandInterp` — purity-aware via `IsPure`: impure statements and terminator/return operands are roots), and SCF needs no heterogeneous continuation stack (loop-carried demand converges on the value worklist; the SCF rules use `block_params`/`terminator_args` queries). *Classic per-point liveness* (`analyze_dense`, `ClassicLiveness`) is the textbook kill-defs/gen-all-uses transfer over block owners with backward block walks (`DenseBlockFrame`, `absorb_edges` maps successor live-ins across edges with pass-through for dominated direct cross-block uses); SCF owns dense member continuations (`DenseScfIfFrame` arm join, `DenseScfForFrame` loop fixpoint), and a language-private stack-item enum composes them with `DenseBlockFrame` through narrow `From` conversions (see toy-lang's `ToyDenseBackwardFrame`). Strong per-point sets are the composition `dense ∩ demanded`, not a third analysis. CFG topology (blocks incl. nested bodies, feeders) comes from `StageQuery` actions — enumeration only; use/def/edge-arg *semantics* stay in dialect rules. One dialect carries one rule per semantic key without coherence conflicts, and two keys can share one shape — the shipped dialects (each carrying `ForwardEval` + `StrongDemand` + `ClassicLiveness` rules) are the living evidence. Dense point-observation ownership and the adequacy of block owners/body coverage are explicitly deferred architecture questions, not claims established by the current composition. diff --git a/crates/kirin-constprop/src/context.rs b/crates/kirin-constprop/src/context.rs index 1483488cbc..aa94209b71 100644 --- a/crates/kirin-constprop/src/context.rs +++ b/crates/kirin-constprop/src/context.rs @@ -22,7 +22,7 @@ use std::collections::{HashMap, HashSet}; use kirin_interpreter::{ - CallContext, ContextInsensitive, FunctionTarget, InterpreterError, WideningStrategy, + CallContext, ContextInsensitive, InterpreterError, LinkTarget, WideningStrategy, }; use kirin_ir::{CompileStage, Product, SpecializedFunction}; @@ -69,9 +69,9 @@ impl Default for ConstPropContext { impl CallContext for ConstPropContext { type Key = (CompileStage, SpecializedFunction, CallCtx); - fn key(&mut self, target: &FunctionTarget, args: &Product) -> Self::Key { + fn key(&mut self, target: &LinkTarget, args: &Product) -> Self::Key { let stage = target.stage; - let function = target.function; + let function = target.specialization; let ctx = match all_const(args) { Some(consts) => { let admitted = self.admitted.entry((stage, function)).or_default(); diff --git a/crates/kirin-derive-chumsky/src/validation.rs b/crates/kirin-derive-chumsky/src/validation.rs index 5c926f114c..2138bc7a74 100644 --- a/crates/kirin-derive-chumsky/src/validation.rs +++ b/crates/kirin-derive-chumsky/src/validation.rs @@ -610,6 +610,7 @@ mod tests { edge: false, }, fields, + callable_body: None, wraps: None, extra: (), extra_attrs: ChumskyStatementAttrs { format: None }, diff --git a/crates/kirin-derive-interpreter/src/function_entry.rs b/crates/kirin-derive-interpreter/src/function_entry.rs deleted file mode 100644 index 2f88e272d1..0000000000 --- a/crates/kirin-derive-interpreter/src/function_entry.rs +++ /dev/null @@ -1,182 +0,0 @@ -use kirin_derive_toolkit::context::DeriveContext; -use kirin_derive_toolkit::ir::{Data, Input}; -use kirin_derive_toolkit::misc::from_str; -use kirin_derive_toolkit::prelude::darling; -use proc_macro2::TokenStream; -use quote::quote; - -use crate::interpretable::parse_interpret_crate_path; -use crate::layout::InterpreterLayout; - -const DEFAULT_IR_CRATE: &str = "::kirin::ir"; - -pub fn do_derive_function_entry(input: &syn::DeriveInput) -> darling::Result { - let ir = Input::::from_derive_input(input)?; - let interp_crate = parse_interpret_crate_path(input)?; - let ir_crate: syn::Path = ir - .attrs - .crate_path - .clone() - .unwrap_or_else(|| from_str(DEFAULT_IR_CRATE)); - - ir.compose() - .add(move |ctx: &DeriveContext<'_, InterpreterLayout>| { - emit_function_entry(ctx, &interp_crate, &ir_crate) - }) - .build() -} - -fn emit_function_entry( - ctx: &DeriveContext<'_, InterpreterLayout>, - interp_crate: &syn::Path, - _ir_crate: &syn::Path, -) -> darling::Result> { - validate_function_entry(ctx)?; - - let type_name = &ctx.meta.name; - 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::new(); - for wrapper_ty in callable_wrappers { - predicates.push(syn::parse_quote! { - #wrapper_ty: #interp_crate::FunctionEntry - }); - } - let extra_where: syn::WhereClause = syn::parse_quote! { where #(#predicates),* }; - let where_clause = - kirin_derive_toolkit::codegen::combine_where_clauses(Some(&extra_where), original_where); - - let Data::Enum(data) = &ctx.input.data else { - return Err(darling::Error::custom("expected enum input")); - }; - - let mut arms = Vec::new(); - for variant in &data.variants { - let stmt_ctx = ctx - .statements - .get(&variant.name.to_string()) - .ok_or_else(|| darling::Error::custom("missing statement context"))?; - let variant_name = &variant.name; - if is_entry_forwarding(ctx, stmt_ctx) { - let pattern = &stmt_ctx.pattern; - let arm_pattern = if stmt_ctx.pattern.is_empty() { - quote! { Self::#variant_name } - } else { - quote! { Self::#variant_name #pattern } - }; - let binding = stmt_ctx - .wrapper_binding - .as_ref() - .ok_or_else(|| darling::Error::custom("expected wrapper binding"))?; - arms.push(quote! { - #arm_pattern => #binding.function_entry() - }); - } else { - arms.push(quote! { - Self::#variant_name { .. } => None - }); - } - } - - let body = if data.has_hidden_variants { - quote! { - match self { - #(#arms,)* - _ => unreachable!() - } - } - } else { - quote! { - match self { - #(#arms),* - } - } - }; - - Ok(vec![quote! { - #[automatically_derived] - impl #impl_generics #interp_crate::FunctionEntry for #type_name #ty_generics #where_clause { - fn function_entry(&self) -> Option<#interp_crate::CallableBody> { - #body - } - } - }]) -} - -fn validate_function_entry(ctx: &DeriveContext<'_, InterpreterLayout>) -> darling::Result<()> { - if !matches!(ctx.input.data, Data::Enum(_)) { - return Err(darling::Error::custom( - "Cannot derive `FunctionEntry`: expected a wrapper enum", - )); - } - let callable_wrappers = collect_callable_wrappers(ctx); - if callable_wrappers.is_empty() { - return Err(darling::Error::custom( - "derive(FunctionEntry) requires at least one #[callable] wrapper variant", - )); - } - Ok(()) -} - -fn is_entry_forwarding( - ctx: &DeriveContext<'_, InterpreterLayout>, - stmt_ctx: &kirin_derive_toolkit::context::StatementContext<'_, InterpreterLayout>, -) -> bool { - let callable_all = ctx.input.extra_attrs.callable; - let is_callable = callable_all || stmt_ctx.stmt.extra_attrs.callable; - stmt_ctx.is_wrapper && is_callable -} - -fn collect_callable_wrappers<'a>( - ctx: &'a DeriveContext<'_, InterpreterLayout>, -) -> Vec<&'a syn::Type> { - ctx.statements - .values() - .filter(|stmt_ctx| is_entry_forwarding(ctx, stmt_ctx)) - .filter_map(|stmt_ctx| stmt_ctx.wrapper_type) - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - use kirin_test_utils::rustfmt; - - fn generate_function_entry_code(input: syn::DeriveInput) -> String { - let tokens = do_derive_function_entry(&input).expect("failed to generate FunctionEntry"); - rustfmt(tokens.to_string()) - } - - #[test] - fn function_entry_for_callable_variants() { - let input: syn::DeriveInput = syn::parse_quote! { - #[wraps] - #[kirin(type = T)] - enum Lexical { - #[callable] - Function(Function), - Call(Call), - #[callable] - Lambda(Lambda), - Return(Return), - } - }; - insta::assert_snapshot!(generate_function_entry_code(input)); - } - - #[test] - fn function_entry_rejects_without_callable() { - let input: syn::DeriveInput = syn::parse_quote! { - #[wraps] - #[kirin(type = T)] - enum Lexical { - Function(Function), - Return(Return), - } - }; - let err = do_derive_function_entry(&input).unwrap_err().to_string(); - assert!(err.contains("requires at least one #[callable]")); - } -} diff --git a/crates/kirin-derive-interpreter/src/interp_dispatch.rs b/crates/kirin-derive-interpreter/src/interp_dispatch.rs index 3d1f93a962..b3823b7254 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 + #dialect_ty: #interp_crate::Interpretable<__InterpI, <__InterpI as #interp_crate::Interp>::Semantics> }); } let mut where_clause = original_where.cloned().unwrap_or_else(|| syn::WhereClause { @@ -84,13 +84,6 @@ pub fn generate(input: &DeriveInput) -> Result { ) } }); - let entry_arms = build_arms(&variants, enum_ident, |_| { - quote! { - #interp_crate::InterpDispatch::dispatch_function_entry( - stage_info, definition, - ) - } - }); Ok(quote! { #[automatically_derived] @@ -109,15 +102,6 @@ pub fn generate(input: &DeriveInput) -> Result { #statement_arms } } - - fn dispatch_function_entry( - &self, - definition: #ir_crate::Statement, - ) -> Result<#interp_crate::CallableBody, <__InterpI as #interp_crate::Interp>::Error> { - match self { - #entry_arms - } - } } }) } diff --git a/crates/kirin-derive-interpreter/src/layout.rs b/crates/kirin-derive-interpreter/src/layout.rs deleted file mode 100644 index 4dccd53c33..0000000000 --- a/crates/kirin-derive-interpreter/src/layout.rs +++ /dev/null @@ -1,52 +0,0 @@ -use kirin_derive_toolkit::ir::Layout; -use kirin_derive_toolkit::prelude::darling::{self, FromDeriveInput, FromVariant}; - -#[derive(Debug, Clone)] -pub struct InterpreterLayout; - -impl Layout for InterpreterLayout { - type StatementExtra = (); - type ExtraGlobalAttrs = InterpreterGlobalAttrs; - type ExtraStatementAttrs = InterpreterStatementAttrs; - type ExtraFieldAttrs = (); - - fn extra_statement_attrs_from_input( - input: &syn::DeriveInput, - ) -> darling::Result { - InterpreterStatementAttrs::from_derive_input(input) - } -} - -#[derive(Debug, Clone)] -pub struct InterpreterGlobalAttrs { - pub callable: bool, -} - -impl FromDeriveInput for InterpreterGlobalAttrs { - fn from_derive_input(input: &syn::DeriveInput) -> darling::Result { - Ok(Self { - callable: input.attrs.iter().any(|a| a.path().is_ident("callable")), - }) - } -} - -#[derive(Debug, Clone)] -pub struct InterpreterStatementAttrs { - pub callable: bool, -} - -impl FromDeriveInput for InterpreterStatementAttrs { - fn from_derive_input(input: &syn::DeriveInput) -> darling::Result { - Ok(Self { - callable: input.attrs.iter().any(|a| a.path().is_ident("callable")), - }) - } -} - -impl FromVariant for InterpreterStatementAttrs { - fn from_variant(variant: &syn::Variant) -> darling::Result { - Ok(Self { - callable: variant.attrs.iter().any(|a| a.path().is_ident("callable")), - }) - } -} diff --git a/crates/kirin-derive-interpreter/src/lib.rs b/crates/kirin-derive-interpreter/src/lib.rs index 32277f4b26..9d11a23a55 100644 --- a/crates/kirin-derive-interpreter/src/lib.rs +++ b/crates/kirin-derive-interpreter/src/lib.rs @@ -1,10 +1,8 @@ extern crate proc_macro; mod frame; -mod function_entry; mod interp_dispatch; mod interpretable; -mod layout; use proc_macro::TokenStream; use syn::parse_macro_input; @@ -20,19 +18,8 @@ pub fn derive_interpretable(input: TokenStream) -> TokenStream { } } -/// 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 { - let ast = parse_macro_input!(input as syn::DeriveInput); - match function_entry::do_derive_function_entry(&ast) { - Ok(tokens) => tokens.into(), - Err(e) => e.write_errors().into(), - } -} - /// Derive `InterpDispatch` for a stage enum, dispatching statement -/// interpretation and function entry to each stage's language. Uses the same +/// interpretation to each stage's language. Uses the same /// `#[stage(...)]` attributes as `StageMeta` / `ParseDispatch`. #[proc_macro_derive(InterpDispatch, attributes(stage))] pub fn derive_interp_dispatch(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 deleted file mode 100644 index 6f6c1987fa..0000000000 --- a/crates/kirin-derive-interpreter/src/snapshots/kirin_derive_interpreter__function_entry__tests__function_entry_for_callable_variants.snap +++ /dev/null @@ -1,19 +0,0 @@ ---- -source: crates/kirin-derive-interpreter/src/function_entry.rs -expression: generate_function_entry_code(input) ---- -#[automatically_derived] -impl ::kirin_interpreter::FunctionEntry for Lexical -where - Function: ::kirin_interpreter::FunctionEntry, - Lambda: ::kirin_interpreter::FunctionEntry, -{ - fn function_entry(&self) -> Option<::kirin_interpreter::CallableBody> { - match self { - 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-derive-ir/src/generate.rs b/crates/kirin-derive-ir/src/generate.rs index b272006ea8..3070a5cd5a 100644 --- a/crates/kirin-derive-ir/src/generate.rs +++ b/crates/kirin-derive-ir/src/generate.rs @@ -208,7 +208,10 @@ pub(crate) fn generate_dialect(ast: &syn::DeriveInput) -> darling::Result TraitImplTemplate { + let struct_crate = crate_path.clone(); + let variant_crate = crate_path.clone(); + let bounds_crate = crate_path.clone(); + TraitImplTemplate::new(from_str("HasCallableBody"), from_str("::kirin::ir")) + .where_clause(move |ctx| { + let bounds: Vec = ctx + .statements + .values() + .filter_map(|stmt| stmt.wrapper_type) + .map(|ty| syn::parse_quote!(#ty: #bounds_crate::HasCallableBody)) + .collect(); + (!bounds.is_empty()).then(|| syn::parse_quote!(where #(#bounds),*)) + }) + .method(MethodSpec { + name: from_str("callable_body"), + self_arg: quote! { &self }, + params: vec![], + return_type: Some(quote! { ::core::option::Option<#crate_path::Body> }), + pattern: Box::new(Custom::separate( + move |_, stmt| { + let body = projection(stmt, &struct_crate); + if !stmt.is_wrapper && stmt.stmt.callable_body.is_none() { + return Ok(body); + } + let pattern = &stmt.pattern; + // Use the same bindings as enum arms, including tuple fields. + Ok(quote! { let Self #pattern = self; #body }) + }, + move |_, stmt| Ok(projection(stmt, &variant_crate)), + )), + generics: None, + method_where_clause: None, + }) +} + +fn projection(stmt: &StatementContext<'_, StandardLayout>, ir: &syn::Path) -> TokenStream { + if let (Some(ty), Some(binding)) = (stmt.wrapper_type, &stmt.wrapper_binding) { + return quote! { <#ty as #ir::HasCallableBody>::callable_body(#binding) }; + } + match &stmt.stmt.callable_body { + Some(field) => { + let binding = field.name(); + quote! { ::core::option::Option::Some(#ir::Body::from(*#binding)) } + } + None => quote! { ::core::option::Option::None }, + } +} diff --git a/crates/kirin-derive-ir/src/lib.rs b/crates/kirin-derive-ir/src/lib.rs index 1c2c12a58c..4b20a02592 100644 --- a/crates/kirin-derive-ir/src/lib.rs +++ b/crates/kirin-derive-ir/src/lib.rs @@ -4,6 +4,7 @@ use proc_macro::TokenStream; use syn::parse_macro_input; mod generate; +mod has_callable_body; mod has_signature; mod project; diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap index 876c781ed3..aa8a46ca03 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_custom_crate_path.snap @@ -336,3 +336,9 @@ impl kirin_ir::HasSignature for Nop { None } } +#[automatically_derived] +impl kirin_ir::HasCallableBody for Nop { + fn callable_body(&self) -> ::core::option::Option { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap index 835441c8a2..301b7b1a0c 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_does_not_generate_lift_project.snap @@ -480,3 +480,15 @@ impl ::kirin::ir::HasSignature for MixedOps { } } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for MixedOps +where + AddOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + match self { + Self::Add(field_0) => ::callable_body(field_0), + Self::Literal { value } => ::core::option::Option::None, + } + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap index 26d8d573a3..4f0d706eec 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_mixed_wraps_and_fields.snap @@ -480,3 +480,15 @@ impl ::kirin::ir::HasSignature for MixedOps { } } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for MixedOps +where + AddOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + match self { + Self::Add(field_0) => ::callable_body(field_0), + Self::Literal { value } => ::core::option::Option::None, + } + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap index 5483766948..e14df1db5c 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_pure_wrapper_generates_lift_project.snap @@ -569,6 +569,25 @@ impl ::kirin::ir::HasSignature for CompositeOps { } } #[automatically_derived] +impl ::kirin::ir::HasCallableBody for CompositeOps +where + AlphaOp: ::kirin::ir::HasCallableBody, + BetaOp: ::kirin::ir::HasCallableBody, + GammaOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + match self { + Self::Alpha(field_0) => { + ::callable_body(field_0) + } + Self::Beta(field_0) => ::callable_body(field_0), + Self::Gamma(field_0) => { + ::callable_body(field_0) + } + } + } +} +#[automatically_derived] impl ::core::convert::From for CompositeOps { fn from(from: AlphaOp) -> Self { CompositeOps::Alpha(from) diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap index ccd3fab3e8..a10916cca7 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_with_wraps.snap @@ -481,6 +481,19 @@ impl ::kirin::ir::HasSignature for ArithLanguage { } } #[automatically_derived] +impl ::kirin::ir::HasCallableBody for ArithLanguage +where + AddOp: ::kirin::ir::HasCallableBody, + SubOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + match self { + Self::Add(field_0) => ::callable_body(field_0), + Self::Sub(field_0) => ::callable_body(field_0), + } + } +} +#[automatically_derived] impl ::core::convert::From for ArithLanguage { fn from(from: AddOp) -> Self { ArithLanguage::Add(from) diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap index 87ec00ed8d..2cc0784e1f 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wrapper_with_side_fields_no_lift_project.snap @@ -510,3 +510,20 @@ impl ::kirin::ir::HasSignature for MixedWraps { } } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for MixedWraps +where + SimpleOp: ::kirin::ir::HasCallableBody, + InnerOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + match self { + Self::Simple(field_0) => { + ::callable_body(field_0) + } + Self::Wrapped { inner, tag } => { + ::callable_body(inner) + } + } + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap index 87ec00ed8d..2cc0784e1f 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_extra_fields_from_impl.snap @@ -510,3 +510,20 @@ impl ::kirin::ir::HasSignature for MixedWraps { } } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for MixedWraps +where + SimpleOp: ::kirin::ir::HasCallableBody, + InnerOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + match self { + Self::Simple(field_0) => { + ::callable_body(field_0) + } + Self::Wrapped { inner, tag } => { + ::callable_body(inner) + } + } + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap index a116ae256a..ddc4fbf835 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_enum_wraps_with_terminator.snap @@ -493,6 +493,23 @@ impl ::kirin::ir::HasSignature for CfOps { } } #[automatically_derived] +impl ::kirin::ir::HasCallableBody for CfOps +where + BranchOp: ::kirin::ir::HasCallableBody, + ReturnOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + match self { + Self::Branch(field_0) => { + ::callable_body(field_0) + } + Self::Return(field_0) => { + ::callable_body(field_0) + } + } + } +} +#[automatically_derived] impl ::core::convert::From for CfOps { fn from(from: BranchOp) -> Self { CfOps::Branch(from) diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap index 59e8cbddd3..7f89c36fc3 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_all_properties.snap @@ -350,3 +350,9 @@ impl ::kirin::ir::HasSignature for Constant { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for Constant { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap index 86f736f475..120c35ac67 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_edge.snap @@ -350,3 +350,9 @@ impl ::kirin::ir::HasSignature for ZxWire { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for ZxWire { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap index 50c6775845..af0a285f26 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_no_fields.snap @@ -336,3 +336,9 @@ impl ::kirin::ir::HasSignature for Nop { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for Nop { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap index dd7dea576a..d02b4e7c83 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_option_block.snap @@ -412,3 +412,9 @@ impl ::kirin::ir::HasSignature for ConditionalOp { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for ConditionalOp { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap index fa499ea14d..8e1d9f5cc1 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_symbol.snap @@ -348,3 +348,9 @@ impl ::kirin::ir::HasSignature for CallExtern { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for CallExtern { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap index 6b7a09a472..6f15eead03 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_terminator.snap @@ -350,3 +350,9 @@ impl ::kirin::ir::HasSignature for Return { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for Return { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap index ccc481ba9e..12cf39a947 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_vec_ssa_value.snap @@ -348,3 +348,9 @@ impl ::kirin::ir::HasSignature for CallOp { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for CallOp { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap index 5f65f8cc7a..4187cd2952 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_cfg_block.snap @@ -426,3 +426,9 @@ impl ::kirin::ir::HasSignature for IfOp { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for IfOp { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap index 4678a818f8..a947b189f9 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_digraph.snap @@ -426,3 +426,9 @@ impl ::kirin::ir::HasSignature for QuantumEval { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for QuantumEval { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap index c5502369cd..5887870efb 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ssa_fields.snap @@ -350,3 +350,9 @@ impl ::kirin::ir::HasSignature for BinaryOp { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for BinaryOp { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap index dc0fbac508..9785ec7e71 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_successors.snap @@ -350,3 +350,9 @@ impl ::kirin::ir::HasSignature for Branch { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for Branch { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap index 83d382c3de..db9b2e9471 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_struct_with_ungraph.snap @@ -412,3 +412,9 @@ impl ::kirin::ir::HasSignature for ZxEval { None } } +#[automatically_derived] +impl ::kirin::ir::HasCallableBody for ZxEval { + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + ::core::option::Option::None + } +} diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap index e8bed0ec87..1b99037537 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project.snap @@ -357,6 +357,16 @@ impl ::kirin::ir::HasSignature for WrapperOp { } } #[automatically_derived] +impl ::kirin::ir::HasCallableBody for WrapperOp +where + InnerOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + let Self(field_0) = self; + ::callable_body(field_0) + } +} +#[automatically_derived] impl ::core::convert::From for WrapperOp { fn from(from: InnerOp) -> Self { WrapperOp(from) diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap index 9ad025d04f..d2153baa8e 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_generates_lift_project_bridge.snap @@ -357,6 +357,16 @@ impl ::kirin::ir::HasSignature for WrapperOp { } } #[automatically_derived] +impl ::kirin::ir::HasCallableBody for WrapperOp +where + InnerOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + let Self(field_0) = self; + ::callable_body(field_0) + } +} +#[automatically_derived] impl ::core::convert::From for WrapperOp { fn from(from: InnerOp) -> Self { WrapperOp(from) diff --git a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap index e8bed0ec87..21a0c7b27e 100644 --- a/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap +++ b/crates/kirin-derive-ir/src/tests/snapshots/kirin_derive_ir__tests__dialect__dialect_derive_wrapper_struct_has_signature.snap @@ -1,6 +1,6 @@ --- source: crates/kirin-derive-ir/src/tests/dialect.rs -expression: code +expression: generate_dialect_code(input) --- #[automatically_derived] impl<'a> ::kirin::ir::HasArguments<'a> for WrapperOp { @@ -357,6 +357,16 @@ impl ::kirin::ir::HasSignature for WrapperOp { } } #[automatically_derived] +impl ::kirin::ir::HasCallableBody for WrapperOp +where + InnerOp: ::kirin::ir::HasCallableBody, +{ + fn callable_body(&self) -> ::core::option::Option<::kirin::ir::Body> { + let Self(field_0) = self; + ::callable_body(field_0) + } +} +#[automatically_derived] impl ::core::convert::From for WrapperOp { fn from(from: InnerOp) -> Self { WrapperOp(from) diff --git a/crates/kirin-derive-toolkit/src/ir/attrs.rs b/crates/kirin-derive-toolkit/src/ir/attrs.rs index eb44e0ff52..7b0849f633 100644 --- a/crates/kirin-derive-toolkit/src/ir/attrs.rs +++ b/crates/kirin-derive-toolkit/src/ir/attrs.rs @@ -68,6 +68,8 @@ pub struct StatementOptions { #[derive(Debug, Clone, FromField)] #[darling(attributes(kirin))] pub struct KirinFieldOptions { + #[darling(default)] + pub callable_body: bool, #[darling(default)] pub into: bool, pub default: Option, diff --git a/crates/kirin-derive-toolkit/src/ir/layout.rs b/crates/kirin-derive-toolkit/src/ir/layout.rs index b4f724a40c..3b216822cf 100644 --- a/crates/kirin-derive-toolkit/src/ir/layout.rs +++ b/crates/kirin-derive-toolkit/src/ir/layout.rs @@ -17,7 +17,7 @@ impl HasCratePath for () { /// Each associated type corresponds to a level in the IR hierarchy where /// a derive macro can inject extra parsed attributes. [`StandardLayout`] /// sets all extras to `()` — use it unless your derive needs custom -/// attributes like `#[callable]` or `#[format(...)]`. +/// attributes like `#[format(...)]`. /// /// # Custom Layout Example /// diff --git a/crates/kirin-derive-toolkit/src/ir/statement/definition.rs b/crates/kirin-derive-toolkit/src/ir/statement/definition.rs index 7060556a26..88a3065dd6 100644 --- a/crates/kirin-derive-toolkit/src/ir/statement/definition.rs +++ b/crates/kirin-derive-toolkit/src/ir/statement/definition.rs @@ -32,6 +32,8 @@ pub struct Statement { pub attrs: StatementOptions, /// Classified fields (arguments, results, values, etc.). pub fields: Vec>, + /// The explicitly designated callable body, if this is a direct definition. + pub callable_body: Option, /// Delegation target if this variant uses `#[wraps]`. pub wraps: Option, /// Layout-specific extra data computed per statement. @@ -55,6 +57,7 @@ impl Statement { name, attrs, fields: Vec::new(), + callable_body: None, wraps: None, extra, extra_attrs, @@ -122,13 +125,42 @@ impl Statement { fields: &syn::Fields, ir_type: &syn::Path, ) -> darling::Result { - let mut errors = darling::Error::accumulator(); let field_wraps = fields .iter() .map(|field| WrapperOptions::from_attrs(&field.attrs)) .collect::>>()?; - if wraps.is_some() || field_wraps.iter().any(Option::is_some) { + let is_wrapper = wraps.is_some() || field_wraps.iter().any(Option::is_some); + for (index, field) in fields.iter().enumerate() { + if !KirinFieldOptions::from_field(field)?.callable_body { + continue; + } + if is_wrapper { + return Err(darling::Error::custom( + "#[wraps] delegates callable-body discovery; mark the body on the wrapped definition", + ).with_span(field)); + } + if self.callable_body.is_some() { + return Err(darling::Error::custom( + "at most one #[kirin(callable_body)] field is allowed per definition", + ) + .with_span(field)); + } + if !["Block", "CFG", "DiGraph", "UnGraph"].iter().any(|kind| { + matches!( + Collection::from_type(&field.ty, kind), + Some(Collection::Single) + ) + }) { + return Err(darling::Error::custom( + "#[kirin(callable_body)] requires a single Block, CFG, DiGraph, or UnGraph field; Option and Vec are unsupported", + ).with_span(field)); + } + self.callable_body = Some(FieldIndex::new(field.ident.clone(), index)); + } + + let mut errors = darling::Error::accumulator(); + if is_wrapper { if fields.len() == 1 { let field = fields.iter().next().unwrap(); let options = merge_wrapper_options(wraps, field_wraps.into_iter().next().unwrap()) diff --git a/crates/kirin-derive-toolkit/src/lib.rs b/crates/kirin-derive-toolkit/src/lib.rs index b6faddd238..17a7316cfc 100644 --- a/crates/kirin-derive-toolkit/src/lib.rs +++ b/crates/kirin-derive-toolkit/src/lib.rs @@ -35,7 +35,8 @@ //! ## Layout Extensibility //! //! [`StandardLayout`] works for most derives. If your derive needs custom attributes -//! on statements or fields (e.g., `#[callable]`), define a custom [`Layout`] impl. +//! on statements or fields (e.g., parser formatting), define a custom [`Layout`] impl. +//! Structural `#[kirin(callable_body)]` is part of the shared IR metadata. //! See [`ir::Layout`] for details. //! //! [`Layout`]: ir::Layout diff --git a/crates/kirin-derive-toolkit/src/misc.rs b/crates/kirin-derive-toolkit/src/misc.rs index 2503bea5eb..9a8ab20a40 100644 --- a/crates/kirin-derive-toolkit/src/misc.rs +++ b/crates/kirin-derive-toolkit/src/misc.rs @@ -150,9 +150,7 @@ pub fn error_unknown_attribute(meta: &syn::meta::ParseNestedMeta) -> syn::Error meta.path.get_ident().unwrap() )) } else if meta.path.is_ident("callable") { - meta.error( - "the 'callable' attribute is not part of #[kirin(...)]; use #[callable] with #[derive(CallSemantics)]", - ) + meta.error("use #[kirin(callable_body)] on the definition's body field") } else if [ "constant", "pure", diff --git a/crates/kirin-function/src/function.rs b/crates/kirin-function/src/function.rs index 00521be92b..f86fcb2e2b 100644 --- a/crates/kirin-function/src/function.rs +++ b/crates/kirin-function/src/function.rs @@ -9,14 +9,9 @@ use kirin::prelude::*; #[kirin(builders, type = T)] #[chumsky(format = "fn {:name}{sig} {body}")] pub struct Function { + #[kirin(callable_body)] pub(crate) body: CFG, pub(crate) sig: Signature, #[kirin(default)] marker: std::marker::PhantomData, } - -impl HasCFGBody for Function { - fn cfg(&self) -> &CFG { - &self.body - } -} diff --git a/crates/kirin-function/src/interpreter.rs b/crates/kirin-function/src/interpreter.rs index ac9fcbd668..d35eee2a0d 100644 --- a/crates/kirin-function/src/interpreter.rs +++ b/crates/kirin-function/src/interpreter.rs @@ -1,8 +1,8 @@ -use kirin::prelude::{CompileTimeValue, HasBottom, HasCFGBody, Product, SSAValue}; +use kirin::prelude::{CompileTimeValue, HasBottom, Product, SSAValue}; use kirin_interpreter::dialect::{ - CallEffect, CallableBody, Callee, ClassicLiveness, ClassicLivenessInterp, DemandInterp, - DenseBackwardEffect, ForwardEval, FunctionEntry, Interpretable, InterpreterError, - SparseForwardEffect, SparseForwardInterp, StrongDemand, + CallEffect, Callee, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, + ForwardEval, Interpretable, InterpreterError, SparseForwardEffect, SparseForwardInterp, + StrongDemand, }; use crate::{ @@ -89,27 +89,9 @@ where } } -impl FunctionEntry for Function -where - T: CompileTimeValue, -{ - fn function_entry(&self) -> Option { - Some(CallableBody::new(*self.cfg())) - } -} - -impl FunctionEntry for Lambda -where - T: CompileTimeValue, -{ - fn function_entry(&self) -> Option { - Some(CallableBody::new(*self.cfg())) - } -} - /// Function definitions are inert at runtime: defining a function does not -/// execute its body. Bodies run when the function is invoked (via -/// [`FunctionEntry`]). +/// execute its body. Invocation discovers the body through +/// `kirin_ir::HasCallableBody`, then the engine initializes and runs it. impl Interpretable for Function where I: SparseForwardInterp, diff --git a/crates/kirin-function/src/lambda.rs b/crates/kirin-function/src/lambda.rs index 22d089e878..0d0fb49f25 100644 --- a/crates/kirin-function/src/lambda.rs +++ b/crates/kirin-function/src/lambda.rs @@ -36,14 +36,9 @@ use kirin::prelude::*; pub struct Lambda { name: Symbol, captures: Vec, + #[kirin(callable_body)] pub(crate) body: CFG, res: ResultValue, #[kirin(default)] marker: std::marker::PhantomData, } - -impl HasCFGBody for Lambda { - fn cfg(&self) -> &CFG { - &self.body - } -} diff --git a/crates/kirin-function/src/lib.rs b/crates/kirin-function/src/lib.rs index 8a41b1d11f..97d37a1e06 100644 --- a/crates/kirin-function/src/lib.rs +++ b/crates/kirin-function/src/lib.rs @@ -15,7 +15,7 @@ //! in how functions are *introduced* (inline `Lambda` vs top-level `Bind`). use kirin::prelude::*; -use kirin_interpreter::{FunctionEntry, Interpretable}; +use kirin_interpreter::Interpretable; pub mod bind; pub mod call; @@ -34,27 +34,20 @@ pub mod interpreter; #[cfg(test)] mod tests; -#[derive( - Debug, Clone, PartialEq, Eq, Hash, Dialect, FunctionEntry, HasParser, PrettyPrint, Interpretable, -)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint, Interpretable)] #[wraps] #[kirin(builders, type = T)] pub enum Lexical { - #[callable] Function(Function), Call(Call), - #[callable] Lambda(Lambda), Return(Return), } -#[derive( - Debug, Clone, PartialEq, Eq, Hash, Dialect, FunctionEntry, HasParser, PrettyPrint, Interpretable, -)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint, Interpretable)] #[wraps] #[kirin(builders, type = T)] pub enum Lifted { - #[callable] Function(Function), Call(Call), Bind(Bind), diff --git a/crates/kirin-interpreter/src/core/dispatch.rs b/crates/kirin-interpreter/src/core/dispatch.rs index a45fd879e0..7018ed811a 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, StageInfo, StageMeta, Statement}; -use crate::{CallableBody, Interp, InterpreterError}; +use crate::Interp; /// Statement semantics. The single trait dialect authors implement. /// @@ -15,28 +15,12 @@ pub trait Interpretable: Dialect { fn interpret(&self, interp: &mut I) -> Result; } -/// Function-entry semantics for callable statements. -/// -/// Implemented by statements that define function bodies (e.g. -/// `kirin_function::Function`); describes the [`CallableBody`] an engine enters -/// when the function is invoked. Derived on language enums with -/// `#[derive(FunctionEntry)]` where `#[callable]` marks the variants that wrap -/// callable statements. -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. /// /// Mirrors `ParseDispatch` from the parser: multi-stage pipelines add /// `#[derive(InterpDispatch)]` to their stage enum; single-language pipelines /// (`Pipeline>`) get the blanket impl below. Engines route every -/// statement execution and function entry through this trait; compiler +/// statement execution through this trait; compiler /// authors derive it and never call it. /// /// Keyed on the engine `I` alone: the semantic key dispatched is always @@ -49,14 +33,12 @@ pub trait InterpDispatch: StageMeta { statement: Statement, interp: &mut I, ) -> Result; - - fn dispatch_function_entry(&self, definition: Statement) -> Result; } impl InterpDispatch for StageInfo where I: Interp, - L: Dialect + Interpretable::Semantics> + FunctionEntry, + L: Dialect + Interpretable::Semantics>, { fn dispatch_statement( &self, @@ -66,11 +48,4 @@ where let definition = statement.definition(self).clone(); definition.interpret(interp) } - - fn dispatch_function_entry(&self, definition: Statement) -> Result { - let callable = definition.definition(self).clone(); - 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 59a0548fb4..b89bba8051 100644 --- a/crates/kirin-interpreter/src/core/effect.rs +++ b/crates/kirin-interpreter/src/core/effect.rs @@ -1,47 +1,7 @@ use kirin_ir::{ - Block, CFG, CompileStage, DiGraph, Function, Product, SSAValue, SpecializedFunction, - StagedFunction, Symbol, UnGraph, + Block, CompileStage, Function, Product, SSAValue, SpecializedFunction, StagedFunction, Symbol, }; -/// A traversal descriptor: which body was the engine handed? -/// -/// Interpreter vocabulary, not an IR concept — dialect ops keep their precise -/// field types (`Block`, `CFG`, `DiGraph`, `UnGraph`); a `Body` appears only at -/// the moment a body is handed to the interpreter (callable entry, -/// body-containment queries, analysis scopes). Bodies carry no semantics of their own: the -/// statement that owns a body defines what entering and exiting it means. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Body { - Block(Block), - CFG(CFG), - DiGraph(DiGraph), - UnGraph(UnGraph), -} - -impl From for Body { - fn from(block: Block) -> Self { - Self::Block(block) - } -} - -impl From for Body { - fn from(cfg: CFG) -> Self { - Self::CFG(cfg) - } -} - -impl From for Body { - fn from(graph: DiGraph) -> Self { - Self::DiGraph(graph) - } -} - -impl From for Body { - fn from(graph: UnGraph) -> Self { - Self::UnGraph(graph) - } -} - /// The closed forward control algebra a statement produces. /// /// Atomic statements read operands, write results, and return [`SparseForwardEffect::Next`]. @@ -104,8 +64,8 @@ impl Edge { pub struct CallEffect { /// What to call. pub callee: Callee, - /// Optional explicit target stage (e.g. staged calls); defaults to the - /// caller's stage. + /// Optional explicit lookup stage (e.g. staged calls); defaults to the + /// caller's stage. The linker may select a different target stage. pub stage: Option, /// Argument values. pub args: Product, @@ -131,29 +91,3 @@ impl From for Callee { Self::Named(symbol) } } - -/// 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 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. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct CallableBody { - pub body: Body, -} - -impl CallableBody { - pub fn new(body: impl Into) -> Self { - Self { body: body.into() } - } -} diff --git a/crates/kirin-interpreter/src/core/error.rs b/crates/kirin-interpreter/src/core/error.rs index f57bfd85cf..833f9e81e5 100644 --- a/crates/kirin-interpreter/src/core/error.rs +++ b/crates/kirin-interpreter/src/core/error.rs @@ -1,6 +1,8 @@ use std::convert::Infallible; -use kirin_ir::{Block, CompileStage, Function, SSAValue, StagedFunction, Statement, Symbol}; +use kirin_ir::{ + Block, CompileStage, Function, SSAValue, SpecializedFunction, StagedFunction, Statement, Symbol, +}; use thiserror::Error; use crate::EnvIndex; @@ -21,6 +23,8 @@ pub enum InterpreterError { MissingStageInfo(CompileStage), #[error("missing block info for block {0:?}")] MissingBlock(Block), + #[error("missing statement info for statement {0:?}")] + MissingStatement(Statement), #[error("missing SSA value {0:?}")] MissingValue(SSAValue), #[error("missing function {0:?}")] @@ -32,6 +36,8 @@ pub enum InterpreterError { }, #[error("staged function {0:?} has no live specialization")] MissingSpecialization(StagedFunction), + #[error("missing specialization record for {0:?}")] + MissingSpecializationRecord(SpecializedFunction), #[error("staged function {function:?} has {count} live specializations")] AmbiguousSpecialization { function: StagedFunction, diff --git a/crates/kirin-interpreter/src/core/frame.rs b/crates/kirin-interpreter/src/core/frame.rs index 1293382c6c..3f82a38b47 100644 --- a/crates/kirin-interpreter/src/core/frame.rs +++ b/crates/kirin-interpreter/src/core/frame.rs @@ -63,9 +63,7 @@ use std::hash::Hash; use kirin_ir::{Block, CFG, CompileStage, Product, SSAValue, Statement}; -use crate::{ - Body, CallEffect, CallableBody, Callee, Env, EnvIndex, FunctionTarget, Interp, InterpreterError, -}; +use crate::{Body, CallEffect, Callee, Env, EnvIndex, Interp, InterpreterError, ResolvedCallable}; /// Structural effect a [`Frame`] returns to the engine driver loop. /// @@ -311,13 +309,13 @@ pub trait DiGraphQueries: Interp { } /// Engine services used by [`CallFrame`](crate::CallFrame): activation storage, -/// linking, and callable-entry dispatch. +/// linking, and structural callable-body discovery. /// /// **[`CallFrame`](crate::CallFrame) still owns the calling convention** — the /// order of operations, which completions are legal, and freeing the activation /// exactly once. This trait only supplies the primitives it calls. /// -/// Kept whole on purpose: the standard `CallFrame` consumes all four together, +/// Kept whole on purpose: the standard `CallFrame` consumes these services together, /// and their pairing is a safety property — an `alloc_env` without its matching /// `free_env` is a leak, a second `free_env` a double free. Splitting them into /// separate capabilities would let an engine offer half a call convention. @@ -334,9 +332,9 @@ pub trait CallServices: Env { /// the selected target stage. fn resolve_callable( &self, - stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, - ) -> Result<(FunctionTarget, CallableBody), Self::Error>; + ) -> Result; } /// An interpreter engine capable of running the complete standard **concrete** diff --git a/crates/kirin-interpreter/src/core/linker.rs b/crates/kirin-interpreter/src/core/linker.rs index dae3f4ae16..586d89600a 100644 --- a/crates/kirin-interpreter/src/core/linker.rs +++ b/crates/kirin-interpreter/src/core/linker.rs @@ -1,65 +1,73 @@ -use kirin_ir::{CompileStage, Pipeline, SpecializedFunction, StageMeta, Statement}; +use kirin_ir::{Body, CompileStage, Pipeline, SpecializedFunction, StageMeta}; use super::query; -use crate::{CallableBody, Callee, Interp, InterpDispatch, InterpreterError, StageQuery}; +use crate::{Callee, InterpreterError, StageQuery}; -/// A fully resolved call target: the stage to execute in, the specialization, -/// and its callable definition statement. +/// The stage and specialization selected by a linker. +/// +/// The specialization record owns its definition statement. Body discovery +/// reads that record in `stage`, so a linker cannot supply a conflicting +/// definition alongside the specialization's identity. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct FunctionTarget { +pub struct LinkTarget { pub stage: CompileStage, - pub function: SpecializedFunction, - pub definition: Statement, + pub specialization: SpecializedFunction, +} + +/// A linked target together with its discovered Kirin implementation. +/// +/// Target identity is used for analysis contexts; the body selects the IR to +/// traverse. Engine-specific boundary initialization follows discovery. +#[derive(Clone, Copy, Debug)] +pub struct ResolvedCallable { + pub target: LinkTarget, + pub body: Body, } /// The calling-convention component of an engine. /// -/// A linker resolves a [`Callee`] to a [`FunctionTarget`]. It is a value +/// A linker resolves a [`Callee`] to a [`LinkTarget`]. It is a value /// passed to engines (`.with_linker(...)`), so compiler authors swap calling /// conventions without touching engine internals — the same linker drives /// concrete execution and abstract analyses, which is what makes /// cross-language analysis a one-line choice. pub trait Linker { + /// Resolve relative to `lookup_stage`; the selected target may live in + /// another stage under a cross-stage policy. fn resolve( &self, pipeline: &Pipeline, - caller_stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, - ) -> Result; + ) -> Result; } /// Run the framework's common callable-root protocol. /// -/// Linking selects a concrete target; callable-entry dispatch then discovers +/// Linking selects a concrete target; an IR query 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 resolve_callable( pipeline: &Pipeline, linker: &Lk, - caller_stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, -) -> Result<(FunctionTarget, CallableBody), I::Error> +) -> Result where - I: Interp, - S: StageMeta + InterpDispatch, + S: StageQuery, 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)) + let target = linker.resolve(pipeline, lookup_stage, callee)?; + let body = query::callable_body(pipeline, target.stage, target.specialization)?; + Ok(ResolvedCallable { target, body }) } -/// Resolve calls within the caller's stage only (the default). +/// Resolve calls within the lookup stage only (the default). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct SameStageLinker; -/// Resolve calls across stages: prefer a live specialization at the caller's +/// Resolve calls across stages: prefer a live specialization at the lookup /// stage, otherwise fall back to any stage that has one. This is the standard /// linker for pipelines where functions are declared at several stages but /// lowered bodies live at only one. @@ -68,12 +76,12 @@ pub struct CrossStageLinker; fn callee_function( pipeline: &Pipeline, - caller_stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, ) -> Result { match *callee { Callee::Named(symbol) => { - let name = query::resolve_symbol_name(pipeline, caller_stage, symbol)? + let name = query::resolve_symbol_name(pipeline, lookup_stage, symbol)? .ok_or(InterpreterError::MissingCallSymbol(symbol))?; let function = pipeline .lookup_function_by_name(&name) @@ -89,7 +97,7 @@ fn target_at_stage( pipeline: &Pipeline, stage: CompileStage, callee: &Callee, -) -> Result { +) -> Result { let specialized = match *callee { Callee::Named(symbol) => return Err(InterpreterError::MissingCallSymbol(symbol)), Callee::Function(function) => { @@ -103,11 +111,12 @@ fn target_at_stage( Callee::Staged(staged) => query::unique_specialization(pipeline, stage, staged)?, Callee::Specialized(specialized) => specialized, }; - let definition = query::function_definition(pipeline, stage, specialized)?; - Ok(FunctionTarget { + // In particular, an already specialized handle must exist here before + // CrossStageLinker accepts this candidate stage. + query::validate_specialization(pipeline, stage, specialized)?; + Ok(LinkTarget { stage, - function: specialized, - definition, + specialization: specialized, }) } @@ -115,11 +124,11 @@ impl Linker for SameStageLinker { fn resolve( &self, pipeline: &Pipeline, - caller_stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, - ) -> Result { - let callee = callee_function(pipeline, caller_stage, callee)?; - target_at_stage(pipeline, caller_stage, &callee) + ) -> Result { + let callee = callee_function(pipeline, lookup_stage, callee)?; + target_at_stage(pipeline, lookup_stage, &callee) } } @@ -127,16 +136,16 @@ impl Linker for CrossStageLinker { fn resolve( &self, pipeline: &Pipeline, - caller_stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, - ) -> Result { - let callee = callee_function(pipeline, caller_stage, callee)?; - let home = target_at_stage(pipeline, caller_stage, &callee); + ) -> Result { + let callee = callee_function(pipeline, lookup_stage, callee)?; + let home = target_at_stage(pipeline, lookup_stage, &callee); if home.is_ok() { return home; } for stage in pipeline.stages().iter().filter_map(StageMeta::stage_id) { - if stage == caller_stage { + if stage == lookup_stage { continue; } if let Ok(target) = target_at_stage(pipeline, stage, &callee) { diff --git a/crates/kirin-interpreter/src/core/mod.rs b/crates/kirin-interpreter/src/core/mod.rs index b05588c4cf..ee89185ec1 100644 --- a/crates/kirin-interpreter/src/core/mod.rs +++ b/crates/kirin-interpreter/src/core/mod.rs @@ -14,8 +14,8 @@ pub(crate) mod linker; pub(crate) mod query; pub(crate) mod value; -pub use dispatch::{FunctionEntry, InterpDispatch, Interpretable}; -pub use effect::{Body, CallEffect, CallableBody, Callee, Edge, SparseForwardEffect}; +pub use dispatch::{InterpDispatch, Interpretable}; +pub use effect::{CallEffect, Callee, Edge, SparseForwardEffect}; pub use env::{EnvIndex, EnvStackStore, Store}; pub use error::InterpreterError; pub use frame::{ @@ -23,6 +23,6 @@ pub use frame::{ ForwardFrameEngine, Frame, FrameEffect, FrameEngine, StatementDispatch, drive_frames, }; pub use interp::{AbstractInterpreter, Env, Interp, InterpLocation, SparseForwardInterp}; -pub use linker::{CrossStageLinker, FunctionTarget, Linker, SameStageLinker}; +pub use linker::{CrossStageLinker, LinkTarget, Linker, ResolvedCallable, SameStageLinker}; pub use query::{GraphWalkPlan, StageQuery, TerminatorArgs}; pub use value::{BranchCondition, HasProductValue, expect_single}; diff --git a/crates/kirin-interpreter/src/core/query.rs b/crates/kirin-interpreter/src/core/query.rs index dc70acf2b6..2515f9ce26 100644 --- a/crates/kirin-interpreter/src/core/query.rs +++ b/crates/kirin-interpreter/src/core/query.rs @@ -10,8 +10,8 @@ use crate::Body; use crate::InterpreterError; use kirin_ir::{ Block, BlockParent, CFG, CompileStage, Dialect, GetInfo, HasArguments, HasBlocks, HasCFG, - HasDigraphs, HasStageInfo, HasUngraphs, Pipeline, PortParent, SSAKind, SSAValue, - SpecializedFunction, StageAction, StageInfo, StageMeta, StagedFunction, Statement, + HasCallableBody, HasDigraphs, HasStageInfo, HasUngraphs, Pipeline, PortParent, SSAKind, + SSAValue, SpecializedFunction, StageAction, StageInfo, StageMeta, StagedFunction, Statement, SupportsStageDispatch, Symbol, UniqueLiveSpecializationError, }; use smallvec::{SmallVec, smallvec}; @@ -262,15 +262,15 @@ where } } -/// Definition statement of a specialized function. -pub struct FunctionDefinition(pub SpecializedFunction); +/// Check that a specialization belongs to a candidate stage before linking it. +pub struct ValidateSpecialization(pub SpecializedFunction); -impl StageAction for FunctionDefinition +impl StageAction for ValidateSpecialization where S: StageMeta + HasStageInfo, L: Dialect, { - type Output = Result; + type Output = (); type Error = InterpreterError; fn run( @@ -278,13 +278,36 @@ where _stage: CompileStage, info: &StageInfo, ) -> Result { - Ok(self + self.0 + .get_info(info) + .map(|_| ()) + .ok_or(InterpreterError::MissingSpecializationRecord(self.0)) + } +} + +/// Discover a specialization's callable body using only IR structure. +pub struct CallableBodyQuery(pub SpecializedFunction); + +impl StageAction for CallableBodyQuery +where + S: StageMeta + HasStageInfo, + L: Dialect + HasCallableBody, +{ + type Output = Body; + type Error = InterpreterError; + + fn run(&mut self, _stage: CompileStage, info: &StageInfo) -> Result { + let specialization = self .0 .get_info(info) - .map(|info| *info.definition()) - .ok_or(InterpreterError::Custom( - "specialized function has no definition", - ))) + .ok_or(InterpreterError::MissingSpecializationRecord(self.0))?; + let definition = *specialization.definition(); + definition + .get_info(info) + .ok_or(InterpreterError::MissingStatement(definition))? + .definition() + .callable_body() + .ok_or(InterpreterError::NotCallable(definition)) } } @@ -537,10 +560,12 @@ where /// Bound bundle for stage enums usable by interpreter engines. /// -/// 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 +/// Satisfied automatically by stages whose dialects implement `HasCallableBody`, +/// including derived dialects with no callables. Manual dialects provide that +/// structural capability when used here; no interpreter or semantic rules +/// are required for these queries. +pub trait StageQuery: + StageMeta + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch, InterpreterError> @@ -551,7 +576,8 @@ pub trait StageQuery: StageMeta UniqueSpecialization, Result, InterpreterError, - > + SupportsStageDispatch, InterpreterError> + > + SupportsStageDispatch + + SupportsStageDispatch + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch @@ -578,11 +604,9 @@ impl StageQuery for S where UniqueSpecialization, Result, InterpreterError, - > + SupportsStageDispatch< - FunctionDefinition, - Result, - InterpreterError, - > + SupportsStageDispatch, InterpreterError> + > + SupportsStageDispatch + + SupportsStageDispatch + + SupportsStageDispatch, InterpreterError> + SupportsStageDispatch + SupportsStageDispatch + SupportsStageDispatch< @@ -670,12 +694,20 @@ pub(crate) fn unique_specialization( dispatch(pipeline, stage, UniqueSpecialization(staged))? } -pub(crate) fn function_definition( +pub(crate) fn validate_specialization( pipeline: &Pipeline, stage: CompileStage, specialized: SpecializedFunction, -) -> Result { - dispatch(pipeline, stage, FunctionDefinition(specialized))? +) -> Result<(), InterpreterError> { + dispatch(pipeline, stage, ValidateSpecialization(specialized)) +} + +pub(crate) fn callable_body( + pipeline: &Pipeline, + stage: CompileStage, + specialized: SpecializedFunction, +) -> Result { + dispatch(pipeline, stage, CallableBodyQuery(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 66d5a66181..cd1af225b6 100644 --- a/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs +++ b/crates/kirin-interpreter/src/engines/concrete/frames/call_frame.rs @@ -2,6 +2,7 @@ use kirin_ir::{CompileStage, Product, SSAValue}; use crate::{ Body, CallEffect, CallServices, Callee, EnvIndex, Frame, FrameEffect, InterpreterError, + ResolvedCallable, }; use super::{BodyFrameEntry, CallBodyTraversal, Completion, DefaultCallBodyTraversal}; @@ -13,7 +14,7 @@ use super::{BodyFrameEntry, CallBodyTraversal, Completion, DefaultCallBodyTraver /// walkers deliberately don't: /// /// 1. resolve the callee and discover its value-independent body through the -/// common callable-root protocol (`Linker` then `FunctionEntry` at the +/// common callable-root protocol (`Linker` then IR body discovery at the /// target stage); /// 2. allocate the callee activation; /// 3. retain the concrete argument product for the selected body walker; @@ -53,7 +54,7 @@ pub struct CallRequest { enum CallState { /// Not yet dispatched: resolve the callee and enter its body. Pending { - resolve_stage: CompileStage, + lookup_stage: CompileStage, callee: Callee, args: Product, dest: CallDest, @@ -84,7 +85,7 @@ impl CallRequest { pub fn pending(scope_stage: CompileStage, caller_env: EnvIndex, call: CallEffect) -> Self { Self { state: CallState::Pending { - resolve_stage: call.stage.unwrap_or(scope_stage), + lookup_stage: call.stage.unwrap_or(scope_stage), callee: call.callee, args: call.args, dest: CallDest::Caller { @@ -100,7 +101,7 @@ impl CallRequest { pub fn root(stage: CompileStage, callee: Callee, args: Product) -> Self { Self { state: CallState::Pending { - resolve_stage: stage, + lookup_stage: stage, callee, args, dest: CallDest::Root, @@ -130,12 +131,13 @@ where fn step_into(self, interp: &mut I) -> Result, F>, E> { match self.state { CallState::Pending { - resolve_stage, + lookup_stage, callee, args, dest, } => { - let (target, entry) = interp.resolve_callable(resolve_stage, &callee)?; + let ResolvedCallable { target, body } = + interp.resolve_callable(lookup_stage, &callee)?; let index = interp.alloc_env(); // The closed `Body` enum is the framework's supported body // vocabulary, so this match is intentionally exhaustive; @@ -144,7 +146,7 @@ where // exhaustive; only *which frame* each arm builds is // configurable, via the `T` traversal. Activation ownership and // completion handling deliberately stay out of the traversal. - let child = match entry.body { + let child = match body { Body::CFG(cfg) => T::from_cfg(BodyFrameEntry { stage: target.stage, index, diff --git a/crates/kirin-interpreter/src/engines/concrete/interp.rs b/crates/kirin-interpreter/src/engines/concrete/interp.rs index e6925ad085..eb76a995de 100644 --- a/crates/kirin-interpreter/src/engines/concrete/interp.rs +++ b/crates/kirin-interpreter/src/engines/concrete/interp.rs @@ -6,10 +6,10 @@ use kirin_ir::{ use crate::core::{linker::resolve_callable, query}; use crate::{ - BlockQueries, CFGQueries, CallServices, CallableBody, Callee, Completion, DiGraphQueries, Env, - EnvIndex, EnvStackStore, ForwardEval, Frame, FunctionTarget, Interp, InterpDispatch, - InterpLocation, InterpreterError, Linker, SameStageLinker, SparseForwardEffect, StageQuery, - StatementDispatch, Store, drive_frames, + BlockQueries, CFGQueries, CallServices, Callee, Completion, DiGraphQueries, Env, EnvIndex, + EnvStackStore, ForwardEval, Frame, Interp, InterpDispatch, InterpLocation, InterpreterError, + Linker, ResolvedCallable, SameStageLinker, SparseForwardEffect, StageQuery, StatementDispatch, + Store, drive_frames, }; use super::frames::{CallRequest, FrameStackItem}; @@ -155,10 +155,10 @@ where fn resolve_callable( &self, - stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, - ) -> Result<(FunctionTarget, CallableBody), E> { - resolve_callable::(self.pipeline, &self.linker, stage, callee) + ) -> Result { + resolve_callable(self.pipeline, &self.linker, lookup_stage, callee).map_err(E::from) } } diff --git a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs index f28b2a7b47..465e4ad04e 100644 --- a/crates/kirin-interpreter/src/engines/dense_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/dense_backward/interp.rs @@ -53,7 +53,6 @@ use kirin_ir::{ }; use super::frames::DenseBlockFrame; -use crate::Body; use crate::core::{linker::resolve_callable as resolve_callable_root, query}; use crate::engines::sparse_backward::BodyScope; use crate::{ @@ -63,6 +62,7 @@ use crate::{ StandardFixpointInterpreter, Summary, SummaryDependency, SummaryDependencyIndex, SummaryEffect, TerminatorArgs, }; +use crate::{Body, ResolvedCallable}; // =========================================================================== // Effect + point-state contract + dialect-facing trait @@ -760,14 +760,8 @@ 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::< - DenseBackwardTransfer<'ir, S, V, E, F, Sem>, - _, - _, - >( - self.driver.inner().pipeline(), &self.linker, stage, &callee - )?; - let body = entry.body; + let ResolvedCallable { target, body } = + resolve_callable_root(self.driver.inner().pipeline(), &self.linker, stage, &callee)?; let scope = (target.stage, body); let blocks = self.direct_body_blocks(target.stage, body)?; let owners: Vec> = blocks diff --git a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs index 9675eda605..25ef78dc1a 100644 --- a/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_backward/interp.rs @@ -53,6 +53,7 @@ use kirin_ir::{ SSAKind, SSAValue, StageMeta, Statement, }; +use crate::ResolvedCallable; use crate::core::{linker::resolve_callable as resolve_callable_root, query}; use crate::{ AbstractInterpreter, Body, Callee, EnvIndex, FixpointProfile, Frame, FrameEffect, Interp, @@ -646,13 +647,8 @@ 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 body = entry.body; + let ResolvedCallable { target, body } = + resolve_callable_root(self.driver.inner().pipeline(), &self.linker, stage, &callee)?; if matches!(body, Body::DiGraph(_) | Body::UnGraph(_)) { return Err(E::from(InterpreterError::NoDefaultWalker(body))); } diff --git a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs index 9a34305826..71dd682d40 100644 --- a/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs +++ b/crates/kirin-interpreter/src/engines/sparse_forward/interp.rs @@ -43,10 +43,10 @@ use kirin_ir::{ 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, - Env, EnvIndex, EnvStackStore, FixpointProfile, ForwardDataflowFrameEngine, ForwardEval, - ForwardSummaryDeps, Frame, FunctionTarget, Interp, InterpDispatch, InterpLocation, - InterpreterError, Linker, OwnerSemantics, SameStageLinker, SparseForwardEffect, + BlockQueries, Body, CFGQueries, CallEffect, CallServices, Callee, DiGraphQueries, Env, + EnvIndex, EnvStackStore, FixpointProfile, ForwardDataflowFrameEngine, ForwardEval, + ForwardSummaryDeps, Frame, Interp, InterpDispatch, InterpLocation, InterpreterError, + LinkTarget, Linker, OwnerSemantics, ResolvedCallable, SameStageLinker, SparseForwardEffect, SparseForwardSemantic, StageQuery, StandardAbstractFrame, StandardFixpointInterpreter, StatementDispatch, Store, Summary, SummaryDependency, SummaryDependencyIndex, SummaryEffect, }; @@ -60,7 +60,7 @@ use crate::{ pub trait CallContext { type Key: Clone + Eq + Hash; - fn key(&mut self, target: &FunctionTarget, args: &Product) -> Self::Key; + fn key(&mut self, target: &LinkTarget, args: &Product) -> Self::Key; } /// Explore/join strategy: combines an `incoming` abstract state into the @@ -90,8 +90,8 @@ impl Default for ContextInsensitive { impl CallContext for ContextInsensitive { type Key = (CompileStage, SpecializedFunction); - fn key(&mut self, target: &FunctionTarget, _args: &Product) -> Self::Key { - (target.stage, target.function) + fn key(&mut self, target: &LinkTarget, _args: &Product) -> Self::Key { + (target.stage, target.specialization) } } @@ -512,7 +512,7 @@ where } /// Key a resolved call target through the analysis. - fn key(&mut self, target: &FunctionTarget, args: &Product) ->

>::Key { + fn key(&mut self, target: &LinkTarget, args: &Product) ->

>::Key { self.analysis.key(target, args) } @@ -628,10 +628,10 @@ where fn resolve_callable( &self, - stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, - ) -> Result<(FunctionTarget, CallableBody), E> { - resolve_callable_root::(self.pipeline, &self.linker, stage, callee) + ) -> Result { + resolve_callable_root(self.pipeline, &self.linker, lookup_stage, callee).map_err(E::from) } } @@ -751,10 +751,10 @@ where fn resolve_callable( &self, - stage: CompileStage, + lookup_stage: CompileStage, callee: &Callee, - ) -> Result<(FunctionTarget, CallableBody), E> { - self.inner().resolve_callable(stage, callee) + ) -> Result { + self.inner().resolve_callable(lookup_stage, callee) } } @@ -881,14 +881,15 @@ where args, results, } = call; - let resolve_stage = call_stage.unwrap_or(stage); - let (target, entry) = self.inner().resolve_callable(resolve_stage, &callee)?; + let lookup_stage = call_stage.unwrap_or(stage); + let ResolvedCallable { target, body } = + self.inner().resolve_callable(lookup_stage, &callee)?; let key = self.inner_mut().key(&target, &args); self.apply_update(ForwardUpdate::FunctionEntry { key: key.clone(), stage: target.stage, - body: entry.body, + body, args, })?; @@ -1567,14 +1568,15 @@ where callee: Callee, args: impl IntoIterator, ) -> Result, E> { - let (target, entry) = self.driver.inner().resolve_callable(stage, &callee)?; + let ResolvedCallable { target, body } = + 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, - body: entry.body, + body, args, })?; diff --git a/crates/kirin-interpreter/src/lib.rs b/crates/kirin-interpreter/src/lib.rs index 570175971d..1430cca835 100644 --- a/crates/kirin-interpreter/src/lib.rs +++ b/crates/kirin-interpreter/src/lib.rs @@ -37,7 +37,7 @@ //! # Two-persona contract //! //! - **Dialect authors** implement [`Interpretable`](Interpretable) -//! per semantic key (and [`FunctionEntry`] for callable statements). A rule +//! per semantic key; callable bodies are declared through `kirin_ir::HasCallableBody`. A rule //! receives the engine `interp` directly. Shape-generic mechanics live on //! the engine traits (read/write on [`SparseForwardInterp`]; //! fact/raise-fact on [`SparseBackwardInterp`]; opaque point-state access on @@ -69,12 +69,13 @@ mod semantics; pub use self::core::{ AbstractInterpreter, Env, GraphWalkPlan, Interp, InterpLocation, SparseForwardInterp, }; -pub use self::core::{Body, CallEffect, CallableBody, Callee, Edge, SparseForwardEffect}; pub use self::core::{BranchCondition, HasProductValue, expect_single}; -pub use self::core::{CrossStageLinker, FunctionTarget, Linker, SameStageLinker}; +pub use self::core::{CallEffect, Callee, Edge, SparseForwardEffect}; +pub use self::core::{CrossStageLinker, LinkTarget, Linker, ResolvedCallable, SameStageLinker}; pub use self::core::{EnvIndex, EnvStackStore, Store}; -pub use self::core::{FunctionEntry, InterpDispatch, Interpretable}; +pub use self::core::{InterpDispatch, Interpretable}; pub use self::core::{InterpreterError, StageQuery, TerminatorArgs}; +pub use kirin_ir::Body; // The shared, direction-neutral frame protocol: `Frame`/`FrameEffect`/ // `drive_frames` (the frame-stack driver loop) anchored on `FrameEngine`, the // minimal engine contract. On top of it, the forward engine capabilities a frame @@ -144,7 +145,7 @@ pub use fixpoint::{ }; #[cfg(feature = "derive")] -pub use kirin_derive_interpreter::{Frame, FunctionEntry, InterpDispatch, Interpretable}; +pub use kirin_derive_interpreter::{Frame, InterpDispatch, Interpretable}; /// Everything a dialect author needs to implement statement semantics — /// forward evaluation (`Interpretable`), backward demand @@ -153,10 +154,10 @@ pub use kirin_derive_interpreter::{Frame, FunctionEntry, InterpDispatch, Interpr /// (`impl SemanticKey for MyKey { type Shape = ...; }`). pub mod dialect { pub use crate::{ - AnalysisShape, Body, BranchCondition, CallEffect, CallableBody, Callee, ClassicLiveness, + AnalysisShape, Body, BranchCondition, CallEffect, Callee, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, DenseBackwardInterp, - DenseBackwardShape, DenseForwardShape, Edge, ForwardEval, FunctionEntry, HasProductValue, - Interp, Interpretable, InterpreterError, PointFacts, SemanticKey, SparseBackwardEffect, + DenseBackwardShape, DenseForwardShape, Edge, ForwardEval, HasProductValue, Interp, + Interpretable, InterpreterError, PointFacts, SemanticKey, SparseBackwardEffect, SparseBackwardInterp, SparseBackwardShape, SparseForwardEffect, SparseForwardInterp, SparseForwardShape, StrongDemand, SuccessorEdge, }; @@ -172,9 +173,9 @@ pub mod engine { DefaultCallBodyTraversal, DenseBackwardCompletion, DenseBackwardFrameEngine, DenseBackwardInterp, DenseBackwardInterpreter, DenseBackwardState, DenseBlockFrame, DiGraphFrame, DiGraphQueries, Env, ForwardDataflowFrameEngine, ForwardFrameEngine, Frame, - FrameEffect, FrameEngine, FunctionTarget, Interp, InterpDispatch, InterpreterError, Linker, - SameStageLinker, SparseBackwardInterp, SparseBackwardInterpreter, SparseForwardInterp, - SparseForwardInterpreter, StandardAbstractFrame, StatementDispatch, WideningStrategy, - drive_frames, expect_single, + FrameEffect, FrameEngine, Interp, InterpDispatch, InterpreterError, LinkTarget, Linker, + ResolvedCallable, SameStageLinker, SparseBackwardInterp, SparseBackwardInterpreter, + SparseForwardInterp, SparseForwardInterpreter, StandardAbstractFrame, StatementDispatch, + WideningStrategy, drive_frames, expect_single, }; } diff --git a/crates/kirin-ir/src/body.rs b/crates/kirin-ir/src/body.rs new file mode 100644 index 0000000000..ddda079e41 --- /dev/null +++ b/crates/kirin-ir/src/body.rs @@ -0,0 +1,54 @@ +use crate::{Block, CFG, DiGraph, UnGraph}; + +/// A handle to one of Kirin's computation representations. +/// +/// The owning operation determines the body's meaning. This sum records its +/// representation without choosing traversal, binding arguments, or requiring +/// an interpreter. Handles are read in the stage that owns them. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Body { + Block(Block), + CFG(CFG), + DiGraph(DiGraph), + UnGraph(UnGraph), +} + +impl From for Body { + fn from(block: Block) -> Self { + Self::Block(block) + } +} + +impl From for Body { + fn from(cfg: CFG) -> Self { + Self::CFG(cfg) + } +} + +impl From for Body { + fn from(graph: DiGraph) -> Self { + Self::DiGraph(graph) + } +} + +impl From for Body { + fn from(graph: UnGraph) -> Self { + Self::UnGraph(graph) + } +} + +/// Discover the Kirin body explicitly designated as a definition's call implementation. +/// +/// `#[derive(Dialect)]` generates this capability: a direct definition marks +/// one `Block`, `CFG`, `DiGraph`, or `UnGraph` field with +/// `#[kirin(callable_body)]`; an unmarked definition returns `None`. +/// `#[wraps]` definitions delegate to their wrapped operation automatically. +/// Merely owning a body does not imply callability. +/// +/// This capability is independent of [`crate::HasSignature`]: a lambda can +/// expose a body without a stored signature, and an external declaration can +/// carry a signature without a Kirin implementation. Discovery neither binds +/// forward arguments nor seeds a backward analysis. +pub trait HasCallableBody { + fn callable_body(&self) -> Option; +} diff --git a/crates/kirin-ir/src/language.rs b/crates/kirin-ir/src/language.rs index 50fd1b6a24..16f8587123 100644 --- a/crates/kirin-ir/src/language.rs +++ b/crates/kirin-ir/src/language.rs @@ -72,20 +72,6 @@ pub trait HasUngraphsMut<'a> { fn ungraphs_mut(&'a mut self) -> Self::IterMut; } -/// 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., `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 { - fn cfg(&self) -> &crate::CFG; - - fn entry_block(&self, stage: &crate::StageInfo) -> Option { - self.cfg().blocks(stage).next() - } -} - pub trait IsTerminator { fn is_terminator(&self) -> bool; } diff --git a/crates/kirin-ir/src/lib.rs b/crates/kirin-ir/src/lib.rs index 7c08936857..63e922f94f 100644 --- a/crates/kirin-ir/src/lib.rs +++ b/crates/kirin-ir/src/lib.rs @@ -1,4 +1,5 @@ mod arena; +mod body; mod builder; mod comptime; mod detach; @@ -16,6 +17,7 @@ mod stage; pub mod query; pub use arena::{Arena, DenseHint, GetInfo, Id, Identifier, Item, SparseHint}; +pub use body::{Body, HasCallableBody}; pub use builder::error::{ PipelineError, PipelineStagedError, SpecializeError, StagedFunctionConflictKind, StagedFunctionError, @@ -25,7 +27,7 @@ pub use comptime::{CompileTimeValue, Placeholder, Typeof}; pub use detach::Detach; pub use intern::InternTable; pub use language::{ - Dialect, HasArguments, HasArgumentsMut, HasBlocks, HasBlocksMut, HasCFG, HasCFGBody, HasCFGMut, + Dialect, HasArguments, HasArgumentsMut, HasBlocks, HasBlocksMut, HasCFG, HasCFGMut, HasDigraphs, HasDigraphsMut, HasResults, HasResultsMut, HasSuccessors, HasSuccessorsMut, HasUngraphs, HasUngraphsMut, IsConstant, IsEdge, IsPure, IsSpeculatable, IsTerminator, }; @@ -54,9 +56,9 @@ pub use stage::{ /// Re-exports of the most commonly used types for dialect authors. pub mod prelude { pub use crate::{ - Block, BuilderStageInfo, CFG, CompileStage, Dialect, Function, GetInfo, HasCFGBody, - HasSignature, HasStageInfo, Pipeline, ResultValue, SSAValue, Signature, SignatureSemantics, - StageInfo, StageMeta, Statement, + Block, Body, BuilderStageInfo, CFG, CompileStage, Dialect, Function, GetInfo, + HasCallableBody, HasSignature, HasStageInfo, Pipeline, ResultValue, SSAValue, Signature, + SignatureSemantics, StageInfo, StageMeta, Statement, }; pub use crate::{ CompileTimeValue, HasProduct, Placeholder, Product, Project, ProjectError, TryProject, diff --git a/crates/kirin-ir/tests/builder_block.rs b/crates/kirin-ir/tests/builder_block.rs index f1c06bb991..b4d7442a11 100644 --- a/crates/kirin-ir/tests/builder_block.rs +++ b/crates/kirin-ir/tests/builder_block.rs @@ -6,16 +6,6 @@ mod common; use common::{BuilderDialect, TestType, new_stage}; use kirin_ir::*; -struct CFGBodyOp { - cfg: CFG, -} - -impl HasCFGBody for CFGBodyOp { - fn cfg(&self) -> &CFG { - &self.cfg - } -} - // --- BlockBuilder tests --- #[test] @@ -410,18 +400,6 @@ fn statement_builder_rejects_block_owned_by_cfg() { .new(); } -#[test] -fn has_cfg_body_entry_block_returns_first_block() { - let mut stage = new_stage(); - let b0 = stage.block().new(); - let b1 = stage.block().new(); - let cfg = stage.cfg().add_block(b0).add_block(b1).new(); - let op = CFGBodyOp { cfg }; - - let stage = stage.finalize().unwrap(); - assert_eq!(op.entry_block(&stage), Some(b0)); -} - #[test] #[should_panic(expected = "already added to the cfg")] fn cfg_builder_panics_on_duplicate_block() { diff --git a/crates/kirin-ir/tests/callable_body.rs b/crates/kirin-ir/tests/callable_body.rs new file mode 100644 index 0000000000..275a5074db --- /dev/null +++ b/crates/kirin-ir/tests/callable_body.rs @@ -0,0 +1,58 @@ +//! Focused structural projections using kirin-ir alone. +#![cfg(feature = "derive")] + +mod common; + +use common::{TestType, new_stage}; +use kirin_ir::*; + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = TestType, crate = kirin_ir)] +struct CfgCallable { + auxiliary: Block, + #[kirin(callable_body)] + implementation: CFG, +} + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = TestType, crate = kirin_ir)] +struct UnmarkedBody { + body: CFG, + sig: Signature, +} + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = TestType, crate = kirin_ir)] +struct LinearCallable(#[kirin(callable_body)] Block); + +#[test] +fn marked_body_is_selected_over_an_auxiliary_body() { + let mut stage = new_stage(); + let cfg = stage.cfg().new(); + let callable = CfgCallable { + auxiliary: stage.block().new(), + implementation: cfg, + }; + assert_eq!(callable.callable_body(), Some(Body::CFG(cfg))); +} + +#[test] +fn unmarked_body_and_signature_do_not_imply_callability() { + let mut stage = new_stage(); + let operation = UnmarkedBody { + body: stage.cfg().new(), + sig: Signature::new(vec![], TestType::I32, ()), + }; + assert_eq!(operation.callable_body(), None); + assert!(operation.signature().is_some()); +} + +#[test] +fn marked_tuple_struct_field_is_projected() { + let mut stage = new_stage(); + let block = stage.block().new(); + assert_eq!( + LinearCallable(block).callable_body(), + Some(Body::Block(block)) + ); +} diff --git a/crates/kirin-liveness/tests/callable_root.rs b/crates/kirin-liveness/tests/callable_root.rs index 8d42398ff8..58aaac609a 100644 --- a/crates/kirin-liveness/tests/callable_root.rs +++ b/crates/kirin-liveness/tests/callable_root.rs @@ -3,7 +3,7 @@ use kirin::prelude::*; use kirin_interpreter::{ - Body, Callee, CrossStageLinker, FunctionTarget, InterpreterError, Linker, SameStageLinker, + Body, Callee, CrossStageLinker, InterpreterError, LinkTarget, Linker, SameStageLinker, }; use kirin_liveness::{Demand, DenseLiveness}; use kirin_test_languages::GraphFunctionLanguage; @@ -125,9 +125,9 @@ impl Linker for RejectingLinker { fn resolve( &self, _pipeline: &Pipeline, - _caller_stage: CompileStage, + _lookup_stage: CompileStage, _callee: &Callee, - ) -> Result { + ) -> Result { Err(InterpreterError::Custom("rejecting linker reached")) } } @@ -200,28 +200,29 @@ fn cross_stage_linking_discovers_the_body_at_the_target_stage() { } #[derive(Clone, Copy)] -struct FixedTargetLinker(FunctionTarget); +struct FixedTargetLinker(LinkTarget); impl Linker for FixedTargetLinker { fn resolve( &self, _pipeline: &Pipeline, - _caller_stage: CompileStage, + _lookup_stage: CompileStage, _callee: &Callee, - ) -> Result { + ) -> Result { Ok(self.0) } } #[test] fn a_resolved_non_callable_definition_is_reported_by_shared_body_discovery() { - let pipeline = parse(BLOCK_PROGRAM); + let mut pipeline = parse(BLOCK_PROGRAM); let (stage, callee) = function_callee(&pipeline, "linear"); - let mut target = SameStageLinker + let 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) { + let definition = *target.specialization.get_info(info).unwrap().definition(); + let body = match definition.definition(info) { GraphFunctionLanguage::LinearFunction { body, .. } => *body, other => panic!("expected linear function, got {other:?}"), }; @@ -229,21 +230,117 @@ fn a_resolved_non_callable_definition_is_reported_by_shared_body_discovery() { .statements(info) .next() .expect("body contains an arithmetic statement"); - target.definition = non_callable; + // The specialization record is the sole source of its definition. + *target + .specialization + .get_info_mut(pipeline.stage_mut(stage).unwrap()) + .unwrap() + .definition_mut() = 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 specialized_handles_are_validated_before_accepting_a_candidate_stage() { + let pipeline = parse(CROSS_STAGE_PROGRAM); + let source = pipeline.stage_by_name("source").unwrap(); + let lowered = pipeline.stage_by_name("lowered").unwrap(); + let function = pipeline.lookup_function_by_name("linear").unwrap(); + let target = SameStageLinker + .resolve(&pipeline, lowered, &Callee::Function(function)) + .unwrap(); + let callee = Callee::Specialized(target.specialization); + + assert!(SameStageLinker.resolve(&pipeline, source, &callee).is_err()); + assert_eq!( + CrossStageLinker + .resolve(&pipeline, source, &callee) + .unwrap(), + target + ); + let demand = Demand::::new(&pipeline) + .with_linker(CrossStageLinker) + .analyze(source, callee) + .unwrap(); + let dense = DenseLiveness::::new(&pipeline) + .with_linker(CrossStageLinker) + .analyze(source, callee) + .unwrap(); + assert_eq!(demand.0, lowered); + assert_eq!(dense, demand); +} + +#[test] +fn malformed_custom_targets_return_errors_without_panicking() { + let mut pipeline = parse(CROSS_STAGE_PROGRAM); + let source = pipeline.stage_by_name("source").unwrap(); + let lowered = pipeline.stage_by_name("lowered").unwrap(); + let function = pipeline.lookup_function_by_name("linear").unwrap(); + let callee = Callee::Function(function); + let target = SameStageLinker + .resolve(&pipeline, lowered, &callee) + .unwrap(); + + let mut foreign_stages = kirin::ir::Arena::::default(); + for _ in pipeline.stages() { + let _ = foreign_stages.alloc(()); + } + let missing_stage = foreign_stages.next_id(); + for (stage, expected) in [ + (missing_stage, InterpreterError::MissingStage(missing_stage)), + ( + source, + InterpreterError::MissingSpecializationRecord(target.specialization), + ), + ] { + let linker = FixedTargetLinker(LinkTarget { stage, ..target }); + assert_eq!( + Demand::::new(&pipeline) + .with_linker(linker) + .analyze(source, callee) + .unwrap_err(), + expected + ); + assert_eq!( + DenseLiveness::::new(&pipeline) + .with_linker(linker) + .analyze(source, callee) + .unwrap_err(), + expected + ); + } + + let missing_definition = pipeline.stage(lowered).unwrap().statement_arena().next_id(); + *target + .specialization + .get_info_mut(pipeline.stage_mut(lowered).unwrap()) + .unwrap() + .definition_mut() = missing_definition; + let expected = InterpreterError::MissingStatement(missing_definition); + assert_eq!( + Demand::::new(&pipeline) + .with_linker(FixedTargetLinker(target)) + .analyze(source, callee) + .unwrap_err(), + expected + ); + assert_eq!( + DenseLiveness::::new(&pipeline) + .with_linker(FixedTargetLinker(target)) + .analyze(source, callee) + .unwrap_err(), + expected + ); +} + #[test] fn unsupported_graph_roots_fail_instead_of_producing_empty_facts() { let pipeline = parse(GRAPH_PROGRAM); diff --git a/crates/kirin-test-languages/src/arith_function_language.rs b/crates/kirin-test-languages/src/arith_function_language.rs index 6e2cb8e1db..6f9f07e574 100644 --- a/crates/kirin-test-languages/src/arith_function_language.rs +++ b/crates/kirin-test-languages/src/arith_function_language.rs @@ -17,6 +17,7 @@ pub enum ArithFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { + #[kirin(callable_body)] body: CFG, sig: Signature, }, @@ -34,8 +35,8 @@ pub enum ArithFunctionLanguage { #[cfg(feature = "interpreter")] mod interpreter { use kirin_interpreter::dialect::{ - CallableBody, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, - FunctionEntry, Interpretable, StrongDemand, + ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, Interpretable, + StrongDemand, }; use kirin_ir::HasBottom; @@ -73,13 +74,4 @@ mod interpreter { } } } - - impl FunctionEntry for ArithFunctionLanguage { - fn function_entry(&self) -> Option { - match self { - ArithFunctionLanguage::Function { body, .. } => Some(CallableBody::new(*body)), - _ => None, - } - } - } } diff --git a/crates/kirin-test-languages/src/bitwise_function_language.rs b/crates/kirin-test-languages/src/bitwise_function_language.rs index d2b612742c..ba82564234 100644 --- a/crates/kirin-test-languages/src/bitwise_function_language.rs +++ b/crates/kirin-test-languages/src/bitwise_function_language.rs @@ -18,6 +18,7 @@ pub enum BitwiseFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { + #[kirin(callable_body)] body: CFG, sig: Signature, }, diff --git a/crates/kirin-test-languages/src/callable_language.rs b/crates/kirin-test-languages/src/callable_language.rs index 3cde8c1b32..45fa6afcf7 100644 --- a/crates/kirin-test-languages/src/callable_language.rs +++ b/crates/kirin-test-languages/src/callable_language.rs @@ -16,6 +16,7 @@ pub enum CallableLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { + #[kirin(callable_body)] body: CFG, sig: Signature, }, diff --git a/crates/kirin-test-languages/src/graph_function_language.rs b/crates/kirin-test-languages/src/graph_function_language.rs index 9ac73f1442..ef60d27821 100644 --- a/crates/kirin-test-languages/src/graph_function_language.rs +++ b/crates/kirin-test-languages/src/graph_function_language.rs @@ -25,6 +25,7 @@ pub enum GraphFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { + #[kirin(callable_body)] body: CFG, sig: Signature, }, @@ -34,6 +35,7 @@ pub enum GraphFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] GraphFunction { + #[kirin(callable_body)] body: DiGraph, sig: Signature, }, @@ -43,6 +45,7 @@ pub enum GraphFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] LinearFunction { + #[kirin(callable_body)] body: Block, sig: Signature, }, @@ -55,6 +58,7 @@ pub enum GraphFunctionLanguage { chumsky(format = "fn {:name}{sig} {body}") )] UnGraphFunction { + #[kirin(callable_body)] body: UnGraph, sig: Signature, }, @@ -88,9 +92,8 @@ mod interpreter { use kirin_arith::{ArithValue, CheckedDiv, CheckedRem, interpreter::DivisionByZero}; use kirin_interpreter::BranchCondition; use kirin_interpreter::{ - CallableBody, ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, - DiGraphFrame, ForwardEval, FunctionEntry, Interpretable, SparseForwardEffect, - SparseForwardInterp, StrongDemand, + ClassicLiveness, ClassicLivenessInterp, DemandInterp, DenseBackwardEffect, DiGraphFrame, + ForwardEval, Interpretable, SparseForwardEffect, SparseForwardInterp, StrongDemand, }; use kirin_ir::{HasBottom, Product, SSAValue}; @@ -183,20 +186,4 @@ mod interpreter { } } } - - 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, .. } => { - Some(CallableBody::new(*body)) - } - GraphFunctionLanguage::UnGraphFunction { body, .. } => { - Some(CallableBody::new(*body)) - } - _ => None, - } - } - } } diff --git a/crates/kirin-test-languages/src/namespaced_language.rs b/crates/kirin-test-languages/src/namespaced_language.rs index e313a5dd0a..fbc8eaeacf 100644 --- a/crates/kirin-test-languages/src/namespaced_language.rs +++ b/crates/kirin-test-languages/src/namespaced_language.rs @@ -17,6 +17,7 @@ pub enum NamespacedLanguage { chumsky(format = "fn {:name}{sig} {body}") )] Function { + #[kirin(callable_body)] body: CFG, sig: Signature, }, diff --git a/docs/design/interpreter/index.md b/docs/design/interpreter/index.md index c2a0d00e07..815e6e48cf 100644 --- a/docs/design/interpreter/index.md +++ b/docs/design/interpreter/index.md @@ -243,32 +243,53 @@ 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` — value-independent callable statements +### `HasCallableBody` — IR-level callable-body discovery ```rust -pub trait FunctionEntry: Dialect { - fn function_entry(&self) -> Option; +// kirin-ir: independent of an interpreter, signature type, and value domain. +pub trait HasCallableBody { + fn callable_body(&self) -> Option; } ``` -Statements that define function bodies (e.g. `kirin_function::Function`) -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. +`Body` is the IR-owned sum of `Block`, `CFG`, `DiGraph`, and `UnGraph` handles. +Dialect definitions retain their precise field types. `#[derive(Dialect)]` +always generates `HasCallableBody`: a direct definition marks zero or one field +with `#[kirin(callable_body)]`; zero markers returns `None`. Multiple marked +fields, unsupported types, and marked `Option`/`Vec` fields are derive errors. +Every `#[wraps]` variant delegates automatically, including through nested +language enums. Merely owning a body does not make an operation callable. + +```rust +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = T)] +struct Function { + #[kirin(callable_body)] + body: CFG, + sig: Signature, +} +``` + +Follow `HasSignature` as a derive-code-generation precedent, not as a shared +semantic abstraction. A `Lambda` exposes a body without a stored signature; +an external declaration can have a signature without a Kirin body. Body +queries need no dialect type parameter. Manual `Dialect` implementations add +`HasCallableBody` when used in this query path; it is not a `Dialect` +supertrait. + +Concrete execution and forward analysis retain their own argument products +and bind them after discovery. Backward analyses manufacture no placeholder +product. The common query reports `NotCallable` with the actual definition +statement when the operation has no marked body. Every engine root follows the same validated prefix: ```text -caller stage + Callee +lookup stage + Callee -> Linker::resolve - -> FunctionTarget - -> FunctionEntry dispatch at FunctionTarget.stage - -> CallableBody + -> LinkTarget { stage, specialization } + -> target-stage IR query: specialization -> definition -> callable_body() + -> ResolvedCallable { target, body } -> engine-specific boundary initialization ``` @@ -284,8 +305,8 @@ Everything is exported from `kirin_interpreter::engine`. Compiler authors usually write zero framework-trait impls: 1. **Language enums** — the same `#[wraps]` enums used for parsing/printing, - with `Interpretable` (and `FunctionEntry` + `#[callable]`) added to the - derive list. + with `Interpretable` added to the derive list. Their existing `Dialect` + derive delegates callable-body discovery to the marked leaf definitions. 2. **Stage enum** — add `#[derive(InterpDispatch)]` next to `StageMeta` and `ParseDispatch`. Single-language pipelines (`Pipeline>`) get a blanket impl. @@ -308,20 +329,51 @@ let value = expect_single(analysis.analyze_by_name("source", "abs", [Const(7)])? ```rust pub trait Linker { - fn resolve(&self, pipeline: &Pipeline, caller_stage: CompileStage, callee: &Callee) - -> Result; + fn resolve(&self, pipeline: &Pipeline, lookup_stage: CompileStage, callee: &Callee) + -> Result; +} + +pub struct LinkTarget { + pub stage: CompileStage, + pub specialization: SpecializedFunction, +} + +pub struct ResolvedCallable { + pub target: LinkTarget, + pub body: Body, } ``` A linker resolves `Callee::{Named, Function, Staged, Specialized}` to a -`(stage, specialization, definition)` target. It is a *field of the engine*, never +`(stage, specialization)` 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. -- `SameStageLinker` (default): resolve within the caller's stage. -- `CrossStageLinker`: prefer a live specialization at the caller's stage, +- `SameStageLinker` (default): resolve within the lookup stage. +- `CrossStageLinker`: prefer a live specialization at the lookup stage, otherwise any stage that has one. +The input and selected stage have the same `CompileStage` type but distinct +roles. If `linear` is declared in `source` and implemented only in `lowered`, +cross-stage resolution can accept `lookup_stage = source` and return +`target.stage = lowered`. Fetching that target stage retrieves its IR storage; +it does not resolve the callee again or perform lowering. + +`SpecializedFunctionInfo` authoritatively owns the definition statement. +`LinkTarget` carries no copied definition, so custom linkers cannot pair a +specialization with a contradictory definition. The shared, engine-independent +query reads the specialization record, statement, and marked body in the +selected stage. Missing records produce errors instead of panics. Default +linkers also check specialization presence before accepting a candidate stage, +including for an already specialized callee; removing the copied definition +must not remove this validation. + +`ResolvedCallable` retains the target for analysis context/summary identity +and the body for traversal. It does not decide whether an engine supports the +representation: for example, an undirected body can be discovered successfully +and subsequently rejected with `NoDefaultWalker` by the engine's traversal +policy. Callable discovery also does not bind arguments or seed backward exits. + Because the linker is shared by all engines, cross-language *analysis* is the same one-line choice as cross-language *execution*: the abstract engine calls the linker at `SparseForwardEffect::Call`, and the analysis lattice flows through @@ -437,14 +489,16 @@ specialization. Two mechanisms keep engines generic over stage enums: -- `InterpDispatch` (derived) — monomorphic dispatch of statement - interpretation and function entry to each stage's language, mirroring - `ParseDispatch`. The engine builds its context and dispatch forwards it to the - matching `Interpretable`/`FunctionEntry` rule. +- `InterpDispatch` (derived) — monomorphic dispatch of statement + interpretation to each stage's language, mirroring `ParseDispatch`. The + engine sets its current location and dispatch forwards it to the matching + `Interpretable` rule. - `StageQuery` — a bound bundle over kirin-ir's `StageDispatch`/`StageAction` machinery for language-independent IR facts (block parameters, statement - order, CFG entry, specialization lookup, symbol resolution). Satisfied - automatically by any stage enum; used by engines and linkers internally. + order, CFG entry, specialization validation, callable-body discovery, symbol + resolution). Derived dialects, including those with no callables, supply + `HasCallableBody` without requiring an interpreter or semantic rules for the + query. Used by engines and linkers internally. ## Custom traversal and policies @@ -858,3 +912,6 @@ and terminating on unknown inputs (both fold to `Top`). Runnable as `CallContext` impl, no engine change. - First-class function values (`Lambda`/`Bind` as values, `Callee` from an SSA value) are not yet supported by either engine. +- Custom-linker targets are checked during body discovery: an absent record + returns `MissingSpecializationRecord`, and an absent definition statement + returns `MissingStatement`. diff --git a/example/toy-lang/src/language.rs b/example/toy-lang/src/language.rs index d4c406d9a0..2009e1dff4 100644 --- a/example/toy-lang/src/language.rs +++ b/example/toy-lang/src/language.rs @@ -5,17 +5,14 @@ use kirin_cf::ControlFlow; use kirin_cmp::Cmp; use kirin_constant::Constant; use kirin_function::{Call, Function, Lexical, Lifted, Return}; -use kirin_interpreter::{FunctionEntry, Interpretable}; +use kirin_interpreter::Interpretable; use kirin_scf::StructuredControlFlow; /// Source-stage language: structured control flow + lexical lambdas. -#[derive( - Debug, Clone, PartialEq, Eq, Hash, Dialect, FunctionEntry, HasParser, PrettyPrint, Interpretable, -)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint, Interpretable)] #[kirin(builders, type = ArithType)] pub enum HighLevel { #[wraps] - #[callable] Lexical(Lexical), #[wraps] Structured(StructuredControlFlow), @@ -48,13 +45,10 @@ impl From> for HighLevel { } /// Lowered-stage language: unstructured CF + lifted functions. -#[derive( - Debug, Clone, PartialEq, Eq, Hash, Dialect, FunctionEntry, HasParser, PrettyPrint, Interpretable, -)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint, Interpretable)] #[kirin(builders, type = ArithType)] pub enum LowLevel { #[wraps] - #[callable] Lifted(Lifted), #[wraps] Constant(Constant), diff --git a/tests/body_kinds.rs b/tests/body_kinds.rs index 56ce967ebf..9d748e6fdf 100644 --- a/tests/body_kinds.rs +++ b/tests/body_kinds.rs @@ -39,7 +39,7 @@ use kirin_interpreter::{ AbstractBlockFrame, AbstractCallFrame, AbstractCompletion, AbstractDiGraphFrame, BlockFrame, Body, CFGFrame, CallContext, CallFrame, CallRequest, Callee, Completion, ConcreteInterpreter, ConcreteInterpreterCore, ContextInsensitive, DefaultCallBodyTraversal, DiGraphFrame, Frame, - FrameEffect, FrameEngine, FunctionEntry, Interpretable, InterpreterError, SameStageLinker, + FrameEffect, FrameEngine, Interpretable, InterpreterError, SameStageLinker, SparseForwardInterpreter, expect_single, }; use kirin_scf::{ScfForFrame, ScfIfFrame, StructuredControlFlow}; @@ -50,7 +50,7 @@ use kirin_test_languages::GraphFunctionLanguage; #[derive(Debug)] enum TestError { Core(InterpreterError), - ArithConversion(ArithConversionError), + ArithConversion((ArithConversionError)), DivisionByZero, } @@ -273,13 +273,10 @@ fn digraph_runs_in_topological_order() { /// Inline language wrapping functions (CFG bodies), scf, and arithmetic. /// Specific to this integration suite; shared test dialects live in /// `kirin-test-languages`. -#[derive( - Debug, Clone, PartialEq, Eq, Hash, Dialect, FunctionEntry, HasParser, PrettyPrint, Interpretable, -)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Dialect, HasParser, PrettyPrint, Interpretable)] #[kirin(builders, type = ArithType)] enum ScfLanguage { #[wraps] - #[callable] Lexical(Lexical), #[wraps] Structured(StructuredControlFlow), diff --git a/tests/compile-fail/callable_body_duplicate.rs b/tests/compile-fail/callable_body_duplicate.rs new file mode 100644 index 0000000000..b86b5acc64 --- /dev/null +++ b/tests/compile-fail/callable_body_duplicate.rs @@ -0,0 +1,12 @@ +use kirin::ir::{Block, CFG, Dialect}; + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = kirin_test_languages::SimpleType)] +struct Invalid { + #[kirin(callable_body)] + first: CFG, + #[kirin(callable_body)] + second: Block, +} + +fn main() {} diff --git a/tests/compile-fail/callable_body_duplicate.stderr b/tests/compile-fail/callable_body_duplicate.stderr new file mode 100644 index 0000000000..feeaa03e01 --- /dev/null +++ b/tests/compile-fail/callable_body_duplicate.stderr @@ -0,0 +1,5 @@ +error: at most one #[kirin(callable_body)] field is allowed per definition + --> tests/compile-fail/callable_body_duplicate.rs:8:5 + | +8 | #[kirin(callable_body)] + | ^ diff --git a/tests/compile-fail/callable_body_invalid_fields.rs b/tests/compile-fail/callable_body_invalid_fields.rs new file mode 100644 index 0000000000..567501a249 --- /dev/null +++ b/tests/compile-fail/callable_body_invalid_fields.rs @@ -0,0 +1,24 @@ +use kirin::ir::{Block, CFG, Dialect}; + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = kirin_test_languages::SimpleType)] +struct Optional { + #[kirin(callable_body)] + body: Option, +} + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = kirin_test_languages::SimpleType)] +struct Multiple { + #[kirin(callable_body)] + body: Vec, +} + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = kirin_test_languages::SimpleType)] +struct Unsupported { + #[kirin(callable_body)] + value: i64, +} + +fn main() {} diff --git a/tests/compile-fail/callable_body_invalid_fields.stderr b/tests/compile-fail/callable_body_invalid_fields.stderr new file mode 100644 index 0000000000..95f74b6857 --- /dev/null +++ b/tests/compile-fail/callable_body_invalid_fields.stderr @@ -0,0 +1,17 @@ +error: #[kirin(callable_body)] requires a single Block, CFG, DiGraph, or UnGraph field; Option and Vec are unsupported + --> tests/compile-fail/callable_body_invalid_fields.rs:6:5 + | +6 | #[kirin(callable_body)] + | ^ + +error: #[kirin(callable_body)] requires a single Block, CFG, DiGraph, or UnGraph field; Option and Vec are unsupported + --> tests/compile-fail/callable_body_invalid_fields.rs:13:5 + | +13 | #[kirin(callable_body)] + | ^ + +error: #[kirin(callable_body)] requires a single Block, CFG, DiGraph, or UnGraph field; Option and Vec are unsupported + --> tests/compile-fail/callable_body_invalid_fields.rs:20:5 + | +20 | #[kirin(callable_body)] + | ^ diff --git a/tests/compile-fail/callable_body_on_wrapper.rs b/tests/compile-fail/callable_body_on_wrapper.rs new file mode 100644 index 0000000000..f52979d4fc --- /dev/null +++ b/tests/compile-fail/callable_body_on_wrapper.rs @@ -0,0 +1,8 @@ +use kirin::ir::{Dialect, CFG}; + +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(type = kirin_test_languages::SimpleType)] +#[wraps] +struct Invalid(#[kirin(callable_body)] CFG); + +fn main() {} diff --git a/tests/compile-fail/callable_body_on_wrapper.stderr b/tests/compile-fail/callable_body_on_wrapper.stderr new file mode 100644 index 0000000000..cd54189883 --- /dev/null +++ b/tests/compile-fail/callable_body_on_wrapper.stderr @@ -0,0 +1,5 @@ +error: #[wraps] delegates callable-body discovery; mark the body on the wrapped definition + --> tests/compile-fail/callable_body_on_wrapper.rs:6:16 + | +6 | struct Invalid(#[kirin(callable_body)] CFG); + | ^ diff --git a/tests/frame_engine_capabilities.rs b/tests/frame_engine_capabilities.rs index ecedd9be0d..e79ec594e6 100644 --- a/tests/frame_engine_capabilities.rs +++ b/tests/frame_engine_capabilities.rs @@ -37,10 +37,10 @@ use std::collections::HashMap; use kirin_interpreter::{ AbstractBlockFrame, AbstractCallFrame, AbstractDiGraphFrame, BlockFrame, BlockQueries, - CFGFrame, CFGQueries, CallEffect, CallFrame, CallRequest, CallServices, CallableBody, Callee, + CFGFrame, CFGQueries, CallEffect, CallFrame, CallRequest, CallServices, Callee, DefaultCallBodyTraversal, DiGraphFrame, DiGraphQueries, Env, EnvIndex, - ForwardDataflowFrameEngine, ForwardEval, ForwardFrameEngine, Frame, FunctionTarget, Interp, - InterpreterError, SparseForwardEffect, StatementDispatch, + ForwardDataflowFrameEngine, ForwardEval, ForwardFrameEngine, Frame, Interp, InterpreterError, + ResolvedCallable, SparseForwardEffect, StatementDispatch, }; use kirin_ir::{Block, CompileStage, Product, SSAValue, Statement}; @@ -264,7 +264,7 @@ impl CallServices for CallOnlyEngine { &self, _stage: CompileStage, _callee: &Callee, - ) -> Result<(FunctionTarget, CallableBody), InterpreterError> { + ) -> Result { unimplemented!("type-level mock") } } diff --git a/tests/roundtrip/function.rs b/tests/roundtrip/function.rs index 1901170a3f..6d51aa387c 100644 --- a/tests/roundtrip/function.rs +++ b/tests/roundtrip/function.rs @@ -139,10 +139,14 @@ enum LambdaLanguage { #[test] fn test_lambda_parse_roundtrip() { - roundtrip::assert_statement_roundtrip::( - "%f = lambda @closure captures(%x, %y) { } -> i32", - &[("x", SimpleType::I32), ("y", SimpleType::I32)], - ); + let input = "%f = lambda @closure captures(%x, %y) { } -> i32"; + let operands = &[("x", SimpleType::I32), ("y", SimpleType::I32)]; + roundtrip::assert_statement_roundtrip::(input, operands); + + let (stage, statement) = roundtrip::emit_statement::(input, operands); + let lambda = statement.definition(&stage); + assert!(matches!(lambda.callable_body(), Some(Body::CFG(_)))); + assert!(lambda.signature().is_none()); } #[test]