Skip to main content

rucc_session/
runtime.rs

1//! The headers the compiler ships, and the directory they appear to live in.
2//!
3//! Design: `spec/04-driver-and-cli.md` section 4.4.
4//!
5//! A hosted C implementation is two halves. The library ships `<stdio.h>` and everything that
6//! declares a function you link against. The compiler ships the handful of headers whose
7//! contents are not the library's to know: `<stdarg.h>` is the target's calling convention,
8//! `<limits.h>` and `<float.h>` are the target's types, and `<stddef.h>` is the ABI. No
9//! library can write those, which is why every compiler carries its own copies and why a
10//! compiler that carries none cannot preprocess a program as ordinary as SQLite.
11//!
12//! They are in the binary rather than on disk. A compiler that has to find its own
13//! installation directory before it can preprocess a file is a compiler that stops working
14//! when it is copied somewhere else, and a single static binary that works from anywhere is
15//! worth more here than the ability to edit a header without rebuilding.
16//!
17//! Since they are not on disk they need a name, because the search path is a list of
18//! directories and a diagnostic has to be able to say where a header came from. That name is
19//! [`DIR`], and the angle brackets are the point: no directory a user can create is spelled
20//! that way, so nothing on the real file system can shadow these or be shadowed by them.
21
22use std::path::Path;
23
24use rucc_diag::SourceBytes;
25
26/// The directory the shipped headers appear to be in.
27///
28/// Not a path. It is a name that cannot be one, so that `#include <stdarg.h>` resolving to
29/// `<builtin>/stdarg.h` reads as what it is, and so that a real directory can never collide
30/// with it.
31pub const DIR: &str = "<builtin>";
32
33/// Every shipped header, in the order they are listed here, which is sorted by name.
34///
35/// The text is in the binary. `include_str!` rather than a build script because the set is
36/// small and fixed, and because a build script would put the headers behind a step that has
37/// to run before anything can be read.
38const HEADERS: &[(&str, &str)] = &[
39    ("float.h", include_str!("../runtime/include/float.h")),
40    ("iso646.h", include_str!("../runtime/include/iso646.h")),
41    ("limits.h", include_str!("../runtime/include/limits.h")),
42    ("mm_malloc.h", include_str!("../runtime/include/mm_malloc.h")),
43    ("mmintrin.h", include_str!("../runtime/include/mmintrin.h")),
44    ("stdalign.h", include_str!("../runtime/include/stdalign.h")),
45    ("stdarg.h", include_str!("../runtime/include/stdarg.h")),
46    ("stdatomic.h", include_str!("../runtime/include/stdatomic.h")),
47    ("stdbool.h", include_str!("../runtime/include/stdbool.h")),
48    ("stddef.h", include_str!("../runtime/include/stddef.h")),
49    ("stdint.h", include_str!("../runtime/include/stdint.h")),
50    ("stdnoreturn.h", include_str!("../runtime/include/stdnoreturn.h")),
51];
52
53/// The names of the shipped headers, sorted.
54#[must_use]
55pub fn names() -> Vec<&'static str> {
56    HEADERS.iter().map(|&(name, _)| name).collect()
57}
58
59/// The text of one shipped header, by its name alone.
60#[must_use]
61pub fn header(name: &str) -> Option<&'static str> {
62    HEADERS.iter().find(|&&(have, _)| have == name).map(|&(_, text)| text)
63}
64
65/// Reads a path that an include search produced, when it names a shipped header.
66///
67/// The path is [`DIR`] joined with the header's name, which on Windows means a backslash
68/// between them, so the two halves are compared rather than the string.
69#[must_use]
70pub fn read(path: &Path) -> Option<SourceBytes> {
71    if path.parent() != Some(Path::new(DIR)) {
72        return None;
73    }
74    let name = path.file_name()?.to_str()?;
75    header(name).map(SourceBytes::new)
76}
77
78#[cfg(test)]
79mod tests {
80    use std::path::PathBuf;
81
82    use super::*;
83
84    #[test]
85    fn the_shipped_headers_are_read_by_the_name_the_search_path_builds() {
86        let path = PathBuf::from(DIR).join("stdarg.h");
87        let bytes = read(&path).expect("stdarg.h is shipped");
88        let text = String::from_utf8(bytes.as_ref().to_vec()).expect("utf-8");
89        assert!(text.contains("__builtin_va_list"));
90    }
91
92    #[test]
93    fn nothing_outside_the_builtin_directory_is_answered() {
94        assert!(read(Path::new("/usr/include/stdarg.h")).is_none());
95        assert!(read(Path::new("stdarg.h")).is_none());
96        assert!(read(&PathBuf::from(DIR).join("stdio.h")).is_none());
97        assert!(read(&PathBuf::from(DIR).join("sys").join("stdarg.h")).is_none());
98    }
99
100    #[test]
101    fn the_list_is_sorted_so_that_a_new_header_has_one_place_to_go() {
102        let mut sorted = names();
103        sorted.sort_unstable();
104        assert_eq!(names(), sorted);
105    }
106
107    /// Every header has to be idempotent and has to name itself in its own guard, because a
108    /// program includes `<stddef.h>` forty times and a guard copied from a neighbour is the
109    /// way one of them silently stops working.
110    #[test]
111    fn every_header_guards_itself_under_its_own_name() {
112        for &(name, text) in HEADERS {
113            let guard = format!("__RUCC_{}", name.trim_end_matches(".h").to_uppercase());
114            assert!(text.contains(&guard), "{name} does not mention {guard}");
115        }
116    }
117}