Skip to main content

vyre_libs/parsing/c/parse/
gnu_builtins.rs

1use crate::parsing::c::parse::vast_kinds::{
2    C_AST_KIND_BUILTIN_CHOOSE_EXPR, C_AST_KIND_BUILTIN_EXPECT_EXPR,
3    C_AST_KIND_BUILTIN_OBJECT_SIZE_EXPR, C_AST_KIND_BUILTIN_OFFSETOF_EXPR,
4    C_AST_KIND_BUILTIN_PREFETCH_EXPR, C_AST_KIND_BUILTIN_UNREACHABLE_STMT,
5};
6use crate::region::wrap_anonymous;
7use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
8
9/// Compatibility opcode for front-end streams that tag `__builtin_expect`.
10pub const GNU_BUILTIN_EXPECT_OPCODE: u32 = 0x4558_5043;
11/// Compatibility opcode for front-end streams that tag `__builtin_offsetof`.
12pub const GNU_BUILTIN_OFFSETOF_OPCODE: u32 = 0x4F46_5354;
13/// Compatibility opcode for front-end streams that tag `__builtin_object_size`.
14pub const GNU_BUILTIN_OBJECT_SIZE_OPCODE: u32 = 0x4F42_4A53;
15/// Compatibility opcode for front-end streams that tag `__builtin_prefetch`.
16pub const GNU_BUILTIN_PREFETCH_OPCODE: u32 = 0x5052_4546;
17/// Compatibility opcode for front-end streams that tag `__builtin_unreachable`.
18pub const GNU_BUILTIN_UNREACHABLE_OPCODE: u32 = 0x554E_5243;
19/// Reserved opcode prefix for unsupported GNU builtin front-end tags.
20pub const GNU_BUILTIN_RESERVED_PREFIX: u32 = 0x474E_5500;
21
22/// Fail-loud GNU builtin classifier error.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct GnuBuiltinError {
25    /// Identifier byte length at the failure site.
26    pub len: usize,
27    /// Actionable diagnostic.
28    pub message: &'static str,
29}
30
31impl core::fmt::Display for GnuBuiltinError {
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        write!(f, "{} for {} bytes", self.message, self.len)
34    }
35}
36
37impl std::error::Error for GnuBuiltinError {}
38
39/// Classify GNU builtin identifier bytes into parser-local VAST kinds.
40///
41/// Ordinary identifiers return `Ok(None)`. Unknown `__builtin_*` names return
42/// an error because silently treating compiler intrinsics as ordinary calls
43/// loses semantics needed by the C frontend.
44///
45/// # Errors
46///
47/// Returns an actionable error for unsupported GNU builtin names.
48pub fn try_classify_gnu_builtin_name(name: &[u8]) -> Result<Option<u32>, GnuBuiltinError> {
49    if let Some(kind) = super::gnu_builtin_catalog::classify_gnu_builtin_name(name) {
50        return Ok(Some(kind));
51    }
52    if name.starts_with(b"__builtin_") {
53        return Err(GnuBuiltinError {
54            len: name.len(),
55            message: "Fix: add explicit GNU builtin semantics before accepting this intrinsic",
56        });
57    }
58    Ok(None)
59}
60
61/// Multiplicative seed for the GPU `__has_builtin` perfect-hash table.
62pub const GPU_BUILTIN_HASH_TABLE_SEED: u32 = 0x27b4;
63/// Entry count for the GPU `__has_builtin` perfect-hash table.
64pub const GPU_BUILTIN_HASH_TABLE_SIZE: usize = 5003;
65
66/// Return the canonical GPU perfect-hash table for `__has_builtin` lookup.
67#[must_use]
68pub fn gpu_builtin_hash_table_words() -> Vec<u32> {
69    let mut table = vec![0u32; GPU_BUILTIN_HASH_TABLE_SIZE];
70    for entry in super::gnu_builtin_catalog::GNU_BUILTIN_NAME_KINDS {
71        let slot = gpu_builtin_hash_slot(entry.hash);
72        assert_eq!(
73            table[slot], 0,
74            "Fix: GPU builtin hash table seed must keep catalog hashes collision-free"
75        );
76        table[slot] = entry.hash;
77    }
78    table
79}
80
81fn gpu_builtin_hash_slot(hash: u32) -> usize {
82    (hash.wrapping_mul(GPU_BUILTIN_HASH_TABLE_SEED) % GPU_BUILTIN_HASH_TABLE_SIZE as u32) as usize
83}
84
85/// GNU builtin front-end normalization pass.
86///
87/// The pass preserves already-classified VAST builtin kinds and maps legacy
88/// front-end builtin opcodes onto the same stable kind IDs. Reserved GNU
89/// builtin opcodes trap instead of passing through as ordinary calls.
90#[must_use]
91pub fn c11_gnu_builtins_pass(
92    ast_opcodes: &str,
93    out_ast_opcodes: &str,
94    num_ast_nodes: Expr,
95) -> Program {
96    let t = Expr::InvocationId { axis: 0 };
97
98    let loop_body = vec![
99        Node::let_bind("opcode", Expr::load(ast_opcodes, t.clone())),
100        Node::let_bind("normalized", Expr::var("opcode")),
101        Node::if_then(
102            Expr::eq(Expr::var("opcode"), Expr::u32(GNU_BUILTIN_EXPECT_OPCODE)),
103            vec![Node::assign(
104                "normalized",
105                Expr::u32(C_AST_KIND_BUILTIN_EXPECT_EXPR),
106            )],
107        ),
108        Node::if_then(
109            Expr::eq(Expr::var("opcode"), Expr::u32(GNU_BUILTIN_OFFSETOF_OPCODE)),
110            vec![Node::assign(
111                "normalized",
112                Expr::u32(C_AST_KIND_BUILTIN_OFFSETOF_EXPR),
113            )],
114        ),
115        Node::if_then(
116            Expr::eq(
117                Expr::var("opcode"),
118                Expr::u32(GNU_BUILTIN_OBJECT_SIZE_OPCODE),
119            ),
120            vec![Node::assign(
121                "normalized",
122                Expr::u32(C_AST_KIND_BUILTIN_OBJECT_SIZE_EXPR),
123            )],
124        ),
125        Node::if_then(
126            Expr::eq(Expr::var("opcode"), Expr::u32(GNU_BUILTIN_PREFETCH_OPCODE)),
127            vec![Node::assign(
128                "normalized",
129                Expr::u32(C_AST_KIND_BUILTIN_PREFETCH_EXPR),
130            )],
131        ),
132        Node::if_then(
133            Expr::eq(
134                Expr::var("opcode"),
135                Expr::u32(GNU_BUILTIN_UNREACHABLE_OPCODE),
136            ),
137            vec![Node::assign(
138                "normalized",
139                Expr::u32(C_AST_KIND_BUILTIN_UNREACHABLE_STMT),
140            )],
141        ),
142        Node::if_then(
143            Expr::eq(
144                Expr::bitand(Expr::var("opcode"), Expr::u32(0xFFFF_FF00)),
145                Expr::u32(GNU_BUILTIN_RESERVED_PREFIX),
146            ),
147            vec![Node::trap(
148                Expr::var("opcode"),
149                "unsupported-gnu-builtin-opcode",
150            )],
151        ),
152        Node::store(out_ast_opcodes, t.clone(), Expr::var("normalized")),
153    ];
154
155    let ast_count = match &num_ast_nodes {
156        Expr::LitU32(n) => *n,
157        _ => 1,
158    };
159    Program::wrapped(
160        vec![
161            BufferDecl::storage(ast_opcodes, 0, BufferAccess::ReadOnly, DataType::U32)
162                .with_count(ast_count),
163            BufferDecl::storage(out_ast_opcodes, 1, BufferAccess::ReadWrite, DataType::U32)
164                .with_count(ast_count),
165        ],
166        [256, 1, 1],
167        vec![wrap_anonymous(
168            "vyre-libs::parsing::c11_gnu_builtins_pass",
169            vec![Node::if_then(Expr::lt(t.clone(), num_ast_nodes), loop_body)],
170        )],
171    )
172    .with_entry_op_id("vyre-libs::parsing::c11_gnu_builtins_pass")
173    .with_non_composable_with_self(true)
174}
175
176inventory::submit! {
177    vyre_foundation::operation::OperationRegistration {
178        semantic_version: 1,
179        signature: None,
180        tier: vyre_foundation::operation::OperationTier::Library,
181        laws: &[],
182        tolerance: vyre_foundation::operation::TolerancePolicy::EXACT,
183        id: "vyre-libs::parsing::c11_gnu_builtins_pass",
184        build: Some(|| c11_gnu_builtins_pass("ast", "out_ast", Expr::u32(4))),
185        test_inputs: Some(|| {
186            let ast = [
187                0x11u32,
188                GNU_BUILTIN_EXPECT_OPCODE,
189                GNU_BUILTIN_OBJECT_SIZE_OPCODE,
190                C_AST_KIND_BUILTIN_CHOOSE_EXPR,
191            ];
192            let ast_bytes = vyre_primitives::wire::pack_u32_slice(&ast);
193            vec![vec![ast_bytes, vec![0u8; 4 * 4]]]
194        }),
195        expected_output: Some(|| {
196            let out = [
197                0x11u32,
198                C_AST_KIND_BUILTIN_EXPECT_EXPR,
199                C_AST_KIND_BUILTIN_OBJECT_SIZE_EXPR,
200                C_AST_KIND_BUILTIN_CHOOSE_EXPR,
201            ];
202            let out_bytes = vyre_primitives::wire::pack_u32_slice(&out);
203            vec![vec![out_bytes]]
204        }),
205        category: Some("parsing"),
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use crate::parsing::c::parse::vast_kinds::{
213        C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR, C_AST_KIND_BUILTIN_BPF_CORE_INTRIN_EXPR,
214        C_AST_KIND_BUILTIN_FRAME_INTRIN_EXPR, C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR,
215        C_AST_KIND_BUILTIN_VA_INTRIN_EXPR,
216    };
217
218    #[test]
219    fn classifier_accepts_common_real_header_gnu_builtins() {
220        let cases: &[(&[u8], u32)] = &[
221            (b"__builtin_memchr", C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR),
222            (b"__builtin_strnlen", C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR),
223            (
224                b"__builtin___memcpy_chk",
225                C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR,
226            ),
227            (
228                b"__builtin___strcpy_chk",
229                C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR,
230            ),
231            (b"__builtin_ms_va_start", C_AST_KIND_BUILTIN_VA_INTRIN_EXPR),
232            (b"__builtin_next_arg", C_AST_KIND_BUILTIN_VA_INTRIN_EXPR),
233            (
234                b"__builtin_frob_return_addr",
235                C_AST_KIND_BUILTIN_FRAME_INTRIN_EXPR,
236            ),
237            (
238                b"__builtin_unwind_init",
239                C_AST_KIND_BUILTIN_FRAME_INTRIN_EXPR,
240            ),
241            (
242                b"__builtin_speculation_safe_value",
243                C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR,
244            ),
245            (
246                b"__builtin_is_aligned",
247                C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR,
248            ),
249            (b"__builtin_align_up", C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR),
250            (
251                b"__builtin_align_down",
252                C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR,
253            ),
254            (
255                b"__builtin_preserve_access_index",
256                C_AST_KIND_BUILTIN_BPF_CORE_INTRIN_EXPR,
257            ),
258            (
259                b"__builtin_btf_type_id",
260                C_AST_KIND_BUILTIN_BPF_CORE_INTRIN_EXPR,
261            ),
262        ];
263
264        for (name, expected) in cases {
265            assert_eq!(
266                try_classify_gnu_builtin_name(name).unwrap(),
267                Some(*expected),
268                "{}",
269                String::from_utf8_lossy(name)
270            );
271        }
272    }
273
274    #[test]
275    fn classifier_still_rejects_unknown_builtin_names() {
276        let error = try_classify_gnu_builtin_name(b"__builtin_vyre_unknown")
277            .expect_err("unknown compiler builtins must not become ordinary calls");
278        assert_eq!(error.len, b"__builtin_vyre_unknown".len());
279    }
280
281    #[test]
282    fn gpu_hash_table_is_exact_for_catalog_hashes() {
283        let table = gpu_builtin_hash_table_words();
284        assert!(
285            table.len() == GPU_BUILTIN_HASH_TABLE_SIZE,
286            "Fix: GPU __has_builtin table size must match its shader slot mask"
287        );
288        for entry in super::super::gnu_builtin_catalog::GNU_BUILTIN_NAME_KINDS {
289            assert_eq!(table[gpu_builtin_hash_slot(entry.hash)], entry.hash);
290        }
291    }
292}