Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/target
__pycache__/
*.py[cod]
.DS_Store
skills-lock.json
docs/superpowers/
refactor-workspace/
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ For user-defined dialects not in this table, ask the user for domain context dur

- **SCF is the example**: `scf.if` → `kirin_scf::ScfIfFrame` (concrete) / `AbstractScfIfFrame` (abstract); `scf.for` → `ScfForFrame` / `AbstractScfForFrame`. Each is selected per engine through a dialect dispatch trait (`ScfIfDispatch`/`ScfForDispatch`) and returned as `SparseForwardEffect::Push`. The if frame owns picking the arm (concrete) or exploring both arms + joining (abstract); the for frame owns the loop-carried join/widen fixpoint. A language that uses SCF includes the corresponding SCF continuations in its private stack-item composition and owns the narrow `From<Scf*Frame>` 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<S>` resolves `Callee` to a `(stage, specialization, body)` target and is passed to engines by value (`.with_linker(..)`). `SameStageLinker` is the default; `CrossStageLinker` routes calls to whichever stage has a live specialization, which is all that cross-language execution *and* cross-language analysis require. Policy must be a component (field), never a trait impl on an engine type.
- **Calling conventions are linkers**: `Linker<S>` resolves `Callee` to a `(stage, specialization, definition)` target and is passed to engines by value (`.with_linker(..)`). `SameStageLinker` is the default; `CrossStageLinker` routes calls to whichever stage has a live specialization, which is all that cross-language execution *and* cross-language analysis require. Policy must be a component (field), never a trait impl on an engine type.

- **Engines run frames; traversal lives in frames**: all frame-using engines share one driver loop, `drive_frames` (`core/frame.rs`), over the direction-neutral `Frame<I: FrameEngine, F = Self>` 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<Member>` 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`).

Expand Down
6 changes: 4 additions & 2 deletions crates/kirin-chumsky/src/function_text/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub enum FunctionParseErrorKind {
UnknownStage,
InconsistentFunctionName,
MissingStageDeclaration,
BodyParseFailed,
DefinitionParseFailed,
EmitFailed,
}

Expand All @@ -24,7 +24,9 @@ impl Display for FunctionParseErrorKind {
FunctionParseErrorKind::MissingStageDeclaration => {
write!(f, "missing stage declaration")
}
FunctionParseErrorKind::BodyParseFailed => write!(f, "function body parse failed"),
FunctionParseErrorKind::DefinitionParseFailed => {
write!(f, "function definition parse failed")
}
FunctionParseErrorKind::EmitFailed => write!(f, "IR emission failed"),
}
}
Expand Down
34 changes: 17 additions & 17 deletions crates/kirin-chumsky/src/function_text/parse_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
//! - collect `(stage, function) -> staged_function` mappings;
//! - record offsets of `specialize` declarations for pass 2.
//!
//! 2. **Pass 2 (specialize bodies)**
//! 2. **Pass 2 (specialization definitions)**
//! - re-parse only the previously recorded `specialize` declarations;
//! - resolve the target staged function from the pass-1 lookup;
//! - emit specialization bodies into the resolved stage dialect.
//! - emit specialization definitions into the resolved stage dialect.
//!
//! This separation guarantees that specialization emission sees a complete
//! staged-function header set, which keeps behavior deterministic even when
Expand All @@ -27,7 +27,7 @@
//! ## Why stage dispatch is central
//!
//! A pipeline can contain different dialects per stage (for example stage `A`
//! with `FunctionBody`, stage `B` with `LowerBody`). The parser does not guess
//! with `FunctionDefinition`, stage `B` with `LowerDefinition`). The parser does not guess
//! which dialect to use from text alone. Instead it:
//!
//! - resolves/creates the stage symbol first (`@A`, `@B`, ...);
Expand All @@ -39,7 +39,7 @@
//!
//! ## Illustrative examples
//!
//! Same-stage header + body:
//! Same-stage header + definition:
//!
//! ```text
//! stage @A fn @foo(()) -> ();
Expand Down Expand Up @@ -275,7 +275,7 @@ where

let Declaration::Specialize {
stage: _stage_sym,
body_span,
definition_span,
span,
} = declaration
else {
Expand All @@ -286,7 +286,7 @@ where
));
};

let body_text = &ctx.src[body_span.start..body_span.end];
let definition_text = &ctx.src[definition_span.start..definition_span.end];

// Use the function name from parse_declaration_head (always available),
// not from the chumsky Declaration (empty for dialect-controlled format).
Expand All @@ -300,7 +300,7 @@ where
stage_id,
&function_name,
ctx.function_symbol,
body_text,
definition_text,
span,
&mut *ctx.function_lookup,
&mut *ctx.staged_lookup,
Expand Down Expand Up @@ -607,7 +607,7 @@ fn apply_specialize_declaration<L>(
stage_id: CompileStage,
function_name: &SymbolName<'_>,
function_symbol: GlobalSymbol,
body_text: &str,
definition_text: &str,
span: SimpleSpan,
function_lookup: &mut FxHashMap<String, Function>,
staged_lookup: &mut FxHashMap<StagedKey, StagedFunction>,
Expand All @@ -617,14 +617,14 @@ where
L: Dialect + ParseEmit<L> + kirin_ir::HasSignature<L>,
L::Type: kirin_ir::Placeholder,
{
// Parse and emit the body first — we need it to extract signature if needed
let body_statement = stage
// Parse and emit the definition first — we need it to extract the signature.
let definition = stage
.with_builder(|builder| {
let mut emit_ctx = EmitContext::new(builder);
L::parse_and_emit(body_text, &mut emit_ctx).map_err(|err| {
L::parse_and_emit(definition_text, &mut emit_ctx).map_err(|err| {
let (kind, message) = match &err {
crate::ChumskyError::Parse(errs) => (
FunctionParseErrorKind::BodyParseFailed,
FunctionParseErrorKind::DefinitionParseFailed,
errs.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
Expand All @@ -645,11 +645,11 @@ where
)
})?;

// Get signature from HasSignature on the body statement.
// Get the signature from HasSignature on the definition statement.
// The Signature field is populated by the statement parser from format string elements.
let def = body_statement.expect_info(stage).definition();
let signature = def.signature().unwrap_or_else(|| {
// Fallback: create a placeholder signature if the body type
let definition_value = definition.expect_info(stage).definition();
let signature = definition_value.signature().unwrap_or_else(|| {
// Fallback: create a placeholder signature if the definition type
// doesn't carry one (e.g., no Signature field).
kirin_ir::Signature::placeholder()
});
Expand All @@ -673,7 +673,7 @@ where
.specialize()
.staged_func(staged_function)
.signature(signature.clone())
.body(body_statement)
.definition(definition)
.new()
.map_err(|err| {
FunctionParseError::new(
Expand Down
18 changes: 9 additions & 9 deletions crates/kirin-chumsky/src/function_text/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ pub(super) enum Declaration<'src, T> {
Stage(Header<'src, T>),
Specialize {
stage: SymbolName<'src>,
/// Span of the body portion (from keyword through closing `}`).
body_span: SimpleSpan,
/// Span of the definition (from keyword through closing `}`).
definition_span: SimpleSpan,
/// Span of the entire specialize declaration.
span: SimpleSpan,
},
Expand Down Expand Up @@ -69,11 +69,11 @@ where
.labelled("function signature")
}

/// Body span scanner. Matches an optional keyword prefix (e.g. `digraph`,
/// Definition span scanner. Matches an optional keyword prefix (e.g. `digraph`,
/// `ungraph`) followed by a brace-balanced `{ ... }` CFG. Returns the
/// span covering everything from the first non-brace token (or the opening
/// brace) through the matching closing brace. Does not parse body contents.
fn body_span<'src, I>() -> impl Parser<'src, I, SimpleSpan, ParserError<'src>>
/// brace) through the matching closing brace. Does not parse the definition.
fn definition_span<'src, I>() -> impl Parser<'src, I, SimpleSpan, ParserError<'src>>
where
I: TokenInput<'src>,
{
Expand All @@ -88,7 +88,7 @@ where
None => {
return Err(Rich::custom(
input.span_since(&start),
"expected '{' in body",
"expected '{' in function definition",
));
}
}
Expand Down Expand Up @@ -132,10 +132,10 @@ where
// The function name is extracted post-parse from EmitContext::function_name().
let specialize_decl = identifier("specialize")
.ignore_then(symbol())
.then(body_span::<I>()) // captures from keyword (e.g. `fn`) through closing `}`
.map_with(|(stage, body_span), extra| Declaration::Specialize {
.then(definition_span::<I>()) // captures from keyword (e.g. `fn`) through closing `}`
.map_with(|(stage, definition_span), extra| Declaration::Specialize {
stage,
body_span,
definition_span,
span: extra.span(),
});

Expand Down
46 changes: 23 additions & 23 deletions crates/kirin-chumsky/src/function_text/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ trivial_type_lattice!(I32Type, "i32", just(Token::Identifier("i32")));
#[derive(Clone, Debug, PartialEq, Eq, Hash, kirin_ir::Dialect, HasParser, PrettyPrint)]
#[kirin(builders, type = UnitType, crate = kirin_ir)]
#[chumsky(crate = crate, format = "fn {:name}{sig} {body}")]
struct FunctionBody {
struct FunctionDefinition {
body: CFG,
sig: Signature<UnitType>,
}
Expand All @@ -109,9 +109,9 @@ struct LowerBody {
#[stage(crate = "kirin_ir", chumsky_crate = "crate")]
enum StageBucket {
#[stage(name = "A")]
Parse(StageInfo<FunctionBody>),
Parse(StageInfo<FunctionDefinition>),
#[stage(name = "B")]
Lower(StageInfo<FunctionBody>),
Lower(StageInfo<FunctionDefinition>),
}

// ---------------------------------------------------------------------------
Expand All @@ -122,7 +122,7 @@ enum StageBucket {
#[stage(crate = "kirin_ir", chumsky_crate = "crate")]
enum MixedStage {
#[stage(name = "A")]
StageA(StageInfo<FunctionBody>),
StageA(StageInfo<FunctionDefinition>),
#[stage(name = "B")]
StageB(StageInfo<LowerBody>),
}
Expand Down Expand Up @@ -155,7 +155,7 @@ fn parsed_names<S>(pipeline: &Pipeline<S>, functions: Vec<Function>) -> BTreeSet

#[test]
fn test_pipeline_parse_accepts_mixed_function_names() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let input = format!(
"stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () {BODY} \
stage @B fn @bar(()) -> (); specialize @B fn @bar(()) -> () {BODY}"
Expand All @@ -171,7 +171,7 @@ fn test_pipeline_parse_accepts_mixed_function_names() {

#[test]
fn test_pipeline_parse_uses_pipeline_global_table() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let input = format!("stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () {BODY}");

let parsed = pipeline.parse(&input).unwrap();
Expand Down Expand Up @@ -217,14 +217,14 @@ fn test_stage_enum_pipeline_parse_suggests_declared_name() {

#[test]
fn test_stage_requires_semicolon() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let err = pipeline.parse("stage @A fn @foo(()) -> ()").unwrap_err();
assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader);
}

#[test]
fn test_specialize_requires_body() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let err = pipeline
.parse("specialize @A fn @foo(()) -> ();")
.unwrap_err();
Expand All @@ -233,7 +233,7 @@ fn test_specialize_requires_body() {

#[test]
fn test_global_symbol_prefix_is_required() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let err = pipeline.parse("stage 1 fn @foo(()) -> ();").unwrap_err();
assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader);
}
Expand All @@ -242,7 +242,7 @@ fn test_global_symbol_prefix_is_required() {
fn test_specialize_without_stage_auto_creates() {
// With auto-creation, specialize without a prior stage declaration
// succeeds by auto-creating the staged function.
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
pipeline
.add_stage()
.stage(StageInfo::default())
Expand All @@ -259,17 +259,17 @@ fn test_specialize_without_stage_auto_creates() {

#[test]
fn test_comments_and_whitespace_are_accepted() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let input = format!(
"/* stage declaration */ stage @A fn @foo(()) -> (); \
// specialization body\n specialize @A fn @foo(()) -> () /* body */ {BODY}"
// specialization definition\n specialize @A fn @foo(()) -> () /* definition */ {BODY}"
);
pipeline.parse(&input).unwrap();
}

#[test]
fn test_pipeline_roundtrip_print_parse_print() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let stage_a = pipeline
.add_stage()
.stage(StageInfo::default())
Expand All @@ -287,18 +287,18 @@ fn test_pipeline_roundtrip_print_parse_print() {
pipeline.stage_mut(stage_a).unwrap().with_builder(|b| {
let block = b.block().new();
let cfg = b.cfg().add_block(block).new();
let body = FunctionBody::new(b, cfg, Signature::new(vec![], UnitType, ()));
let definition = FunctionDefinition::new(b, cfg, Signature::new(vec![], UnitType, ()));
b.specialize()
.staged_func(staged_function)
.signature(unit_sig())
.body(body)
.definition(definition)
.new()
.unwrap();
});

let rendered = function.sprint(&pipeline);

let mut parsed_pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut parsed_pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let parsed_functions = parsed_pipeline.parse(&rendered).unwrap();
let parsed_function = parsed_functions
.into_iter()
Expand Down Expand Up @@ -355,15 +355,15 @@ fn test_pipeline_parse_uses_stage_language_dispatch() {

#[test]
fn test_pipeline_parse_empty_input() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let err = pipeline.parse("").unwrap_err();
assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader);
assert!(err.message.contains("expected at least one declaration"));
}

#[test]
fn test_pipeline_parse_whitespace_only() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let err = pipeline.parse(" \n\t ").unwrap_err();
assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader);
}
Expand All @@ -374,7 +374,7 @@ fn test_pipeline_parse_whitespace_only() {

#[test]
fn test_pipeline_parse_numeric_stage_symbol_rejected() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
// Numeric tokens like `1` are not prefixed with `@`, so `stage 1` should fail
let err = pipeline.parse("stage 1 fn @foo(()) -> ();").unwrap_err();
assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader);
Expand All @@ -383,7 +383,7 @@ fn test_pipeline_parse_numeric_stage_symbol_rejected() {
#[test]
fn test_pipeline_numeric_stage_lookup_by_existing_id() {
// When a stage already exists in the pipeline, @<numeric> can find it by raw ID
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let stage_id = pipeline
.add_stage()
.stage(StageInfo::default())
Expand Down Expand Up @@ -442,7 +442,7 @@ fn test_stage_suggestion_very_distant_name() {
#[test]
fn test_invalid_body_parse_has_source() {
use std::error::Error;
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
// Valid header but invalid body tokens
let err = pipeline
.parse("stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () { invalid }")
Expand All @@ -458,7 +458,7 @@ fn test_invalid_body_parse_has_source() {

#[test]
fn test_duplicate_stage_declaration_same_signature() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let input = format!(
"stage @A fn @foo(()) -> (); \
stage @A fn @foo(()) -> (); \
Expand All @@ -474,7 +474,7 @@ fn test_duplicate_stage_declaration_same_signature() {

#[test]
fn test_invalid_declaration_keyword() {
let mut pipeline: Pipeline<StageInfo<FunctionBody>> = Pipeline::new();
let mut pipeline: Pipeline<StageInfo<FunctionDefinition>> = Pipeline::new();
let err = pipeline.parse("define @A fn @foo(()) -> ();").unwrap_err();
assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader);
}
Loading