diff --git a/crates/kirin-chumsky/src/ast/blocks.rs b/crates/kirin-chumsky/src/ast/blocks.rs index 3b1557489c..0d254fbc38 100644 --- a/crates/kirin-chumsky/src/ast/blocks.rs +++ b/crates/kirin-chumsky/src/ast/blocks.rs @@ -42,7 +42,7 @@ pub struct BlockHeader<'src, TypeOutput> { /// A basic block containing a label, arguments, and statements. /// -/// Represents syntax like: +/// Represents the untagged block syntax: /// ```ignore /// ^bb0(%arg: i32) { /// %x = add %arg, %arg; @@ -50,6 +50,11 @@ pub struct BlockHeader<'src, TypeOutput> { /// } /// ``` /// +/// This AST node covers both roles the untagged form plays: a CFG's member +/// block (where the enclosing `cfg { .. }` supplies the discriminator) and a +/// standalone `Block` body, which the parser and printer tag with the `block` +/// keyword around this same shape. +/// /// Fields are flat to support both full parsing (with block header) and /// projection-based parsing (where pieces come from different format positions). /// @@ -69,12 +74,15 @@ pub struct Block<'src, TypeOutput, StmtOutput> { /// /// Represents syntax like: /// ```ignore -/// { +/// cfg { /// ^entry(%arg: i32) { ... }; /// ^bb1() { ... }; /// } /// ``` /// +/// The `cfg` discriminator belongs to the whole container; the member blocks +/// stay untagged. +/// /// The `TypeOutput` parameter is the parsed type representation. /// The `StmtOutput` parameter is the parsed statement representation. #[derive(Debug, Clone, PartialEq)] diff --git a/crates/kirin-chumsky/src/function_text/parse_text.rs b/crates/kirin-chumsky/src/function_text/parse_text.rs index 44edf13e96..f131a41ff5 100644 --- a/crates/kirin-chumsky/src/function_text/parse_text.rs +++ b/crates/kirin-chumsky/src/function_text/parse_text.rs @@ -43,7 +43,7 @@ //! //! ```text //! stage @A fn @foo(()) -> (); -//! specialize @A fn @foo(()) -> () { ^0() {} } +//! specialize @A fn @foo(()) -> () cfg { ^0() {} } //! ``` //! //! - Pass 1 creates/finds function `@foo` and staged function `(A, foo)`. @@ -53,9 +53,9 @@ //! //! ```text //! stage @A fn @foo(()) -> (); -//! specialize @A fn @foo(()) -> () { ^0() {} } +//! specialize @A fn @foo(()) -> () cfg { ^0() {} } //! stage @B fn @bar(i32) -> i32; -//! specialize @B fn @bar(i32) -> i32 { ^0() {} } +//! specialize @B fn @bar(i32) -> i32 cfg { ^0() {} } //! ``` //! //! - declarations for `@A` are parsed with stage `A`'s dialect; @@ -64,7 +64,7 @@ //! Missing header before specialize: //! //! ```text -//! specialize @A fn @missing(()) -> () { ^0() {} } +//! specialize @A fn @missing(()) -> () cfg { ^0() {} } //! ``` //! //! - pass 2 cannot find `(A, missing)` in the staged lookup; diff --git a/crates/kirin-chumsky/src/function_text/syntax.rs b/crates/kirin-chumsky/src/function_text/syntax.rs index 94110b3bfe..680cf6e6aa 100644 --- a/crates/kirin-chumsky/src/function_text/syntax.rs +++ b/crates/kirin-chumsky/src/function_text/syntax.rs @@ -69,18 +69,35 @@ where .labelled("function signature") } -/// Body 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. +/// Body span scanner. Skips the body's discriminator and header — whatever the +/// dialect's format string puts before the first `{` — then matches a +/// brace-balanced `{ ... }`. Returns the span covering everything from the +/// first token through the matching closing brace. Does not parse body +/// contents; that is the dialect statement parser's job, which is what keeps +/// dialect-level validation intact. +/// +/// All four body kinds carry an explicit textual discriminator, and each is +/// scanned by the same rule: +/// +/// ```text +/// fn @f(..) -> T cfg { ^entry(..) { .. } } // keyword, then the CFG's braces +/// fn @f(..) -> T block ^body(..) { .. } // keyword + header, then braces +/// fn @f(..) -> T digraph ^g0(..) { .. } // keyword + header, then braces +/// fn @f(..) -> T ungraph ^u0(..) { .. } // keyword + header, then braces +/// ``` +/// +/// Projected formats (`fn @f(..) -> T (%x: T) { .. }`) work the same way: the +/// scanner does not care what the prefix tokens are, only where the first `{` +/// is. fn body_span<'src, I>() -> impl Parser<'src, I, SimpleSpan, ParserError<'src>> where I: TokenInput<'src>, { chumsky::primitive::custom(|input: &mut chumsky::input::InputRef<'src, '_, I, _>| { let start = input.cursor(); - // Skip tokens until we find the opening brace. This allows keyword - // prefixes like `digraph ^name(ports...) {` or `ungraph ^name(...) {`. + // Skip tokens until we find the opening brace. This is what lets the + // discriminator and header through: `cfg {`, `block ^name(args...) {`, + // `digraph ^name(ports...) {`, `ungraph ^name(...) {`. loop { match input.next() { Some(Token::LBrace) => break, diff --git a/crates/kirin-chumsky/src/function_text/tests.rs b/crates/kirin-chumsky/src/function_text/tests.rs index 0d55bf6483..8e68c048a4 100644 --- a/crates/kirin-chumsky/src/function_text/tests.rs +++ b/crates/kirin-chumsky/src/function_text/tests.rs @@ -131,7 +131,7 @@ enum MixedStage { // Helpers // --------------------------------------------------------------------------- -const BODY: &str = "{ ^0() {} }"; +const BODY: &str = "cfg { ^0() {} }"; fn unit_sig() -> Signature { Signature::new(vec![UnitType], UnitType, ()) @@ -445,7 +445,7 @@ fn test_invalid_body_parse_has_source() { let mut pipeline: Pipeline> = Pipeline::new(); // Valid header but invalid body tokens let err = pipeline - .parse("stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () { invalid }") + .parse("stage @A fn @foo(()) -> (); specialize @A fn @foo(()) -> () cfg { invalid }") .unwrap_err(); // The body parse failure should chain a source error // (may or may not have source depending on where it fails) @@ -478,3 +478,98 @@ fn test_invalid_declaration_keyword() { let err = pipeline.parse("define @A fn @foo(()) -> ();").unwrap_err(); assert_eq!(err.kind, crate::FunctionParseErrorKind::InvalidHeader); } + +// --------------------------------------------------------------------------- +// Body-span scanner +// +// The framework only strips `specialize @stage`; the rest of the declaration — +// discriminator, header, and brace-balanced body — is handed to the dialect +// statement parser as one span. The scanner skips tokens until the first `{`, +// so it is agnostic to *which* discriminator precedes the body, and stays +// correct for all four body kinds without knowing any of them. +// --------------------------------------------------------------------------- + +/// Scan one `specialize` declaration and return the exact source slice handed +/// to the dialect statement parser. +/// +/// `LowerBody`'s `I32Type` only affects the *header* type list; the scanner +/// never parses body contents, so any body kind can be scanned through it. +fn scanned_body_span(src: &str) -> String { + let tokens = super::syntax::tokenize(src); + let (declaration, _) = super::syntax::parse_one_declaration::(&tokens) + .expect("declaration should scan"); + match declaration { + super::syntax::Declaration::Specialize { body_span, .. } => { + src[body_span.start..body_span.end].to_string() + } + super::syntax::Declaration::Stage(_) => panic!("expected a specialize declaration"), + } +} + +#[test] +fn test_body_span_scans_tagged_cfg() { + let body = "fn @f(i32) -> i32 cfg { ^entry(%x: i32) { %r = add %x, %x; ret %r; } }"; + assert_eq!(scanned_body_span(&format!("specialize @A {body}")), body); +} + +#[test] +fn test_body_span_scans_tagged_block() { + let body = "fn @f(i32) -> i32 block ^body(%x: i32) { %r = add %x, %x; ret %r; }"; + assert_eq!(scanned_body_span(&format!("specialize @A {body}")), body); +} + +#[test] +fn test_body_span_scans_digraph() { + let body = "fn @f(i32) -> i32 digraph ^g0(%x: i32) { %r = add %x, %x; yield %r; }"; + assert_eq!(scanned_body_span(&format!("specialize @A {body}")), body); +} + +#[test] +fn test_body_span_scans_ungraph() { + let body = "fn @f(i32) -> i32 ungraph ^u0(%x: i32) { edge %w = wire; node(%x, %w); }"; + assert_eq!(scanned_body_span(&format!("specialize @A {body}")), body); +} + +#[test] +fn test_body_span_scans_a_projected_body_without_a_discriminator() { + // Projections stay raw, so a dialect can spell its own wrapper. The + // scanner does not require a keyword — only a brace-balanced body. + let body = "fn @f(i32) -> i32 (%x: i32) { %r = add %x, %x; ret %r; }"; + assert_eq!(scanned_body_span(&format!("specialize @A {body}")), body); +} + +#[test] +fn test_body_span_stops_at_the_matching_brace() { + // Two declarations: the first span must end at *its* closing brace, not + // run on into the second. + let first = "fn @f(i32) -> i32 cfg { ^entry(%x: i32) { ret %x; } }"; + let second = "fn @g(i32) -> i32 cfg { ^e { } }"; + let src = format!("specialize @A {first} specialize @A {second}"); + assert_eq!(scanned_body_span(&src), first); +} + +#[test] +fn test_body_span_requires_an_opening_brace() { + let tokens = super::syntax::tokenize("specialize @A fn @f(i32) -> i32 cfg;"); + let errors = super::syntax::parse_one_declaration::(&tokens) + .expect_err("a body with no `{` should not scan"); + assert!( + errors + .iter() + .any(|e| format!("{e}").contains("expected '{'")), + "expected a missing-brace diagnostic, got: {errors:?}" + ); +} + +#[test] +fn test_body_span_rejects_an_unclosed_brace() { + let tokens = super::syntax::tokenize("specialize @A fn @f(i32) -> i32 cfg { ^entry {"); + let errors = super::syntax::parse_one_declaration::(&tokens) + .expect_err("an unbalanced body should not scan"); + assert!( + errors + .iter() + .any(|e| format!("{e}").contains("unclosed '{'")), + "expected an unclosed-brace diagnostic, got: {errors:?}" + ); +} diff --git a/crates/kirin-chumsky/src/parsers/blocks.rs b/crates/kirin-chumsky/src/parsers/blocks.rs index 72a79ab250..927c0e101e 100644 --- a/crates/kirin-chumsky/src/parsers/blocks.rs +++ b/crates/kirin-chumsky/src/parsers/blocks.rs @@ -120,14 +120,25 @@ where .labelled("block header") } -/// Parses a complete block with header and statements. +/// Parses a block with header and statements, **without** the `block` +/// discriminator keyword. +/// +/// Matches: `^bb0(%arg: i32) { ... }` +/// +/// This is the shared block grammar. It is internal on purpose: the two public +/// entry points differ only in what wraps it. +/// +/// - [`block`] prefixes the `block` keyword — a standalone `Block` body is +/// always tagged. +/// - [`cfg()`] and [`cfg_body()`] use it directly — a CFG's member blocks stay +/// untagged, because the enclosing `cfg { ... }` already names the body kind. /// /// Requires a parser for the language/dialect statements. /// /// The type parameter `T` specifies the type annotation type (typically the TypeLattice). /// The type parameter `S` is the statement AST type produced by the language parser. /// The parser produces `Block<'t, ::Output, S>`. -pub fn block<'t, I, T, S>( +fn untagged_block<'t, I, T, S>( language: RecursiveParser<'t, I, S>, ) -> impl Parser<'t, I, Spanned>::Output, S>>, ParserError<'t>> where @@ -162,18 +173,56 @@ where }) } -/// Parses a CFG containing multiple blocks. +/// Parses a complete standalone `Block` body, including its `block` discriminator. /// /// Matches: /// ```text -/// { +/// block ^bb0(%arg: i32) { +/// %x = add %arg, %arg; +/// ret %x; +/// } +/// ``` +/// +/// This is the parser behind the default `{body}` interpolation of a `Block` +/// field. The `block` keyword is **required** — the untagged form +/// `^bb0(...) { ... }` is only valid for a CFG's member blocks, where +/// [`cfg()`] supplies the discriminator once for the whole body. +/// +/// Requires a parser for the language/dialect statements. +/// +/// The type parameter `T` specifies the type annotation type (typically the TypeLattice). +/// The type parameter `S` is the statement AST type produced by the language parser. +/// The parser produces `Block<'t, ::Output, S>`. +pub fn block<'t, I, T, S>( + language: RecursiveParser<'t, I, S>, +) -> impl Parser<'t, I, Spanned>::Output, S>>, ParserError<'t>> +where + I: TokenInput<'t>, + T: HasParser<'t>, + S: Clone, +{ + just(Token::Identifier("block")) + .ignore_then(untagged_block::<_, T, S>(language)) + .labelled("block") +} + +/// Parses a CFG containing multiple blocks, including its `cfg` discriminator. +/// +/// Matches: +/// ```text +/// cfg { /// ^bb0(%arg: i32) { /// %x = add %arg, %arg; -/// return %x; +/// ret %x; /// } /// } /// ``` /// +/// This is the parser behind the default `{body}` interpolation of a `CFG` +/// field. The `cfg` keyword is **required**; the member blocks are untagged +/// (`^bb0 { ... }`, never `block ^bb0 { ... }`) because `cfg` already names +/// the body kind for the whole container. +/// /// The type parameter `T` specifies the type annotation type (typically the TypeLattice). /// The type parameter `S` is the statement AST type produced by the language parser. /// The parser produces `CFG<'t, ::Output, S>`. @@ -185,11 +234,14 @@ where T: HasParser<'t>, S: Clone, { - block::<_, T, S>(language) - .then_ignore(just(Token::Semicolon).or_not()) - .repeated() - .collect::>() - .delimited_by(just(Token::LBrace), just(Token::RBrace)) + just(Token::Identifier("cfg")) + .ignore_then( + untagged_block::<_, T, S>(language) + .then_ignore(just(Token::Semicolon).or_not()) + .repeated() + .collect::>() + .delimited_by(just(Token::LBrace), just(Token::RBrace)), + ) .map(|blocks| CFG { blocks }) .labelled("cfg") } @@ -197,8 +249,9 @@ where /// Parses block body statements (without header, without braces). /// /// Matches a sequence of `statement ;` pairs. This is the inner content of -/// a block body, used for `:body` projections on Block fields where the -/// caller provides surrounding syntax via the format string. +/// a block body, used for `{field:body}` projections on Block fields where the +/// caller provides surrounding syntax via the format string. It stays raw: no +/// `block` keyword and no braces are injected. pub fn block_body_statements<'t, I, S>( language: RecursiveParser<'t, I, S>, ) -> impl Parser<'t, I, Vec>, ParserError<'t>> @@ -217,11 +270,13 @@ where .labelled("block body statements") } -/// Parses CFG body (blocks without outer braces). +/// Parses CFG body (untagged blocks, without the `cfg` keyword or outer braces). /// -/// Matches a sequence of blocks, each optionally terminated by a semicolon. -/// This is the inner content of a CFG, used for `:body` projections on -/// CFG fields where the caller provides surrounding syntax via the format string. +/// Matches a sequence of untagged blocks, each optionally terminated by a +/// semicolon. This is the inner content of a CFG, used for `{field:body}` +/// projections on CFG fields where the caller provides surrounding syntax via +/// the format string. It stays raw: no `cfg` keyword is injected, so a dialect +/// author can spell their own wrapper. pub fn cfg_body<'t, I, T, S>( language: RecursiveParser<'t, I, S>, ) -> impl Parser<'t, I, Vec>::Output, S>>>, ParserError<'t>> @@ -230,7 +285,7 @@ where T: HasParser<'t>, S: Clone, { - block::<_, T, S>(language) + untagged_block::<_, T, S>(language) .then_ignore(just(Token::Semicolon).or_not()) .repeated() .collect::>() diff --git a/crates/kirin-chumsky/src/tests.rs b/crates/kirin-chumsky/src/tests.rs index c2cd207401..4cf9b02133 100644 --- a/crates/kirin-chumsky/src/tests.rs +++ b/crates/kirin-chumsky/src/tests.rs @@ -1488,6 +1488,138 @@ fn test_digraph_multiple_yields() { assert_eq!(dg.yields[2].value, "c"); } +// === Body Kind Discriminator Tests === +// +// Every body kind carries an explicit textual discriminator in the default +// (whole-field `{body}`) form: +// +// cfg { ^entry(..) { .. } } block ^body(..) { .. } +// digraph ^g0(..) { .. } ungraph ^u0(..) { .. } +// +// A CFG's member blocks stay *untagged* — `cfg` names the body kind once for +// the whole container. + +#[test] +fn test_cfg_requires_the_cfg_keyword() { + let language = graph_lang!(); + let result = test_parse!( + "cfg { ^bb0 ( %x : 1 ) { 42 ; } }", + cfg::<_, i32, _>(language) + ); + let cfg = result.expect("tagged cfg should parse"); + assert_eq!(cfg.blocks.len(), 1); + assert_eq!(cfg.blocks[0].value.label.unwrap().value, "bb0"); + assert_eq!(cfg.blocks[0].value.arguments.len(), 1); + assert_eq!(cfg.blocks[0].value.statements.len(), 1); +} + +#[test] +fn test_cfg_member_blocks_are_untagged() { + let language = graph_lang!(); + let result = test_parse!( + "cfg { ^entry { 1 ; } ^next { 2 ; } }", + cfg::<_, i32, _>(language) + ); + let cfg = result.expect("multi-block tagged cfg should parse"); + assert_eq!(cfg.blocks.len(), 2); + assert_eq!(cfg.blocks[0].value.label.unwrap().value, "entry"); + assert_eq!(cfg.blocks[1].value.label.unwrap().value, "next"); +} + +#[test] +fn test_block_requires_the_block_keyword() { + let language = graph_lang!(); + let result = test_parse!( + "block ^body ( %x : 1 ) { 42 ; }", + block::<_, i32, _>(language) + ); + let block = result.expect("tagged block should parse").value; + assert_eq!(block.label.unwrap().value, "body"); + assert_eq!(block.arguments.len(), 1); + assert_eq!(block.statements.len(), 1); +} + +#[test] +fn test_digraph_keeps_its_discriminator() { + let language = graph_lang!(); + let result = test_parse!( + "digraph ^g0 ( %x : 1 ) { 42 ; yield %x ; }", + digraph::<_, i32, _>(language) + ); + let dg = result.expect("tagged digraph should parse"); + assert_eq!(dg.name.unwrap().value, "g0"); + assert_eq!(dg.statements.len(), 1); + assert_eq!(dg.yields.len(), 1); +} + +#[test] +fn test_ungraph_keeps_its_discriminator() { + let language = graph_lang!(); + let result = test_parse!( + "ungraph ^u0 ( %x : 1 ) { edge 10 ; 20 ; }", + ungraph::<_, i32, _>(language) + ); + let ug = result.expect("tagged ungraph should parse"); + assert_eq!(ug.name.unwrap().value, "u0"); + assert_eq!(ug.statements.len(), 2); + assert!(ug.statements[0].is_edge); +} + +#[test] +fn test_legacy_untagged_cfg_is_rejected() { + let language = graph_lang!(); + let result: Result, _> = + test_parse!("{ ^bb0 { 42 ; } }", cfg::<_, i32, _>(language)); + assert!( + result.is_err(), + "the pre-discriminator CFG form `{{ ^bb0 .. }}` must not parse" + ); +} + +#[test] +fn test_legacy_untagged_block_is_rejected() { + let language = graph_lang!(); + let result: Result>, _> = + test_parse!("^body ( %x : 1 ) { 42 ; }", block::<_, i32, _>(language)); + assert!( + result.is_err(), + "the pre-discriminator Block form `^body(..) {{ .. }}` must not parse" + ); +} + +#[test] +fn test_block_keyword_with_cfg_shape_is_rejected() { + let language = graph_lang!(); + let result: Result>, _> = + test_parse!("block { ^entry { 42 ; } }", block::<_, i32, _>(language)); + assert!( + result.is_err(), + "`block {{ ^entry .. }}` mixes the Block tag with the CFG shape" + ); +} + +#[test] +fn test_cfg_keyword_with_block_shape_is_rejected() { + let language = graph_lang!(); + let result: Result, _> = + test_parse!("cfg ^body ( %x : 1 ) { 42 ; }", cfg::<_, i32, _>(language)); + assert!( + result.is_err(), + "`cfg ^body(..) {{ .. }}` mixes the CFG tag with the Block shape" + ); +} + +#[test] +fn test_tagged_member_block_inside_cfg_is_rejected() { + let language = graph_lang!(); + let result: Result, _> = + test_parse!("cfg { block ^bb0 { 42 ; } }", cfg::<_, i32, _>(language)); + assert!( + result.is_err(), + "a CFG's member blocks must stay untagged — `cfg {{ block ^bb0 .. }}` is not valid" + ); +} + // === Scope Guard Tests (P1-5, P1-6 regression) === #[test] diff --git a/crates/kirin-derive-chumsky/src/field_kind.rs b/crates/kirin-derive-chumsky/src/field_kind.rs index 83643b90ca..cb54d0f7d6 100644 --- a/crates/kirin-derive-chumsky/src/field_kind.rs +++ b/crates/kirin-derive-chumsky/src/field_kind.rs @@ -93,6 +93,12 @@ pub fn ast_type( } /// Generates the parser expression for a field. +/// +/// For body fields the split is: [`FormatOption::Default`] selects the +/// **canonical tagged** parser (`cfg`, `block`, `digraph`, `ungraph` — each +/// requires its discriminator keyword), while `FormatOption::Body(_)` +/// projections select **raw** component parsers that inject no keyword and no +/// delimiters, so a dialect author can spell their own syntax around them. pub fn parser_expr( field: &FieldInfo, crate_path: &syn::Path, @@ -225,6 +231,11 @@ pub fn parser_expr( } /// Generates pretty print expression for a field. +/// +/// Mirrors [`parser_expr`] exactly: [`FormatOption::Default`] emits the +/// **canonical tagged** printer (`print_cfg`/`print_block`/`print_digraph`/ +/// `print_ungraph`), while `FormatOption::Body(_)` projections emit the **raw** +/// `*_only` helpers, which print no discriminator and no delimiters. pub fn print_expr( field: &FieldInfo, prettyless_path: &syn::Path, diff --git a/crates/kirin-derive-chumsky/src/format.rs b/crates/kirin-derive-chumsky/src/format.rs index 3a5f477776..69e10c77d5 100644 --- a/crates/kirin-derive-chumsky/src/format.rs +++ b/crates/kirin-derive-chumsky/src/format.rs @@ -56,6 +56,36 @@ //! category must be present for roundtrip correctness. For example, a DiGraph field //! with `:body` must also have `:ports` and `:captures`. //! +//! # Body Kinds: Default Interpolation vs Projections +//! +//! A body field's **default** interpolation (`{body}`, no projection) parses and +//! prints the *canonical tagged* form — every body kind carries an explicit +//! textual discriminator, supplied centrally rather than typed into each format +//! string: +//! +//! | Field Category | `{body}` parses/prints | +//! |----------------|------------------------| +//! | Block | `block ^name(%arg: T, ..) { stmt; .. }` | +//! | CFG | `cfg { ^name(%arg: T, ..) { stmt; .. } .. }` | +//! | DiGraph | `digraph ^name(%port: T) [capture(%c: T)] { stmt; .. [yield %v;] }` | +//! | UnGraph | `ungraph ^name(%port: T) [capture(%c: T)] { [edge] stmt; .. }` | +//! +//! A CFG's member blocks stay **untagged** — `cfg` names the body kind once for +//! the whole container, so `cfg { block ^entry { .. } }` is not valid. +//! +//! **Projections stay raw.** `:args`, `:body`, `:ports`, and `:captures` never +//! inject a discriminator, delimiters, or a header — they are the seam a dialect +//! author uses to define custom syntax, so the format string supplies whatever +//! surrounds them: +//! +//! ```text +//! // Default: the canonical tagged Block form, `block ^body(..) { .. }`. +//! "fn {:name}{sig} {body}" +//! +//! // Projected: the dialect's own wrapper — no `block` keyword is added. +//! "fn {:name}{sig} ({body:args}) {{ {body:body} }}" +//! ``` +//! //! # Escaping //! //! To include a literal `{` character in the format string, use `{{`: diff --git a/crates/kirin-liveness/tests/cfg.rs b/crates/kirin-liveness/tests/cfg.rs index 879c5cbeec..5379a30590 100644 --- a/crates/kirin-liveness/tests/cfg.rs +++ b/crates/kirin-liveness/tests/cfg.rs @@ -10,7 +10,7 @@ use kirin_test_languages::ArithFunctionLanguage; const PROGRAM: &str = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { %dead = add %x, %x -> i64; cond_br %cond then=^then(%x) else=^else(%x); @@ -28,7 +28,7 @@ specialize @test fn @main(i64, i64) -> i64 { const DEAD_EDGE_ARG_PROGRAM: &str = r#" stage @test fn @main(i64, i64, i64) -> i64; -specialize @test fn @main(i64, i64, i64) -> i64 { +specialize @test fn @main(i64, i64, i64) -> i64 cfg { ^entry(%live: i64, %dead: i64, %cond: i64) { cond_br %cond then=^then(%live) else=^else(%dead); } @@ -178,7 +178,7 @@ fn unused_successor_block_argument_does_not_keep_edge_arg_live() { const RET_PARAM_PROGRAM: &str = r#" stage @test fn @main(i64) -> i64; -specialize @test fn @main(i64) -> i64 { +specialize @test fn @main(i64) -> i64 cfg { ^entry(%x: i64) { ret %x; } @@ -188,7 +188,7 @@ specialize @test fn @main(i64) -> i64 { const DEMANDED_RESULT_PROGRAM: &str = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%a: i64, %b: i64) { %s = add %a, %b -> i64; ret %s; @@ -199,7 +199,7 @@ specialize @test fn @main(i64, i64) -> i64 { const DEAD_RESULT_PROGRAM: &str = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%a: i64, %b: i64) { %s = add %a, %b -> i64; ret %a; diff --git a/crates/kirin-prettyless/src/document/ir_render.rs b/crates/kirin-prettyless/src/document/ir_render.rs index 4c284cb57d..addc8cc8c6 100644 --- a/crates/kirin-prettyless/src/document/ir_render.rs +++ b/crates/kirin-prettyless/src/document/ir_render.rs @@ -18,7 +18,7 @@ where { /// Resolve a block/graph name to `^resolved` or fall back to `fallback`'s Display output. /// - /// Used by `print_block`, `print_digraph`, and `print_ungraph` to avoid + /// Used by `print_untagged_block`, `print_digraph`, and `print_ungraph` to avoid /// duplicating the symbol-table lookup + caret-prefix logic. pub(crate) fn resolve_caret_name( &self, @@ -120,8 +120,33 @@ where } } - /// Pretty print a block with its header and statements. + /// Pretty print a standalone `Block` body, tagged with its `block` discriminator. + /// + /// Renders as: + /// ```text + /// block ^name(%arg0: type) { + /// + /// } + /// ``` + /// + /// This is the printer behind the default `{body}` interpolation of a + /// `Block` field, and is the exact inverse of `kirin_chumsky::block`. + /// A CFG's member blocks are *not* printed through here — they stay + /// untagged; the shared untagged block-rendering helper handles those. pub fn print_block(&'a self, block: &Block) -> ArenaDoc<'a> { + self.text("block ") + self.print_untagged_block(block) + } + + /// Pretty print a block with its header and statements, **without** the + /// `block` discriminator keyword. + /// + /// Renders as `^name(%arg0: type) { }`. + /// + /// Internal: this is the shared block-rendering helper. [`Document::print_block`] + /// wraps it with the `block` keyword; [`Document::print_cfg`] and + /// [`Document::print_cfg_body_only`] use it directly, because the enclosing + /// `cfg { ... }` already names the body kind for every member block. + pub(crate) fn print_untagged_block(&'a self, block: &Block) -> ArenaDoc<'a> { let block_info = block.expect_info(self.stage); // Build block header with arguments: ^name(%arg0: type, %arg1: type) @@ -153,14 +178,24 @@ where header + self.text(" {") + self.block_indent(inner) + self.line_() + self.text("}") } - /// Pretty print a CFG with its blocks. + /// Pretty print a CFG body, tagged with its `cfg` discriminator. + /// + /// Renders as: + /// ```text + /// cfg { + /// ^entry(%arg0: type) { + /// + /// } + /// } + /// ``` + /// + /// This is the printer behind the default `{body}` interpolation of a `CFG` + /// field, and is the exact inverse of `kirin_chumsky::cfg`. Member blocks + /// are rendered untagged — `cfg` names the body kind once for the whole + /// container. pub fn print_cfg(&'a self, cfg: &CFG) -> ArenaDoc<'a> { - let mut inner = self.nil(); - for block in cfg.blocks(self.stage) { - inner += self.print_block(&block); - inner += self.line_(); - } - self.block_indent(inner).enclose("{", "}") + let inner = self.print_cfg_body_only(cfg); + self.text("cfg ") + self.block_indent(inner).enclose("{", "}") } /// Pretty print a list of ports. @@ -220,10 +255,14 @@ where /// Pretty print a specialized function with its full header. /// - /// Renders as: + /// The framework contributes only the `specialize @stage` prefix; everything + /// after it is the dialect's own format string. With the common + /// `"fn {:name}{sig} {body}"` over a `CFG` field that renders as: /// ```text - /// specialize @stage fn @name(Type0, Type1) -> RetType { - /// + /// specialize @stage fn @name(Type0, Type1) -> RetType cfg { + /// ^entry(%arg0: Type0, %arg1: Type1) { + /// + /// } /// } /// ``` /// @@ -254,9 +293,10 @@ where /// stage @A fn @name(Type0, Type1) -> RetType; /// ``` /// - /// Each active specialization is then rendered as: + /// Each active specialization is then rendered as (body layout is + /// dialect-controlled; `cfg { ... }` is what a `CFG` field's `{body}` gives): /// ```text - /// specialize @A fn @name(Type0, Type1) -> RetType { ... } + /// specialize @A fn @name(Type0, Type1) -> RetType cfg { ... } /// ``` pub fn print_staged_function(&'a self, func: &StagedFunction) -> ArenaDoc<'a> { let info = func.expect_info(self.stage); @@ -458,17 +498,22 @@ where inner } - /// Print a CFG body only: blocks without outer braces. + /// Print a CFG body only: untagged blocks, without the `cfg` keyword or + /// outer braces. + /// + /// Stays raw for `{field:body}` projections — no `cfg` keyword is injected, + /// and the member blocks are untagged, matching `kirin_chumsky::cfg_body`. pub fn print_cfg_body_only(&'a self, cfg: &CFG) -> ArenaDoc<'a> { let mut inner = self.nil(); for block in cfg.blocks(self.stage) { - inner += self.print_block(&block); + inner += self.print_untagged_block(&block); inner += self.line_(); } inner } - /// Print a Block body only: statements without the header or braces. + /// Print a Block body only: statements without the `block` keyword, the + /// header, or the braces. pub fn print_block_body_only(&'a self, block: &Block) -> ArenaDoc<'a> { let mut inner = self.nil(); for (i, stmt) in block.statements(self.stage).enumerate() { diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__block.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__block.snap index cea7b39969..f04cd33c28 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__block.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__block.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/snapshot.rs expression: buf --- -specialize @0 %8 = { +specialize @0 %8 = cfg { ^0(%5: i64, %y: f64) { %0 = constant 1.2; %1 = constant 3.4; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_large_tab_spaces.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_large_tab_spaces.snap index b04c13f484..cc405e5c05 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_large_tab_spaces.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_large_tab_spaces.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/edge_cases.rs expression: buf --- -^0 { +block ^0 { %0 = constant 1; return %0; } diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_zero_tab_spaces.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_zero_tab_spaces.snap index e05bdcc9bd..155b93904a 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_zero_tab_spaces.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__config_zero_tab_spaces.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/edge_cases.rs expression: buf --- -^0 { +block ^0 { %0 = constant 1; return %0; } diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__custom_width.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__custom_width.snap index 07896d30d7..e2429e3f79 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__custom_width.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__custom_width.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/snapshot.rs expression: output --- -specialize @0 %8 = { +specialize @0 %8 = cfg { ^0(%5: i64, %y: f64) { %0 = constant 1.2; %1 = constant 3.4; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_function_print.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_function_print.snap index 2ca0a0a8cf..f8c0cd78ad 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_function_print.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_function_print.snap @@ -3,7 +3,7 @@ source: crates/kirin-prettyless/src/tests/pipeline.rs expression: output --- stage @A fn @foo(i64) -> i64; -specialize @A %1 = { +specialize @A %1 = cfg { ^0 { %0 = constant 42; return %0; @@ -11,7 +11,7 @@ specialize @A %1 = { } stage @B fn @foo(i64) -> i64; -specialize @B %3 = { +specialize @B %3 = cfg { ^0 { %0 = constant 10; %1 = constant 20; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_unnamed_stage.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_unnamed_stage.snap index 0aeab7db0a..cfe6bf4694 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_unnamed_stage.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__pipeline_unnamed_stage.snap @@ -3,7 +3,7 @@ source: crates/kirin-prettyless/src/tests/pipeline.rs expression: output --- stage @0 fn @bar(i64, f64) -> i64; -specialize @0 %1 = { +specialize @0 %1 = cfg { ^0 { %0 = constant 7; return %0; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_empty_body.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_empty_body.snap index 54cdc6cb89..bf2db4aeaf 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_empty_body.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_empty_body.snap @@ -2,6 +2,6 @@ source: crates/kirin-prettyless/src/tests/impls.rs expression: buf --- -^0 { +block ^0 { } diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_multiple_unnamed_args.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_multiple_unnamed_args.snap index 050b9b0f06..499c857473 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_multiple_unnamed_args.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_multiple_unnamed_args.snap @@ -2,6 +2,6 @@ source: crates/kirin-prettyless/src/tests/edge_cases.rs expression: buf --- -^0(%1: i64, %2: f64, %3: i64) { +block ^0(%1: i64, %2: f64, %3: i64) { return %1; } diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_only_terminator.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_only_terminator.snap index b2ca33263f..642f863ad0 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_only_terminator.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_only_terminator.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/impls.rs expression: buf --- -^0 { +block ^0 { %0 = constant 1; return %0; } diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_with_named_args.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_with_named_args.snap index 155579e36e..9f095b9f86 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_with_named_args.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_block_with_named_args.snap @@ -2,6 +2,6 @@ source: crates/kirin-prettyless/src/tests/impls.rs expression: buf --- -^0(%x: i64) { +block ^0(%x: i64) { return %x; } diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_empty.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_empty.snap index 8979266712..74217c3417 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_empty.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_empty.snap @@ -2,5 +2,5 @@ source: crates/kirin-prettyless/src/tests/impls.rs expression: buf --- -{ +cfg { } diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_multiple_blocks.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_multiple_blocks.snap index 3e02c522e4..6efc2d5ff4 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_multiple_blocks.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__print_cfg_multiple_blocks.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/edge_cases.rs expression: buf --- -{ +cfg { ^0 { %0 = constant 1; return %0; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_builder_config.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_builder_config.snap index 52ae02717f..040ea6ea17 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_builder_config.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_builder_config.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/impls.rs expression: output --- -specialize @0 %3 = { +specialize @0 %3 = cfg { ^0 { %0 = constant 1; %1 = constant 2; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_specialized_function.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_specialized_function.snap index 07896d30d7..e2429e3f79 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_specialized_function.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__render_specialized_function.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/snapshot.rs expression: output --- -specialize @0 %8 = { +specialize @0 %8 = cfg { ^0(%5: i64, %y: f64) { %0 = constant 1.2; %1 = constant 3.4; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__sprint_with_globals.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__sprint_with_globals.snap index 5c428902b5..c71f12004c 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__sprint_with_globals.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__sprint_with_globals.snap @@ -3,7 +3,7 @@ source: crates/kirin-prettyless/src/tests/sprint_with_globals.rs expression: output --- stage @0 fn @my_function(i64) -> i64; -specialize @0 %1 = { +specialize @0 %1 = cfg { ^0 { %0 = constant 42; return %0; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_no_params.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_no_params.snap index cc05e9bb1d..6610568109 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_no_params.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_no_params.snap @@ -3,7 +3,7 @@ source: crates/kirin-prettyless/src/tests/edge_cases.rs expression: output --- stage @0 fn @nullary() -> i64; -specialize @0 %1 = { +specialize @0 %1 = cfg { ^0 { %0 = constant 0; return %0; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_unnamed.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_unnamed.snap index 08dea8eb55..ef0ddc2ad2 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_unnamed.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__staged_function_unnamed.snap @@ -3,7 +3,7 @@ source: crates/kirin-prettyless/src/tests/edge_cases.rs expression: output --- stage @X fn @unnamed() -> i64; -specialize @X %1 = { +specialize @X %1 = cfg { ^0 { %0 = constant 0; return %0; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_to_vec.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_to_vec.snap index cf3deb70fe..c05caedadb 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_to_vec.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_to_vec.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/write.rs expression: output --- -specialize @0 %8 = { +specialize @0 %8 = cfg { ^0(%5: i64, %y: f64) { %0 = constant 1.2; %1 = constant 3.4; diff --git a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_with_config.snap b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_with_config.snap index cf3deb70fe..c05caedadb 100644 --- a/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_with_config.snap +++ b/crates/kirin-prettyless/src/tests/snapshots/kirin_prettyless__tests__write_with_config.snap @@ -2,7 +2,7 @@ source: crates/kirin-prettyless/src/tests/write.rs expression: output --- -specialize @0 %8 = { +specialize @0 %8 = cfg { ^0(%5: i64, %y: f64) { %0 = constant 1.2; %1 = constant 3.4; diff --git a/docs/design/backlog/missing-tests.md b/docs/design/backlog/missing-tests.md index 6385dac514..377978a507 100644 --- a/docs/design/backlog/missing-tests.md +++ b/docs/design/backlog/missing-tests.md @@ -33,9 +33,7 @@ No test for graph bodies as function bodies in the full pipeline parse: ``` stage @A fn @f(f64) -> f64; -specialize @A fn @f(f64) -> f64 { - digraph ^dg0(%p0: f64) { ... yield %r; } -} +specialize @A fn @f(f64) -> f64 digraph ^dg0(%p0: f64) { ... yield %r; } ``` **What to test**: `roundtrip::assert_pipeline_roundtrip` with a language that has a graph-body function variant. diff --git a/docs/design/graph-ir-node.md b/docs/design/graph-ir-node.md index 468eb0f9d4..4e297e1b79 100644 --- a/docs/design/graph-ir-node.md +++ b/docs/design/graph-ir-node.md @@ -1,6 +1,6 @@ # Native Graph IR Node — Text Format and Semantics Design -This design introduces two new IR body kinds — `digraph` and `ungraph` — alongside Block and CFG (`CFG`). A graph body uses standard statement syntax where SSAValues represent edges. The leading keyword (`^`, `digraph`, `ungraph`) selects the backing storage: Block (linked list), petgraph DiGraph, or petgraph UnGraph. +This design introduces two new IR body kinds — `digraph` and `ungraph` — alongside Block and CFG (`CFG`). A graph body uses standard statement syntax where SSAValues represent edges. A leading keyword (`cfg`, `block`, `digraph`, `ungraph`) selects the backing storage: block list, linked list, petgraph DiGraph, or petgraph UnGraph. - For **directed graphs**, the text format follows MLIR graph region semantics (relaxed dominance, SSA def-use = directed edges). - For **undirected graphs**, `edge`-prefixed statements introduce edge SSAValues, and statements that share edge references are connected. @@ -15,15 +15,72 @@ This design introduces two new IR body kinds — `digraph` and `ungraph` — alo ## Overview -### Three Body Kinds +### Four Body Kinds -| Keyword | Body Kind | Storage | Dominance | Edge Semantics | -|---------|-----------|---------|-----------|----------------| -| `^bb0(args)` | Block | Linked list | Enforced | N/A (sequential) | -| `digraph ^dg0(args)` | Directed graph | petgraph DiGraph | Relaxed | SSA def-use = directed edge | -| `ungraph ^ug0(args)` | Undirected graph | petgraph UnGraph | Relaxed | Shared edge reference = connection | +Every body kind carries an **explicit textual discriminator**. There is no +implicit form — nothing is disambiguated by peeking at whether the next token is +`{` or `^`. -All three use standard statement syntax. +| Discriminator | Body Kind | Storage | Dominance | Edge Semantics | +|---------------|-----------|---------|-----------|----------------| +| `cfg { ^bb0(args) { .. } .. }` | CFG | Block list | Enforced | N/A (control-flow edges) | +| `block ^bb0(args) { .. }` | Block | Linked list | Enforced | N/A (sequential) | +| `digraph ^dg0(args) { .. }` | Directed graph | petgraph DiGraph | Relaxed | SSA def-use = directed edge | +| `ungraph ^ug0(args) { .. }` | Undirected graph | petgraph UnGraph | Relaxed | Shared edge reference = connection | + +All four use standard statement syntax. + +#### A CFG's member blocks stay untagged + +`cfg` names the body kind once for the whole container, so the blocks inside it +are written bare: + +``` +fn @f(i64) -> i64 cfg { + ^entry(%x: i64) { + br ^next(%x); + } + ^next(%r: i64) { + ret %r; + } +} +``` + +`cfg { block ^entry { .. } }` is **not** valid — the inner `block` tag would +claim each member block is a standalone Block body. + +#### Where the discriminator comes from + +The discriminator belongs to the **default whole-field interpolation** of a body +field, so it is produced centrally rather than typed into every dialect format +string. A dialect writes `{body}` and gets the tagged canonical form: + +| Field type | Default `{body}` parses/prints | Parser | Printer | +|------------|-------------------------------|--------|---------| +| `CFG` | `cfg { ^bb0(..) { .. } .. }` | `kirin_chumsky::cfg` | `Document::print_cfg` | +| `Block` | `block ^bb0(..) { .. }` | `kirin_chumsky::block` | `Document::print_block` | +| `DiGraph` | `digraph ^dg0(..) [capture(..)] { .. }` | `kirin_chumsky::digraph` | `Document::print_digraph` | +| `UnGraph` | `ungraph ^ug0(..) [capture(..)] { .. }` | `kirin_chumsky::ungraph` | `Document::print_ungraph` | + +`cfg` and `block` share one internal untagged block grammar (and one untagged +block-rendering helper), so the member-block form is defined in exactly one +place. + +#### Projections stay raw + +Body **projections** are the escape hatch for a dialect that wants its own +syntax, so they never inject a discriminator: + +| Projection | Parses/prints | +|------------|---------------| +| `{body:args}` | `%name: Type, %name: Type` — Block arguments only | +| `{body:body}` | the inner statements (Block) or the untagged member blocks (CFG) | +| `{graph:ports}` | `%name: Type, %name: Type` — edge ports | +| `{graph:captures}` | `%name: Type, %name: Type` — capture ports | +| `{graph:body}` | the inner statements (plus `yield` / `edge` prefixes) | + +A dialect using `{body:body}` supplies its own delimiters and, if it wants one, +its own keyword. ### The `capture(...)` Clause @@ -66,12 +123,10 @@ digraph ^name(edge_args...) { ### Example: Quantum Circuit ``` -specialize @quantum fn @bell_pair(Qubit, Qubit) -> (Qubit, Qubit) { - digraph ^dg0(%q0: Qubit, %q1: Qubit) { - %0 = hadamard %q0 -> Qubit; - %1, %2 = cnot %0, %q1 -> (Qubit, Qubit); - yield %1, %2; - } +specialize @quantum fn @bell_pair(Qubit, Qubit) -> (Qubit, Qubit) digraph ^dg0(%q0: Qubit, %q1: Qubit) { + %0 = hadamard %q0 -> Qubit; + %1, %2 = cnot %0, %q1 -> (Qubit, Qubit); + yield %1, %2; } ``` @@ -82,12 +137,10 @@ specialize @quantum fn @bell_pair(Qubit, Qubit) -> (Qubit, Qubit) { ### Example: Parameterized Quantum Circuit ``` -specialize @quantum fn @variational(Qubit, f64, f64) -> Qubit { - digraph ^dg0(%q: Qubit) capture(%theta: f64, %phi: f64) { - %0 = rz(%theta) %q -> Qubit; - %1 = rx(%phi) %0 -> Qubit; - yield %1; - } +specialize @quantum fn @variational(Qubit, f64, f64) -> Qubit digraph ^dg0(%q: Qubit) capture(%theta: f64, %phi: f64) { + %0 = rz(%theta) %q -> Qubit; + %1 = rx(%phi) %0 -> Qubit; + yield %1; } ``` @@ -98,13 +151,11 @@ specialize @quantum fn @variational(Qubit, f64, f64) -> Qubit { ### Example: Dataflow / Computational Graph ``` -specialize @nn fn @layer(Tensor, Tensor, Tensor) -> Tensor { - digraph ^dg0(%input: Tensor, %weights: Tensor, %bias: Tensor) { - %0 = matmul %input, %weights -> Tensor; - %1 = add %0, %bias -> Tensor; - %2 = relu %1 -> Tensor; - yield %2; - } +specialize @nn fn @layer(Tensor, Tensor, Tensor) -> Tensor digraph ^dg0(%input: Tensor, %weights: Tensor, %bias: Tensor) { + %0 = matmul %input, %weights -> Tensor; + %1 = add %0, %bias -> Tensor; + %2 = relu %1 -> Tensor; + yield %2; } ``` @@ -155,17 +206,15 @@ The edge type (`-> Type`) is dialect-defined and carries whatever metadata the d ### Example: ZX Diagram ``` -specialize @zx fn @simplify(Wire, Wire, f64, f64, f64) -> (Wire, Wire) { - ungraph ^ug0(%p0: Wire, %p1: Wire) capture(%zero: f64, %pi: f64, %half_pi: f64) { - edge %w0 = wire -> Wire; - edge %w1 = wire -> Wire; - edge %w2 = wire -> Wire; - edge %w3 = wire -> Wire; - edge %w4 = wire -> Wire; - z_spider(%zero, %p0, %w0, %w1); - x_spider(%pi, %w0, %w2, %w3); - z_spider(%half_pi, %w1, %w3, %w4); - } +specialize @zx fn @simplify(Wire, Wire, f64, f64, f64) -> (Wire, Wire) ungraph ^ug0(%p0: Wire, %p1: Wire) capture(%zero: f64, %pi: f64, %half_pi: f64) { + edge %w0 = wire -> Wire; + edge %w1 = wire -> Wire; + edge %w2 = wire -> Wire; + edge %w3 = wire -> Wire; + edge %w4 = wire -> Wire; + z_spider(%zero, %p0, %w0, %w1); + x_spider(%pi, %w0, %w2, %w3); + z_spider(%half_pi, %w1, %w3, %w4); } ``` @@ -176,13 +225,11 @@ specialize @zx fn @simplify(Wire, Wire, f64, f64, f64) -> (Wire, Wire) { ### Example: ZX Diagram with Edge Metadata ``` -specialize @zx fn @colored(Wire, Wire, f64, f64) -> Wire { - ungraph ^ug0(%p0: Wire, %p1: Wire) capture(%theta: f64, %phi: f64) { - edge %w0 = hadamard_wire -> ZXEdge; - edge %w1 = plain_wire -> ZXEdge; - z_spider(%theta, %p0, %w0); - x_spider(%phi, %w0, %w1); - } +specialize @zx fn @colored(Wire, Wire, f64, f64) -> Wire ungraph ^ug0(%p0: Wire, %p1: Wire) capture(%theta: f64, %phi: f64) { + edge %w0 = hadamard_wire -> ZXEdge; + edge %w1 = plain_wire -> ZXEdge; + z_spider(%theta, %p0, %w0); + x_spider(%phi, %w0, %w1); } ``` @@ -195,10 +242,8 @@ specialize @zx fn @colored(Wire, Wire, f64, f64) -> Wire { A statement inside a graph body can contain an inner graph body, creating a compound node. It follows the same convention as a function call: operands map positionally to the inner graph's `[edge_args ++ captures]`. ``` -%out = compound_op(%edge0, %edge1, %captured0) { - ungraph ^ug1(%ip0: Wire, %ip1: Wire) capture(%c: f64) { - ... - } +%out = compound_op(%edge0, %edge1, %captured0) ungraph ^ug1(%ip0: Wire, %ip1: Wire) capture(%c: f64) { + ... } -> Wire; // %edge0 → %ip0, %edge1 → %ip1, %captured0 → %c ``` @@ -209,22 +254,19 @@ A statement inside a graph body can contain an inner graph body, creating a comp ### Example: Nested ZX Diagram ``` -specialize @zx fn @composed(Wire, Wire, f64, f64, f64, f64) -> Wire { - ungraph ^ug0(%p0: Wire, %p1: Wire) - capture(%theta: f64, %phi: f64, %alpha: f64, %beta: f64) { - edge %w0 = wire -> Wire; - edge %w1 = wire -> Wire; - edge %w3 = wire -> Wire; - z_spider(%theta, %p0, %w0, %w1); - x_spider(%phi, %w2, %w3); - %w2 = zx_sub(%w0, %w1, %alpha, %beta) { - ungraph ^ug1(%ip0: Wire, %ip1: Wire) capture(%a: f64, %b: f64) { - edge %iw0 = wire -> Wire; - z_spider(%a, %ip0, %iw0); - x_spider(%b, %ip1, %iw0); - } - } -> Wire; - } +specialize @zx fn @composed(Wire, Wire, f64, f64, f64, f64) -> Wire + ungraph ^ug0(%p0: Wire, %p1: Wire) + capture(%theta: f64, %phi: f64, %alpha: f64, %beta: f64) { + edge %w0 = wire -> Wire; + edge %w1 = wire -> Wire; + edge %w3 = wire -> Wire; + z_spider(%theta, %p0, %w0, %w1); + x_spider(%phi, %w2, %w3); + %w2 = zx_sub(%w0, %w1, %alpha, %beta) ungraph ^ug1(%ip0: Wire, %ip1: Wire) capture(%a: f64, %b: f64) { + edge %iw0 = wire -> Wire; + z_spider(%a, %ip0, %iw0); + x_spider(%b, %ip1, %iw0); + } -> Wire; } ``` @@ -235,19 +277,15 @@ specialize @zx fn @composed(Wire, Wire, f64, f64, f64, f64) -> Wire { ### Example: Nested Directed Graph ``` -specialize @hybrid fn @nested(Qubit, Qubit, f64) -> (Qubit, Qubit) { - digraph ^dg0(%q0: Qubit, %q1: Qubit) capture(%theta: f64) { - %0 = hadamard %q0 -> Qubit; - %1 = sub_circuit(%q1, %theta) { - digraph ^dg1(%iq: Qubit) capture(%t: f64) { - %2 = rz(%t) %iq -> Qubit; - %3 = hadamard %2 -> Qubit; - yield %3; - } - } -> Qubit; - %4, %5 = cnot %0, %1 -> (Qubit, Qubit); - yield %4, %5; - } +specialize @hybrid fn @nested(Qubit, Qubit, f64) -> (Qubit, Qubit) digraph ^dg0(%q0: Qubit, %q1: Qubit) capture(%theta: f64) { + %0 = hadamard %q0 -> Qubit; + %1 = sub_circuit(%q1, %theta) digraph ^dg1(%iq: Qubit) capture(%t: f64) { + %2 = rz(%t) %iq -> Qubit; + %3 = hadamard %2 -> Qubit; + yield %3; + } -> Qubit; + %4, %5 = cnot %0, %1 -> (Qubit, Qubit); + yield %4, %5; } ``` @@ -258,12 +296,10 @@ specialize @hybrid fn @nested(Qubit, Qubit, f64) -> (Qubit, Qubit) { A function can have a graph body directly. The function signature maps positionally to `[edge_args ++ captures]`: ``` -specialize @quantum fn @bell_pair(Qubit, Qubit) -> (Qubit, Qubit) { - digraph ^dg0(%q0: Qubit, %q1: Qubit) { - %0 = hadamard %q0 -> Qubit; - %1, %2 = cnot %0, %q1 -> (Qubit, Qubit); - yield %1, %2; - } +specialize @quantum fn @bell_pair(Qubit, Qubit) -> (Qubit, Qubit) digraph ^dg0(%q0: Qubit, %q1: Qubit) { + %0 = hadamard %q0 -> Qubit; + %1, %2 = cnot %0, %q1 -> (Qubit, Qubit); + yield %1, %2; } ``` @@ -276,16 +312,14 @@ specialize @quantum fn @bell_pair(Qubit, Qubit) -> (Qubit, Qubit) { Classical computation in a Block feeds into a graph body via a wrapping statement: ``` -specialize @hybrid fn @vqe(f64, f64) -> Qubit { +specialize @hybrid fn @vqe(f64, f64) -> Qubit cfg { ^entry(%theta: f64, %phi: f64) { %angle = arith.add %theta, %phi -> f64; %q = qubit_alloc() -> Qubit; - %result = quantum_eval(%q, %angle) { - digraph ^dg0(%q_in: Qubit) capture(%angle: f64) { - %0 = rz(%angle) %q_in -> Qubit; - %1 = hadamard %0 -> Qubit; - yield %1; - } + %result = quantum_eval(%q, %angle) digraph ^dg0(%q_in: Qubit) capture(%angle: f64) { + %0 = rz(%angle) %q_in -> Qubit; + %1 = hadamard %0 -> Qubit; + yield %1; } -> Qubit; ret %result; } @@ -388,7 +422,8 @@ For undirected graphs, the Block form uses `edge`-prefixed statements plus relax ### Reserved Keywords -Inside a graph body: `digraph`, `ungraph`, `edge`, `capture`. +Body discriminators: `cfg`, `block`, `digraph`, `ungraph`. +Inside a graph body, additionally: `edge`, `capture`, `yield`. ## Deferred (Backlog) diff --git a/example/simple.rs b/example/simple.rs index 75be570979..bb852a7b6c 100644 --- a/example/simple.rs +++ b/example/simple.rs @@ -93,15 +93,15 @@ enum Stage { const PROGRAM: &str = r#" stage @source fn @main(i64, i64) -> i64; -specialize @source fn @main(i64, i64) -> i64 { +specialize @source fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { %doubled = add %x, %x -> i64; - if %cond then ^then() { + if %cond then block ^then() { %r = add %doubled, %doubled -> i64; - } else ^else() { + } else block ^else() { %r2 = sub %doubled, %doubled -> i64; }; - %f = lambda @adder captures(%doubled) { + %f = lambda @adder captures(%doubled) cfg { ^bb0(%a: i64) { %sum = add %a, %a -> i64; ret %sum; @@ -115,14 +115,14 @@ specialize @source fn @main(i64, i64) -> i64 { stage @lowered fn @main(i64, i64) -> i64; stage @lowered fn @adder(i64, i64) -> i64; -specialize @lowered fn @adder(i64, i64) -> i64 { +specialize @lowered fn @adder(i64, i64) -> i64 cfg { ^entry(%capture: i64, %a: i64) { %sum = add %a, %capture -> i64; ret %sum; } } -specialize @lowered fn @main(i64, i64) -> i64 { +specialize @lowered fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { %doubled = add %x, %x -> i64; %f = bind @adder captures(%doubled) -> i64; diff --git a/example/toy-lang/README.md b/example/toy-lang/README.md index 57ccdc73df..60121086a2 100644 --- a/example/toy-lang/README.md +++ b/example/toy-lang/README.md @@ -11,7 +11,7 @@ toy-lang composes several Kirin dialects into two compilation stages: - Comparison: `lt`, `le`, `gt`, `ge`, `eq`, `ne` - Bitwise: `and`, `or`, `xor`, `not`, `shl`, `shr` - Constants: `constant -> ` -- Control flow: `if then ^then() { ... } else ^else() { ... }` +- Control flow: `if then block ^then() { ... } else block ^else() { ... }` - Functions: `call.named @name(args...) -> `, `ret ` **Lowered stage** (`@lowered`): unstructured control flow with lifted functions @@ -59,7 +59,7 @@ Use `--` before negative arguments so they aren't parsed as flags. ``` stage @source fn @main(i64, i64) -> i64; -specialize @source fn @main(i64, i64) -> i64 { +specialize @source fn @main(i64, i64) -> i64 cfg { ^entry(%a: i64, %b: i64) { %result = add %a, %b -> i64; ret %result; diff --git a/example/toy-lang/programs/add.kirin b/example/toy-lang/programs/add.kirin index 456940dac3..b4363d083b 100644 --- a/example/toy-lang/programs/add.kirin +++ b/example/toy-lang/programs/add.kirin @@ -1,6 +1,6 @@ stage @source fn @main(i64, i64) -> i64; -specialize @source fn @main(i64, i64) -> i64 { +specialize @source fn @main(i64, i64) -> i64 cfg { ^entry(%a: i64, %b: i64) { %result = add %a, %b -> i64; ret %result; diff --git a/example/toy-lang/programs/branching.kirin b/example/toy-lang/programs/branching.kirin index af4e829436..f304fcc412 100644 --- a/example/toy-lang/programs/branching.kirin +++ b/example/toy-lang/programs/branching.kirin @@ -1,13 +1,13 @@ stage @source fn @abs(i64) -> i64; -specialize @source fn @abs(i64) -> i64 { +specialize @source fn @abs(i64) -> i64 cfg { ^entry(%x: i64) { %zero = constant 0 -> i64; %is_neg = lt %x, %zero -> i64; - %result = if %is_neg then ^then() { + %result = if %is_neg then block ^then() { %negated = neg %x -> i64; yield %negated; - } else ^else() { + } else block ^else() { yield %x; } -> i64; ret %result; diff --git a/example/toy-lang/programs/factorial.kirin b/example/toy-lang/programs/factorial.kirin index 29c0fce69e..d79b10de4c 100644 --- a/example/toy-lang/programs/factorial.kirin +++ b/example/toy-lang/programs/factorial.kirin @@ -1,12 +1,12 @@ stage @source fn @factorial(i64) -> i64; -specialize @source fn @factorial(i64) -> i64 { +specialize @source fn @factorial(i64) -> i64 cfg { ^entry(%n: i64) { %one = constant 1 -> i64; %is_base = le %n, %one -> i64; - %result = if %is_base then ^then() { + %result = if %is_base then block ^then() { yield %one; - } else ^else() { + } else block ^else() { %n_minus_1 = sub %n, %one -> i64; %rec = call.named @factorial(%n_minus_1) -> i64; %prod = mul %n, %rec -> i64; diff --git a/example/toy-lang/programs/fibonacci.kirin b/example/toy-lang/programs/fibonacci.kirin index c90e430610..17703242bb 100644 --- a/example/toy-lang/programs/fibonacci.kirin +++ b/example/toy-lang/programs/fibonacci.kirin @@ -1,12 +1,12 @@ stage @source fn @fib(i64) -> i64; -specialize @source fn @fib(i64) -> i64 { +specialize @source fn @fib(i64) -> i64 cfg { ^entry(%n: i64) { %one = constant 1 -> i64; %is_base = le %n, %one -> i64; - %result = if %is_base then ^base() { + %result = if %is_base then block ^base() { yield %n; - } else ^rec() { + } else block ^rec() { %two = constant 2 -> i64; %n1 = sub %n, %one -> i64; %n2 = sub %n, %two -> i64; diff --git a/example/toy-lang/src/interpreter/tests.rs b/example/toy-lang/src/interpreter/tests.rs index 1ad99108b4..4c11f17d6f 100644 --- a/example/toy-lang/src/interpreter/tests.rs +++ b/example/toy-lang/src/interpreter/tests.rs @@ -11,7 +11,7 @@ type ConstProp = kirin_constprop::ConstPropValue; const ADD_LOWERED: &str = r#" stage @lowered fn @add(i64, i64) -> i64; -specialize @lowered fn @add(i64, i64) -> i64 { +specialize @lowered fn @add(i64, i64) -> i64 cfg { ^entry(%a: i64, %b: i64) { %result = add %a, %b -> i64; ret %result; @@ -22,7 +22,7 @@ specialize @lowered fn @add(i64, i64) -> i64 { const BRANCH_LOWERED: &str = r#" stage @lowered fn @sign(i64) -> i64; -specialize @lowered fn @sign(i64) -> i64 { +specialize @lowered fn @sign(i64) -> i64 cfg { ^entry(%x: i64) { %zero = constant 0 -> i64; %is_neg = lt %x, %zero -> i64; @@ -41,7 +41,7 @@ specialize @lowered fn @sign(i64) -> i64 { const SAME_BRANCH_LOWERED: &str = r#" stage @lowered fn @same(i64) -> i64; -specialize @lowered fn @same(i64) -> i64 { +specialize @lowered fn @same(i64) -> i64 cfg { ^entry(%x: i64) { %zero = constant 0 -> i64; %is_neg = lt %x, %zero -> i64; @@ -59,7 +59,7 @@ specialize @lowered fn @same(i64) -> i64 { const CROSS_BLOCK_DIRECT_USE: &str = r#" stage @lowered fn @cross(i64) -> i64; -specialize @lowered fn @cross(i64) -> i64 { +specialize @lowered fn @cross(i64) -> i64 cfg { ^entry(%x: i64) { %v = add %x, %x -> i64; %zero = constant 0 -> i64; @@ -86,7 +86,7 @@ specialize @lowered fn @cross(i64) -> i64 { const LOOP_CARRIED_CROSS_BLOCK_RISE: &str = r#" stage @lowered fn @rise(i64) -> i64; -specialize @lowered fn @rise(i64) -> i64 { +specialize @lowered fn @rise(i64) -> i64 cfg { ^entry(%n: i64) { %z = constant 0 -> i64; br ^head(%z); @@ -110,10 +110,10 @@ specialize @lowered fn @rise(i64) -> i64 { const SOURCE_FOR_CARRIED_STABLE: &str = r#" stage @source fn @stable(i64, i64, i64) -> i64; -specialize @source fn @stable(i64, i64, i64) -> i64 { +specialize @source fn @stable(i64, i64, i64) -> i64 cfg { ^entry(%lo: i64, %hi: i64, %s: i64) { %init = constant 0 -> i64; - %sum = for %lo in %lo..%hi step %s iter_args(%init) do ^body(%i: i64, %acc: i64) { + %sum = for %lo in %lo..%hi step %s iter_args(%init) do block ^body(%i: i64, %acc: i64) { yield %acc; } -> i64; ret %sum; @@ -127,12 +127,12 @@ specialize @source fn @stable(i64, i64, i64) -> i64 { const SOURCE_IF_SAME_CONST: &str = r#" stage @source fn @if_same(i64) -> i64; -specialize @source fn @if_same(i64) -> i64 { +specialize @source fn @if_same(i64) -> i64 cfg { ^entry(%cond: i64) { - %result = if %cond then ^then() { + %result = if %cond then block ^then() { %a = constant 1 -> i64; yield %a; - } else ^else() { + } else block ^else() { %b = constant 1 -> i64; yield %b; } -> i64; @@ -146,12 +146,12 @@ specialize @source fn @if_same(i64) -> i64 { const SOURCE_IF_DIFF_CONST: &str = r#" stage @source fn @if_diff(i64) -> i64; -specialize @source fn @if_diff(i64) -> i64 { +specialize @source fn @if_diff(i64) -> i64 cfg { ^entry(%cond: i64) { - %result = if %cond then ^then() { + %result = if %cond then block ^then() { %a = constant 1 -> i64; yield %a; - } else ^else() { + } else block ^else() { %b = constant 2 -> i64; yield %b; } -> i64; @@ -167,28 +167,28 @@ stage @source fn @source_abs(i64) -> i64; stage @lowered fn @low_then_high(i64) -> i64; stage @lowered fn @source_abs(i64) -> i64; -specialize @source fn @source_to_lowered_to_source(i64) -> i64 { +specialize @source fn @source_to_lowered_to_source(i64) -> i64 cfg { ^entry(%x: i64) { %result = call.named @low_then_high(%x) -> i64; ret %result; } } -specialize @source fn @source_abs(i64) -> i64 { +specialize @source fn @source_abs(i64) -> i64 cfg { ^entry(%x: i64) { %zero = constant 0 -> i64; %is_neg = lt %x, %zero -> i64; - %result = if %is_neg then ^then() { + %result = if %is_neg then block ^then() { %negated = neg %x -> i64; yield %negated; - } else ^else() { + } else block ^else() { yield %x; } -> i64; ret %result; } } -specialize @lowered fn @low_then_high(i64) -> i64 { +specialize @lowered fn @low_then_high(i64) -> i64 cfg { ^entry(%x: i64) { %abs = call.named @source_abs(%x) -> i64; %one = constant 1 -> i64; @@ -203,7 +203,7 @@ stage @source fn @source_direct_specialized(i64) -> i64; stage @source fn @dual_impl(i64) -> i64; stage @lowered fn @dual_impl(i64) -> i64; -specialize @source fn @dual_impl(i64) -> i64 { +specialize @source fn @dual_impl(i64) -> i64 cfg { ^entry(%x: i64) { %one = constant 1 -> i64; %result = add %x, %one -> i64; @@ -211,7 +211,7 @@ specialize @source fn @dual_impl(i64) -> i64 { } } -specialize @lowered fn @dual_impl(i64) -> i64 { +specialize @lowered fn @dual_impl(i64) -> i64 cfg { ^entry(%x: i64) { %hundred = constant 100 -> i64; %result = add %x, %hundred -> i64; @@ -951,13 +951,13 @@ mod demand { const IF_BODY_DEMAND: &str = r#" stage @source fn @if_body(i64) -> i64; -specialize @source fn @if_body(i64) -> i64 { +specialize @source fn @if_body(i64) -> i64 cfg { ^entry(%cond: i64) { - %result = if %cond then ^then() { + %result = if %cond then block ^then() { %a = constant 1 -> i64; %junk = constant 9 -> i64; yield %a; - } else ^else() { + } else block ^else() { %b = constant 2 -> i64; yield %b; } -> i64; @@ -1000,12 +1000,12 @@ specialize @source fn @if_body(i64) -> i64 { pub(super) const IF_DEAD_RESULT: &str = r#" stage @source fn @if_dead(i64) -> i64; -specialize @source fn @if_dead(i64) -> i64 { +specialize @source fn @if_dead(i64) -> i64 cfg { ^entry(%cond: i64) { - %result = if %cond then ^then() { + %result = if %cond then block ^then() { %a = constant 1 -> i64; yield %a; - } else ^else() { + } else block ^else() { %b = constant 2 -> i64; yield %b; } -> i64; @@ -1040,10 +1040,10 @@ specialize @source fn @if_dead(i64) -> i64 { pub(super) const FOR_CARRIED_DEMAND: &str = r#" stage @source fn @loop_sum(i64, i64, i64) -> i64; -specialize @source fn @loop_sum(i64, i64, i64) -> i64 { +specialize @source fn @loop_sum(i64, i64, i64) -> i64 cfg { ^entry(%lo: i64, %hi: i64, %s: i64) { %init = constant 0 -> i64; - %sum = for %lo in %lo..%hi step %s iter_args(%init) do ^body(%i: i64, %acc: i64) { + %sum = for %lo in %lo..%hi step %s iter_args(%init) do block ^body(%i: i64, %acc: i64) { %one = constant 1 -> i64; %next = add %acc, %one -> i64; yield %next; @@ -1110,10 +1110,10 @@ specialize @source fn @loop_sum(i64, i64, i64) -> i64 { const FOR_DEAD_RESULT: &str = r#" stage @source fn @loop_dead(i64, i64, i64) -> i64; -specialize @source fn @loop_dead(i64, i64, i64) -> i64 { +specialize @source fn @loop_dead(i64, i64, i64) -> i64 cfg { ^entry(%lo: i64, %hi: i64, %s: i64) { %init = constant 0 -> i64; - %sum = for %lo in %lo..%hi step %s iter_args(%init) do ^body(%i: i64, %acc: i64) { + %sum = for %lo in %lo..%hi step %s iter_args(%init) do block ^body(%i: i64, %acc: i64) { %one = constant 1 -> i64; %next = add %acc, %one -> i64; yield %next; @@ -1157,13 +1157,13 @@ specialize @source fn @loop_dead(i64, i64, i64) -> i64 { stage @source fn @callee(i64) -> i64; stage @source fn @main(i64, i64) -> i64; -specialize @source fn @callee(i64) -> i64 { +specialize @source fn @callee(i64) -> i64 cfg { ^entry(%v: i64) { ret %v; } } -specialize @source fn @main(i64, i64) -> i64 { +specialize @source fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %y: i64) { %unused = call.named @callee(%x) -> i64; %deadsum = add %y, %y -> i64; @@ -1294,11 +1294,11 @@ mod dense { const IF_ARMS_DIFFERENT_USES: &str = r#" stage @source fn @if_arms(i64, i64, i64) -> i64; -specialize @source fn @if_arms(i64, i64, i64) -> i64 { +specialize @source fn @if_arms(i64, i64, i64) -> i64 cfg { ^entry(%cond: i64, %x: i64, %y: i64) { - %r = if %cond then ^then() { + %r = if %cond then block ^then() { yield %x; - } else ^else() { + } else block ^else() { yield %y; } -> i64; ret %r; diff --git a/tests/body_kinds.rs b/tests/body_kinds.rs index 2d99523ec1..89e2d62c8b 100644 --- a/tests/body_kinds.rs +++ b/tests/body_kinds.rs @@ -129,7 +129,7 @@ specialize @test fn @gadd(i64, i64) -> i64 digraph ^g0(%x: i64, %y: i64) { yield %s; } -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %a = constant 2 -> i64; %b = constant 3 -> i64; @@ -159,7 +159,7 @@ fn cfg_main_calls_digraph_function() { const NESTED_DIGRAPH_PROGRAM: &str = r#" stage @test fn @main() -> i64; -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %a = constant 20 -> i64; %b = constant 22 -> i64; @@ -192,12 +192,12 @@ const BLOCK_CALLABLE_PROGRAM: &str = r#" stage @test fn @ladd(i64, i64) -> i64; stage @test fn @main() -> i64; -specialize @test fn @ladd(i64, i64) -> i64 ^body(%x: i64, %y: i64) { +specialize @test fn @ladd(i64, i64) -> i64 block ^body(%x: i64, %y: i64) { %s = add %x, %y -> i64; ret %s; } -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %a = constant 40 -> i64; %b = constant 2 -> i64; @@ -239,7 +239,7 @@ specialize @test fn @g(i64) -> i64 digraph ^g0(%x: i64) { yield %d; } -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %a = constant 3 -> i64; %r = call.named @g(%a) -> i64; @@ -379,14 +379,14 @@ fn run_scf(pipeline: &Pipeline, function: &str, args: &[i64]) -> Result i64; -specialize @test fn @abs(i64) -> i64 { +specialize @test fn @abs(i64) -> i64 cfg { ^entry(%x: i64) { %zero = constant 0 -> i64; %is_neg = lt %x, %zero -> i64; - %result = if %is_neg then ^then() { + %result = if %is_neg then block ^then() { %negated = neg %x -> i64; yield %negated; - } else ^else() { + } else block ^else() { yield %x; } -> i64; ret %result; @@ -410,11 +410,11 @@ fn scf_for_loop_carries_yielded_values() { r#" stage @test fn @sum_below(i64) -> i64; -specialize @test fn @sum_below(i64) -> i64 { +specialize @test fn @sum_below(i64) -> i64 cfg { ^entry(%n: i64) { %zero = constant 0 -> i64; %one = constant 1 -> i64; - %sum = for %zero in %zero..%n step %one iter_args(%zero) do ^body(%i: i64, %acc: i64) { + %sum = for %zero in %zero..%n step %one iter_args(%zero) do block ^body(%i: i64, %acc: i64) { %next = add %acc, %i -> i64; yield %next; } -> i64; @@ -446,13 +446,13 @@ fn return_bubbles_through_scf_frames_to_call_frame() { stage @test fn @clamp0(i64) -> i64; stage @test fn @twice(i64) -> i64; -specialize @test fn @clamp0(i64) -> i64 { +specialize @test fn @clamp0(i64) -> i64 cfg { ^entry(%x: i64) { %zero = constant 0 -> i64; %is_neg = lt %x, %zero -> i64; - %kept = if %is_neg then ^then() { + %kept = if %is_neg then block ^then() { ret %zero; - } else ^else() { + } else block ^else() { yield %x; } -> i64; %one = constant 1 -> i64; @@ -461,7 +461,7 @@ specialize @test fn @clamp0(i64) -> i64 { } } -specialize @test fn @twice(i64) -> i64 { +specialize @test fn @twice(i64) -> i64 cfg { ^entry(%x: i64) { %a = call.named @clamp0(%x) -> i64; %b = call.named @clamp0(%x) -> i64; @@ -697,7 +697,7 @@ specialize @test fn @usq(i64, i64) -> i64 ungraph ^u0(%x: i64, %y: i64) { %t = mul %s, %s -> i64; } -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %a = constant 2 -> i64; %b = constant 3 -> i64; @@ -907,7 +907,7 @@ stage @test fn @same(i64) -> i64; stage @test fn @diff(i64) -> i64; stage @test fn @caller(i64) -> i64; -specialize @test fn @same(i64) -> i64 { +specialize @test fn @same(i64) -> i64 cfg { ^entry(%c: i64) { cond_br %c then=^t() else=^f(); } @@ -921,7 +921,7 @@ specialize @test fn @same(i64) -> i64 { } } -specialize @test fn @diff(i64) -> i64 { +specialize @test fn @diff(i64) -> i64 cfg { ^entry(%c: i64) { cond_br %c then=^t() else=^f(); } @@ -935,7 +935,7 @@ specialize @test fn @diff(i64) -> i64 { } } -specialize @test fn @caller(i64) -> i64 { +specialize @test fn @caller(i64) -> i64 cfg { ^entry(%c: i64) { %r = call.named @same(%c) -> i64; %one = constant 1 -> i64; @@ -1106,7 +1106,7 @@ stage @test fn @inc(i64) -> i64; stage @test fn @gcall(i64) -> i64; stage @test fn @main() -> i64; -specialize @test fn @inc(i64) -> i64 { +specialize @test fn @inc(i64) -> i64 cfg { ^entry(%v: i64) { %one = constant 1 -> i64; %s = add %v, %one -> i64; @@ -1120,7 +1120,7 @@ specialize @test fn @gcall(i64) -> i64 digraph ^g0(%x: i64) { yield %b; } -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %c = constant 5 -> i64; %r = call.named @gcall(%c) -> i64; @@ -1202,7 +1202,7 @@ specialize @test fn @gdouble(i64) -> i64 digraph ^g0(%x: i64) { yield %s; } -specialize @test fn @twocalls() -> i64 { +specialize @test fn @twocalls() -> i64 cfg { ^entry() { %a = constant 1 -> i64; %b = constant 2 -> i64; @@ -1312,7 +1312,7 @@ specialize @test fn @gpair(i64) -> i64 digraph ^g0(%x: i64) { yield %a, %b; } -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %c = constant 3 -> i64; %p, %q = call.named @gpair(%c) -> i64, i64; @@ -1363,7 +1363,7 @@ specialize @test fn @g2(i64) -> i64 digraph ^g0(%x: i64, %y: i64) { yield %s; } -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %c = constant 3 -> i64; %r = call.named @g2(%c) -> i64; diff --git a/tests/roundtrip/arith.rs b/tests/roundtrip/arith.rs index 1172dd8c5f..7a91b2ca94 100644 --- a/tests/roundtrip/arith.rs +++ b/tests/roundtrip/arith.rs @@ -160,7 +160,7 @@ fn test_composes_with_constant_and_control_flow() { fn test_arithmetic_function_roundtrip_print_parse_print() { let input = r#" stage @arith fn @compose(i64, i64, f64, f64) -> i64; -specialize @arith fn @compose(i64, i64, f64, f64) -> i64 { +specialize @arith fn @compose(i64, i64, f64, f64) -> i64 cfg { ^entry(%a: i64, %b: i64, %x: f64, %y: f64) { %sum = add %a, %b -> i64; %diff = sub %sum, %b -> i64; diff --git a/tests/roundtrip/bitwise.rs b/tests/roundtrip/bitwise.rs index cd7a6fe632..146cb04e44 100644 --- a/tests/roundtrip/bitwise.rs +++ b/tests/roundtrip/bitwise.rs @@ -145,7 +145,7 @@ fn test_composes_with_constant_and_control_flow() { fn test_bitwise_function_roundtrip_print_parse_print() { let input = r#" stage @bitwise fn @compose(i64, i64, u32, u32) -> i64; -specialize @bitwise fn @compose(i64, i64, u32, u32) -> i64 { +specialize @bitwise fn @compose(i64, i64, u32, u32) -> i64 cfg { ^entry(%a: i64, %b: i64, %x: u32, %y: u32) { %and = and %a, %b -> i64; %or = or %and, %b -> i64; diff --git a/tests/roundtrip/body_kinds.rs b/tests/roundtrip/body_kinds.rs new file mode 100644 index 0000000000..78aa1f6975 --- /dev/null +++ b/tests/roundtrip/body_kinds.rs @@ -0,0 +1,114 @@ +//! Round-trip coverage for the four body-kind discriminators in one pipeline. +//! +//! Every Kirin body kind is spelled with an explicit textual discriminator in +//! the default whole-field `{body}` position: +//! +//! ```text +//! fn @f(..) -> T cfg { ^entry(..) { .. } } // CFG: keyword, then member blocks (untagged) +//! fn @f(..) -> T block ^body(..) { .. } // Block +//! fn @f(..) -> T digraph ^g0(..) { .. } // DiGraph +//! fn @f(..) -> T ungraph ^u0(..) { .. } // UnGraph +//! ``` +//! +//! `GraphFunctionLanguage` is the only test language with a callable variant +//! for all four, so it is where the full parse → emit → pretty-print → reparse +//! pipeline can be exercised on a single program. + +use kirin_test_languages::GraphFunctionLanguage; +use kirin_test_utils::roundtrip; + +/// One pipeline, four callables, one discriminator each — plus a CFG-bodied +/// `@main` whose `graph_eval` statement carries a *nested* DiGraph, so the +/// nested-body path is tagged and round-tripped too. +const ALL_BODY_KINDS: &str = r#" +stage @test fn @by_cfg(i64, i64) -> i64; +stage @test fn @by_block(i64, i64) -> i64; +stage @test fn @by_digraph(i64, i64) -> i64; +stage @test fn @by_ungraph(i64, i64) -> i64; +stage @test fn @main(i64, i64) -> i64; + +specialize @test fn @by_cfg(i64, i64) -> i64 cfg { + ^entry(%x: i64, %y: i64) { + %s = add %x, %y -> i64; + ret %s; + } + ^unused(%z: i64) { + ret %z; + } +} + +specialize @test fn @by_block(i64, i64) -> i64 block ^body(%x: i64, %y: i64) { + %s = sub %x, %y -> i64; + ret %s; +} + +specialize @test fn @by_digraph(i64, i64) -> i64 digraph ^g0(%x: i64, %y: i64) { + %s = mul %x, %y -> i64; + yield %s; +} + +specialize @test fn @by_ungraph(i64, i64) -> i64 ungraph ^u0(%x: i64, %y: i64) { + %s = add %x, %y -> i64; + %t = mul %s, %s -> i64; +} + +specialize @test fn @main(i64, i64) -> i64 cfg { + ^entry(%x: i64, %y: i64) { + %a = call.named @by_cfg(%x, %y) -> i64; + %b = call.named @by_block(%x, %y) -> i64; + %c = call.named @by_digraph(%x, %y) -> i64; + %d = call.named @by_ungraph(%x, %y) -> i64; + %e = graph_eval %a, %b digraph ^g1(%p: i64, %q: i64) { + %n = add %p, %q -> i64; + yield %n; + } -> i64; + %f = add %c, %d -> i64; + %r = add %e, %f -> i64; + ret %r; + } +} +"#; + +/// parse → emit → pretty-print → reparse → pretty-print, comparing both +/// renders. A missing or spurious discriminator on any of the four kinds +/// breaks either the first parse or the reparse. +#[test] +fn test_all_body_kinds_pipeline_roundtrip() { + roundtrip::assert_pipeline_roundtrip::(ALL_BODY_KINDS); +} + +/// The printed form of each body kind carries its discriminator, and a CFG's +/// member blocks stay untagged. +#[test] +fn test_printed_body_kinds_carry_their_discriminators() { + use kirin::prelude::*; + + let mut pipeline: Pipeline> = Pipeline::new(); + pipeline + .parse(ALL_BODY_KINDS) + .expect("pipeline parse should succeed"); + let printed = pipeline.sprint(); + + for expected in [ + "@by_cfg (i64, i64) -> i64 cfg {", + "@by_block (i64, i64) -> i64 block ^body(%x: i64, %y: i64) {", + "@by_digraph (i64, i64) -> i64 digraph ^g0(%x: i64, %y: i64) {", + "@by_ungraph (i64, i64) -> i64 ungraph ^u0(%x: i64, %y: i64) {", + ] { + assert!( + printed.contains(expected), + "expected {expected:?} in printed pipeline:\n{printed}" + ); + } + + // A CFG's member blocks are untagged — `cfg` names the body kind once for + // the whole container. + assert!( + printed.contains("^entry(%x: i64, %y: i64) {"), + "CFG member blocks should print untagged:\n{printed}" + ); + assert!( + !printed.contains("block ^entry"), + "CFG member blocks must not be tagged:\n{printed}" + ); +} diff --git a/tests/roundtrip/cf.rs b/tests/roundtrip/cf.rs index 1725233114..83a86fe7b9 100644 --- a/tests/roundtrip/cf.rs +++ b/tests/roundtrip/cf.rs @@ -6,7 +6,7 @@ fn test_branch_roundtrip() { let input = r#" stage @test fn @main(i64) -> i64; -specialize @test fn @main(i64) -> i64 { +specialize @test fn @main(i64) -> i64 cfg { ^entry(%x: i64) { br ^exit(%x); } @@ -23,7 +23,7 @@ fn test_conditional_branch_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { cond_br %cond then=^then(%x) else=^else(%x); } @@ -44,7 +44,7 @@ fn test_branch_with_multiple_args_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %y: i64) { br ^target(%x, %y); } @@ -62,7 +62,7 @@ fn test_diamond_control_flow_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { cond_br %cond then=^left(%x) else=^right(%x); } diff --git a/tests/roundtrip/composable_existing_dialects.rs b/tests/roundtrip/composable_existing_dialects.rs index 663da5a628..4c0c14efc5 100644 --- a/tests/roundtrip/composable_existing_dialects.rs +++ b/tests/roundtrip/composable_existing_dialects.rs @@ -53,16 +53,16 @@ fn test_composed_source_language_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { %doubled = add %x, %x -> i64; - %if_result = if %cond then ^then() { + %if_result = if %cond then block ^then() { yield %doubled; - } else ^else() { + } else block ^else() { yield %x; } -> i64; %captured = constant 41 -> i64; - %closure = lambda @adder captures(%captured) { + %closure = lambda @adder captures(%captured) cfg { ^bb0(%arg: i64) { %sum = add %captured, %arg -> i64; ret %sum; @@ -82,7 +82,7 @@ fn test_wrapped_constant_roundtrip() { let input = r#" stage @test fn @main() -> i64; -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %lhs = constant 20 -> i64; %rhs = constant 22 -> i64; diff --git a/tests/roundtrip/composite.rs b/tests/roundtrip/composite.rs index 4eb48ad629..027b385b66 100644 --- a/tests/roundtrip/composite.rs +++ b/tests/roundtrip/composite.rs @@ -157,7 +157,7 @@ fn test_roundtrip_return() { fn test_roundtrip_function() { let mut stage: BuilderStageInfo = BuilderStageInfo::default(); - let input = r#"%f = function { + let input = r#"%f = function cfg { ^entry(%x: f64) { %y = add %x, %x -> f64; %z = constant 42 -> f64; @@ -204,7 +204,7 @@ fn test_roundtrip_function() { fn test_roundtrip_function_multiple_blocks() { let mut stage: BuilderStageInfo = BuilderStageInfo::default(); - let input = r#"%f = function { + let input = r#"%f = function cfg { ^entry(%x: f64) { %y = add %x, %x -> f64; return %y; diff --git a/tests/roundtrip/constant.rs b/tests/roundtrip/constant.rs index 81cc670f01..8992c39e22 100644 --- a/tests/roundtrip/constant.rs +++ b/tests/roundtrip/constant.rs @@ -33,7 +33,7 @@ fn test_constant_i64() { let input = r#" stage @test fn @main() -> i64; -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %x = constant 42 -> i64; ret %x; @@ -48,7 +48,7 @@ fn test_constant_f64() { let input = r#" stage @test fn @main() -> f64; -specialize @test fn @main() -> f64 { +specialize @test fn @main() -> f64 cfg { ^entry() { %x = constant 3.14 -> f64; ret %x; @@ -63,7 +63,7 @@ fn test_constant_with_arithmetic() { let input = r#" stage @test fn @main() -> i64; -specialize @test fn @main() -> i64 { +specialize @test fn @main() -> i64 cfg { ^entry() { %a = constant 10 -> i64; %b = constant 20 -> i64; diff --git a/tests/roundtrip/function.rs b/tests/roundtrip/function.rs index 1901170a3f..eaeea7cee9 100644 --- a/tests/roundtrip/function.rs +++ b/tests/roundtrip/function.rs @@ -105,13 +105,13 @@ fn test_lowered_function_roundtrip_print_parse_print() { stage @A fn @main(i32) -> i32; stage @A fn @closure(i32, i32) -> i32; -specialize @A fn @closure(i32, i32) -> i32 { +specialize @A fn @closure(i32, i32) -> i32 cfg { ^bb0(%capt0: i32, %arg0: i32) { ret %arg0; } } -specialize @A fn @main(i32) -> i32 { +specialize @A fn @main(i32) -> i32 cfg { ^bb0(%x: i32) { %f = bind @closure captures(%x) -> i32; %r_call = call.named @closure(%x, %x) -> i32; @@ -140,7 +140,7 @@ enum LambdaLanguage { #[test] fn test_lambda_parse_roundtrip() { roundtrip::assert_statement_roundtrip::( - "%f = lambda @closure captures(%x, %y) { } -> i32", + "%f = lambda @closure captures(%x, %y) cfg { } -> i32", &[("x", SimpleType::I32), ("y", SimpleType::I32)], ); } @@ -148,7 +148,7 @@ fn test_lambda_parse_roundtrip() { #[test] fn test_lambda_parse_roundtrip_single_capture() { roundtrip::assert_statement_roundtrip::( - "%f = lambda @closure captures(%x) { } -> i32", + "%f = lambda @closure captures(%x) cfg { } -> i32", &[("x", SimpleType::I32)], ); } @@ -165,7 +165,7 @@ fn test_specialize_without_stage_auto_creates() { .new(); // No `stage` declaration -- specialize auto-creates the staged function - let input = "specialize @A fn @foo(i32) -> i32 { ^bb0(%x: i32) { ret %x; } }"; + let input = "specialize @A fn @foo(i32) -> i32 cfg { ^bb0(%x: i32) { ret %x; } }"; let functions = pipeline .parse(input) .expect("should parse without stage declaration"); @@ -184,7 +184,7 @@ fn test_specialize_without_stage_roundtrip() { let input = r#" stage @A fn @foo(i32) -> i32; -specialize @A fn @foo(i32) -> i32 { ^bb0(%x: i32) { ret %x; } } +specialize @A fn @foo(i32) -> i32 cfg { ^bb0(%x: i32) { ret %x; } } "#; pipeline.parse(input).expect("should parse"); @@ -213,7 +213,7 @@ fn test_split_sig_pipeline_multiple_params() { let input = r#" stage @A fn @main(i32, i64) -> i32; -specialize @A fn @main(i32, i64) -> i32 { +specialize @A fn @main(i32, i64) -> i32 cfg { ^bb0(%x: i32, %y: i64) { ret %x; } @@ -227,7 +227,7 @@ fn test_split_sig_pipeline_single_param() { let input = r#" stage @A fn @main(i32) -> i32; -specialize @A fn @main(i32) -> i32 { +specialize @A fn @main(i32) -> i32 cfg { ^bb0(%x: i32) { ret %x; } @@ -242,7 +242,7 @@ fn test_split_sig_pipeline_many_params() { let input = r#" stage @A fn @compute(i32, i64, f32) -> f64; -specialize @A fn @compute(i32, i64, f32) -> f64 { +specialize @A fn @compute(i32, i64, f32) -> f64 cfg { ^bb0(%x: i32, %y: i64, %z: f32) { ret %x; } diff --git a/tests/roundtrip/main.rs b/tests/roundtrip/main.rs index 07a9557208..a987184ac5 100644 --- a/tests/roundtrip/main.rs +++ b/tests/roundtrip/main.rs @@ -1,5 +1,6 @@ mod arith; mod bitwise; +mod body_kinds; mod cf; mod cmp; mod composable_existing_dialects; diff --git a/tests/roundtrip/namespace.rs b/tests/roundtrip/namespace.rs index 249eaab716..447d834d31 100644 --- a/tests/roundtrip/namespace.rs +++ b/tests/roundtrip/namespace.rs @@ -11,7 +11,7 @@ fn test_namespace_pipeline_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%a: i64, %b: i64) { %sum = arith.add %a, %b -> i64; %diff = arith.sub %sum, %b -> i64; @@ -79,7 +79,7 @@ fn test_bare_pipeline_roundtrip() { let input = r#" stage @test fn @compose(i64, i64) -> i64; -specialize @test fn @compose(i64, i64) -> i64 { +specialize @test fn @compose(i64, i64) -> i64 cfg { ^entry(%a: i64, %b: i64) { %sum = add %a, %b -> i64; %diff = sub %sum, %b -> i64; diff --git a/tests/roundtrip/scf.rs b/tests/roundtrip/scf.rs index 93252f0483..a4a9d1398e 100644 --- a/tests/roundtrip/scf.rs +++ b/tests/roundtrip/scf.rs @@ -27,12 +27,12 @@ fn test_if_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { %doubled = add %x, %x -> i64; - %r = if %cond then ^then() { + %r = if %cond then block ^then() { %r2 = add %doubled, %doubled -> i64; - } else ^else() { + } else block ^else() { %r3 = sub %doubled, %doubled -> i64; } -> i64; } @@ -46,11 +46,11 @@ fn test_yield_in_if_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { - %result = if %cond then ^then() { + %result = if %cond then block ^then() { yield %x; - } else ^else() { + } else block ^else() { yield %x; } -> i64; } @@ -64,10 +64,10 @@ fn test_for_with_iter_args_roundtrip() { let input = r#" stage @test fn @main(i64, i64, i64) -> i64; -specialize @test fn @main(i64, i64, i64) -> i64 { +specialize @test fn @main(i64, i64, i64) -> i64 cfg { ^entry(%lo: i64, %hi: i64, %s: i64) { %init = add %lo, %lo -> i64; - %sum = for %lo in %lo..%hi step %s iter_args(%init) do ^body(%i: i64, %acc: i64) { + %sum = for %lo in %lo..%hi step %s iter_args(%init) do block ^body(%i: i64, %acc: i64) { %next = add %acc, %i -> i64; yield %next; } -> i64; @@ -83,9 +83,9 @@ fn test_for_no_iter_args_roundtrip() { let input = r#" stage @test fn @main(i64, i64, i64) -> i64; -specialize @test fn @main(i64, i64, i64) -> i64 { +specialize @test fn @main(i64, i64, i64) -> i64 cfg { ^entry(%lo: i64, %hi: i64, %s: i64) { - %r = for %lo in %lo..%hi step %s iter_args() do ^body(%i: i64) { + %r = for %lo in %lo..%hi step %s iter_args() do block ^body(%i: i64) { yield %i; } -> i64; ret %r; @@ -100,11 +100,11 @@ fn test_void_if_roundtrip() { let input = r#" stage @test fn @main(i64) -> i64; -specialize @test fn @main(i64) -> i64 { +specialize @test fn @main(i64) -> i64 cfg { ^entry(%cond: i64) { - if %cond then ^then() { + if %cond then block ^then() { yield; - } else ^else() { + } else block ^else() { yield; }; } @@ -118,11 +118,11 @@ fn test_multi_result_if_roundtrip() { let input = r#" stage @test fn @main(i64, i64) -> i64; -specialize @test fn @main(i64, i64) -> i64 { +specialize @test fn @main(i64, i64) -> i64 cfg { ^entry(%x: i64, %cond: i64) { - %a, %b = if %cond then ^then() { + %a, %b = if %cond then block ^then() { yield %x, %x; - } else ^else() { + } else block ^else() { yield %x, %x; } -> i64, i64; } @@ -136,10 +136,10 @@ fn test_multi_accumulator_for_roundtrip() { let input = r#" stage @test fn @main(i64, i64, i64, i64) -> i64; -specialize @test fn @main(i64, i64, i64, i64) -> i64 { +specialize @test fn @main(i64, i64, i64, i64) -> i64 cfg { ^entry(%lo: i64, %hi: i64, %s: i64, %init2: i64) { %init1 = add %lo, %lo -> i64; - %r1, %r2 = for %lo in %lo..%hi step %s iter_args(%init1, %init2) do ^body(%i: i64, %acc1: i64, %acc2: i64) { + %r1, %r2 = for %lo in %lo..%hi step %s iter_args(%init1, %init2) do block ^body(%i: i64, %acc1: i64, %acc2: i64) { %next1 = add %acc1, %i -> i64; %next2 = add %acc2, %i -> i64; yield %next1, %next2; diff --git a/tests/roundtrip/tuple.rs b/tests/roundtrip/tuple.rs index b0a2148e68..6940815bb6 100644 --- a/tests/roundtrip/tuple.rs +++ b/tests/roundtrip/tuple.rs @@ -61,7 +61,7 @@ fn test_new_tuple_pipeline_roundtrip() { let input = r#" stage @test fn @main(i32, i32) -> i32; -specialize @test fn @main(i32, i32) -> i32 { +specialize @test fn @main(i32, i32) -> i32 cfg { ^entry(%x: i32, %y: i32) { %t = new_tuple(%x, %y) -> i32; ret %t; @@ -76,7 +76,7 @@ fn test_unpack_pipeline_roundtrip() { let input = r#" stage @test fn @main(i32) -> i32; -specialize @test fn @main(i32) -> i32 { +specialize @test fn @main(i32) -> i32 cfg { ^entry(%t: i32) { %a, %b = unpack %t -> i32, i32; ret %a; @@ -91,7 +91,7 @@ fn test_new_tuple_then_unpack_pipeline_roundtrip() { let input = r#" stage @test fn @main(i32, i32) -> i32; -specialize @test fn @main(i32, i32) -> i32 { +specialize @test fn @main(i32, i32) -> i32 cfg { ^entry(%x: i32, %y: i32) { %t = new_tuple(%x, %y) -> i32; %a, %b = unpack %t -> i32, i32;