Skip to main content

vyre_libs/parsing/c/parse/
inline_asm.rs

1use crate::parsing::c::atomic_collect::atomic_collect_u32;
2use crate::parsing::c::lex::tokens::{
3    TOK_COLON, TOK_GNU_ASM, TOK_GOTO, TOK_LPAREN, TOK_RPAREN, TOK_STRING, TOK_VOLATILE,
4};
5use vyre_foundation::ir::{Expr, Program};
6
7/// Front-end opcode for a GNU inline-asm AST row.
8pub const GNU_INLINE_ASM_OPCODE: u32 = 0x4153_4D00;
9const OP_ID: &str = "vyre-libs::parsing::c11_gnu_inline_asm_pass";
10
11/// Token-level summary for a GNU inline assembly statement or declaration alias.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct GnuInlineAsmSummary {
14    /// Token index containing `asm`, `__asm`, or `__asm__`.
15    pub asm_token: usize,
16    /// Whether `volatile` / `__volatile__` was present before the operand list.
17    pub is_volatile: bool,
18    /// Whether `goto` was present before the operand list.
19    pub is_goto: bool,
20    /// Token index of the template string.
21    pub template_token: usize,
22    /// One-past-last token index of the asm construct.
23    pub end_token: usize,
24    /// Number of top-level colon separators in the operand list.
25    pub top_level_colons: u32,
26}
27
28/// Fail-loud inline-asm parser error.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct GnuInlineAsmError {
31    /// Token index where parsing failed.
32    pub token_index: usize,
33    /// Actionable diagnostic.
34    pub message: &'static str,
35}
36
37impl core::fmt::Display for GnuInlineAsmError {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        write!(f, "{} at token {}", self.message, self.token_index)
40    }
41}
42
43impl std::error::Error for GnuInlineAsmError {}
44
45/// Parse the token envelope of a GNU inline-asm construct.
46///
47/// The parser validates the GNU shape `asm [volatile] [goto] ( "template" ... )`
48/// without interpreting architecture-specific template text. That keeps asm
49/// payloads opaque for ABI lowering while still giving the C frontend stable
50/// spans and fail-loud malformed-stream behavior.
51///
52/// # Errors
53///
54/// Returns an actionable error for a malformed or truncated asm envelope.
55pub fn try_classify_gnu_inline_asm_tokens(
56    tok_types: &[u32],
57    asm_token: usize,
58) -> Result<GnuInlineAsmSummary, GnuInlineAsmError> {
59    if tok_types.get(asm_token).copied() != Some(TOK_GNU_ASM) {
60        return Err(GnuInlineAsmError {
61            token_index: asm_token,
62            message: "Fix: GNU inline asm parser must start at TOK_GNU_ASM",
63        });
64    }
65
66    let mut cursor = asm_token + 1;
67    let mut is_volatile = false;
68    let mut is_goto = false;
69    while let Some(kind) = tok_types.get(cursor).copied() {
70        match kind {
71            TOK_VOLATILE => {
72                is_volatile = true;
73                cursor += 1;
74            }
75            TOK_GOTO => {
76                is_goto = true;
77                cursor += 1;
78            }
79            _ => break,
80        }
81    }
82
83    if tok_types.get(cursor).copied() != Some(TOK_LPAREN) {
84        return Err(GnuInlineAsmError {
85            token_index: cursor,
86            message: "Fix: GNU inline asm requires an opening parenthesis",
87        });
88    }
89
90    let template_token = cursor + 1;
91    if tok_types.get(template_token).copied() != Some(TOK_STRING) {
92        return Err(GnuInlineAsmError {
93            token_index: template_token,
94            message: "Fix: GNU inline asm requires a string template as the first operand",
95        });
96    }
97
98    let mut depth = 1u32;
99    let mut top_level_colons = 0u32;
100    cursor += 1;
101    while cursor + 1 < tok_types.len() {
102        cursor += 1;
103        match tok_types[cursor] {
104            TOK_LPAREN => depth = depth.saturating_add(1),
105            TOK_RPAREN => {
106                depth = depth.saturating_sub(1);
107                if depth == 0 {
108                    return Ok(GnuInlineAsmSummary {
109                        asm_token,
110                        is_volatile,
111                        is_goto,
112                        template_token,
113                        end_token: cursor + 1,
114                        top_level_colons,
115                    });
116                }
117            }
118            TOK_COLON if depth == 1 => top_level_colons = top_level_colons.saturating_add(1),
119            _ => {}
120        }
121    }
122
123    Err(GnuInlineAsmError {
124        token_index: tok_types.len(),
125        message: "Fix: GNU inline asm operand list is missing its closing parenthesis",
126    })
127}
128
129/// GNU Compiler Extensions: Inline Assembly Parser
130///
131/// GNU-C code often uses `asm volatile(...)` blocks for architecture-specific
132/// hardware control. This module isolates inline assembly tokens and passes
133/// the raw strings to an architecture-specific assembler block during ABI
134/// lowering, preventing the C semantic analyzer from treating assembler text
135/// as ordinary C expressions.
136#[must_use]
137pub fn c11_gnu_inline_asm_pass(
138    ast_opcodes: &str,
139    out_asm_blocks: &str,
140    num_ast_nodes: Expr,
141) -> Program {
142    atomic_collect_u32(
143        OP_ID,
144        ast_opcodes,
145        out_asm_blocks,
146        "out_asm_counts",
147        num_ast_nodes,
148        1,
149        Some("inline-asm-registry-overflow"),
150        |opcode, _t| Expr::eq(opcode, Expr::u32(GNU_INLINE_ASM_OPCODE)),
151        |_t, asm_id| asm_id,
152        |t, _asm_id| t,
153    )
154}
155
156inventory::submit! {
157    vyre_foundation::operation::OperationRegistration {
158        semantic_version: 1,
159        signature: None,
160        tier: vyre_foundation::operation::OperationTier::Library,
161        laws: &[],
162        tolerance: vyre_foundation::operation::TolerancePolicy::EXACT,
163        id: OP_ID,
164        build: Some(|| c11_gnu_inline_asm_pass("ast", "out_asm", Expr::u32(4))),
165        // ast: 4 u32 opcodes including one ASM tag (0x41534D00) at
166        // index 2. out_asm: 4 u32 slots. out_asm_counts: 1 u32 slot
167        // for the atomic counter. The pass writes t=2 into
168        // out_asm[0] and leaves non-ASM slots untouched.
169        test_inputs: Some(|| {
170            let ast = [0u32, 1, GNU_INLINE_ASM_OPCODE, 3];
171            let ast_bytes = vyre_primitives::wire::pack_u32_slice(&ast);
172            vec![vec![ast_bytes, vec![0u8; 4 * 4], vec![0u8; 4]]]
173        }),
174        expected_output: Some(|| {
175            // t=2 sees the ASM tag, atomic_add claims slot 0, and
176            // we store `t=2` into out_asm_blocks[0]. All other
177            // slots stay zero. The counter records the single asm block.
178            let mut out = vec![0u8; 4 * 4];
179            out[0..4].copy_from_slice(&2u32.to_le_bytes());
180            let mut count = vec![0u8; 4];
181            count.copy_from_slice(&1u32.to_le_bytes());
182            vec![vec![out, count]]
183        }),
184        category: Some("parsing"),
185    }
186}