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::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    crate::harness::OpEntry {
178        id: "vyre-libs::parsing::c11_gnu_builtins_pass",
179        build: || c11_gnu_builtins_pass("ast", "out_ast", Expr::u32(4)),
180        test_inputs: Some(|| {
181            let ast = [
182                0x11u32,
183                GNU_BUILTIN_EXPECT_OPCODE,
184                GNU_BUILTIN_OBJECT_SIZE_OPCODE,
185                C_AST_KIND_BUILTIN_CHOOSE_EXPR,
186            ];
187            let ast_bytes = vyre_primitives::wire::pack_u32_slice(&ast);
188            vec![vec![ast_bytes, vec![0u8; 4 * 4]]]
189        }),
190        expected_output: Some(|| {
191            let out = [
192                0x11u32,
193                C_AST_KIND_BUILTIN_EXPECT_EXPR,
194                C_AST_KIND_BUILTIN_OBJECT_SIZE_EXPR,
195                C_AST_KIND_BUILTIN_CHOOSE_EXPR,
196            ];
197            let out_bytes = vyre_primitives::wire::pack_u32_slice(&out);
198            vec![vec![out_bytes]]
199        }),
200        category: Some("parsing"),
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use crate::parsing::c::parse::vast_kinds::{
208        C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR, C_AST_KIND_BUILTIN_BPF_CORE_INTRIN_EXPR,
209        C_AST_KIND_BUILTIN_FRAME_INTRIN_EXPR, C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR,
210        C_AST_KIND_BUILTIN_VA_INTRIN_EXPR,
211    };
212
213    #[test]
214    fn classifier_accepts_common_real_header_gnu_builtins() {
215        let cases: &[(&[u8], u32)] = &[
216            (b"__builtin_memchr", C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR),
217            (b"__builtin_strnlen", C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR),
218            (
219                b"__builtin___memcpy_chk",
220                C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR,
221            ),
222            (
223                b"__builtin___strcpy_chk",
224                C_AST_KIND_BUILTIN_LIBC_INTRIN_EXPR,
225            ),
226            (b"__builtin_ms_va_start", C_AST_KIND_BUILTIN_VA_INTRIN_EXPR),
227            (b"__builtin_next_arg", C_AST_KIND_BUILTIN_VA_INTRIN_EXPR),
228            (
229                b"__builtin_frob_return_addr",
230                C_AST_KIND_BUILTIN_FRAME_INTRIN_EXPR,
231            ),
232            (
233                b"__builtin_unwind_init",
234                C_AST_KIND_BUILTIN_FRAME_INTRIN_EXPR,
235            ),
236            (
237                b"__builtin_speculation_safe_value",
238                C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR,
239            ),
240            (
241                b"__builtin_is_aligned",
242                C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR,
243            ),
244            (b"__builtin_align_up", C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR),
245            (
246                b"__builtin_align_down",
247                C_AST_KIND_BUILTIN_ASSUME_INTRIN_EXPR,
248            ),
249            (
250                b"__builtin_preserve_access_index",
251                C_AST_KIND_BUILTIN_BPF_CORE_INTRIN_EXPR,
252            ),
253            (
254                b"__builtin_btf_type_id",
255                C_AST_KIND_BUILTIN_BPF_CORE_INTRIN_EXPR,
256            ),
257        ];
258
259        for (name, expected) in cases {
260            assert_eq!(
261                try_classify_gnu_builtin_name(name).unwrap(),
262                Some(*expected),
263                "{}",
264                String::from_utf8_lossy(name)
265            );
266        }
267    }
268
269    #[test]
270    fn classifier_still_rejects_unknown_builtin_names() {
271        let error = try_classify_gnu_builtin_name(b"__builtin_vyre_unknown")
272            .expect_err("unknown compiler builtins must not become ordinary calls");
273        assert_eq!(error.len, b"__builtin_vyre_unknown".len());
274    }
275
276    #[test]
277    fn gpu_hash_table_is_exact_for_catalog_hashes() {
278        let table = gpu_builtin_hash_table_words();
279        assert!(
280            table.len() == GPU_BUILTIN_HASH_TABLE_SIZE,
281            "Fix: GPU __has_builtin table size must match its shader slot mask"
282        );
283        for entry in super::super::gnu_builtin_catalog::GNU_BUILTIN_NAME_KINDS {
284            assert_eq!(table[gpu_builtin_hash_slot(entry.hash)], entry.hash);
285        }
286    }
287}