Skip to main content

rucc_session/
lib.rs

1//! The `Session`: the options, the interner and the diagnostic sink that every stage of a
2//! single compilation is handed.
3//!
4//! Design: `spec/03-architecture.md` and `spec/04-driver-and-cli.md`. Layer rank 4, see
5//! `spec/18-package-layout.md`.
6//!
7//! Everything below the driver reaches the outside world through this type and not through
8//! `std::fs`, `std::env` or `println!`. That is the whole reason the compiler can be used as
9//! a library and tested without spawning a process, and it is enforced by the layer rule
10//! rather than by discipline.
11//!
12//! # Status
13//!
14//! Options, optimisation levels, emit kinds, diagnostic counting, the source map every span
15//! is resolved against, the file system the compiler reads through, the include search path
16//! and the headers the compiler itself ships are real. The parallel job model is still a
17//! placeholder.
18//!
19//! This crate is tier 3 in `spec/18-package-layout.md` section 18.5: its Rust API is
20//! explicitly unstable and will change without a major version bump.
21
22#![doc(html_root_url = "https://docs.rs/rucc-session/0.10.4")]
23
24mod fs;
25pub mod runtime;
26
27pub use crate::fs::{Dir, FileSystem, Found, IncludeForm, MemoryFileSystem, SearchPath, path_key};
28
29use std::fmt;
30use std::str::FromStr;
31
32use rucc_base::Interner;
33use rucc_diag::{Diagnostic, Severity, SourceMap};
34use rucc_target::{TargetInfo, Triple};
35
36/// An optimisation level.
37///
38/// `spec/16-performance.md` section 16.4 gives each level a throughput budget and a code
39/// quality budget, and the levels exist to make that tradeoff explicit rather than to be a
40/// dial. There is no `-O4`, because a level nobody can state the contract for is a level
41/// nobody can test.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
43pub enum OptLevel {
44    /// `-O0`. Compile as fast as possible and keep every variable inspectable.
45    #[default]
46    O0,
47    /// `-O1`. The cheap wins, at roughly the cost of `-O0`.
48    O1,
49    /// `-O2`. The full pipeline. This is the level the code quality claim is about.
50    O2,
51    /// `-O3`. `-O2` plus the transformations that trade size for speed.
52    O3,
53    /// `-Os`. Optimise for size, at roughly `-O2` compile time.
54    Os,
55    /// `-Oz`. Optimise for size, aggressively.
56    Oz,
57}
58
59impl OptLevel {
60    /// The flag that selects this level.
61    pub const fn as_flag(self) -> &'static str {
62        match self {
63            OptLevel::O0 => "-O0",
64            OptLevel::O1 => "-O1",
65            OptLevel::O2 => "-O2",
66            OptLevel::O3 => "-O3",
67            OptLevel::Os => "-Os",
68            OptLevel::Oz => "-Oz",
69        }
70    }
71
72    /// Whether this level optimises for size rather than speed.
73    pub const fn is_size(self) -> bool {
74        matches!(self, OptLevel::Os | OptLevel::Oz)
75    }
76
77    /// Whether the middle end runs at all.
78    pub const fn runs_optimizer(self) -> bool {
79        !matches!(self, OptLevel::O0)
80    }
81}
82
83impl fmt::Display for OptLevel {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.write_str(self.as_flag())
86    }
87}
88
89impl FromStr for OptLevel {
90    type Err = ();
91
92    /// Parses the part after `-O`, so `""` is `-O` which GCC treats as `-O1`.
93    fn from_str(s: &str) -> Result<Self, ()> {
94        Ok(match s {
95            "0" => OptLevel::O0,
96            "" | "1" => OptLevel::O1,
97            "2" => OptLevel::O2,
98            // GCC accepts `-O4` and above and treats them as `-O3`. Build systems in the
99            // wild do pass them, so matching that is cheaper than being right.
100            "3" | "4" | "5" | "6" | "7" | "8" | "9" => OptLevel::O3,
101            "s" => OptLevel::Os,
102            "z" => OptLevel::Oz,
103            _ => return Err(()),
104        })
105    }
106}
107
108/// How much of the memory safety monitor is on, from `-fsafety=`.
109///
110/// Design: `spec/safe-memory/15-integration.md` section 15.4. One flag rather than a plane at a
111/// time, because the tiers of `spec/safe-memory/02-threat-model.md` are the product and the
112/// modifiers are how somebody who has read that document departs from one.
113///
114/// The tiers agree about which accesses are checked and disagree about what happens when a check
115/// says no and about how much of the boundary is covered. That is why they are one value here and
116/// not three booleans: a build asks for a tier, and everything else follows from it.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
118pub enum Safety {
119    /// `-fsafety=off`. No checks and no runtime. The default, and what every existing build gets.
120    #[default]
121    Off,
122    /// `-fsafety=detect`. Tier D: report and carry on, for a test run or a fuzzer.
123    Detect,
124    /// `-fsafety=enforce`. Tier E: report and stop, for a program that faces the network.
125    Enforce,
126    /// `-fsafety=kernel`. Tier K: what a kernel can afford, with the allocator and the libc
127    /// wrappers taken out because a kernel has neither.
128    Kernel,
129}
130
131impl Safety {
132    /// The spelling this tier is asked for by, without the flag in front of it.
133    pub const fn as_str(self) -> &'static str {
134        match self {
135            Safety::Off => "off",
136            Safety::Detect => "detect",
137            Safety::Enforce => "enforce",
138            Safety::Kernel => "kernel",
139        }
140    }
141
142    /// Whether checks are inserted at all.
143    ///
144    /// The three tiers that are not `off` all insert the same checks at this milestone. What
145    /// separates them is the reporter and the boundary, which are milestones S2 and S3 in
146    /// `spec/safe-memory/16-milestones.md`.
147    pub const fn instruments(self) -> bool {
148        !matches!(self, Safety::Off)
149    }
150}
151
152impl fmt::Display for Safety {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        f.write_str(self.as_str())
155    }
156}
157
158impl FromStr for Safety {
159    type Err = ();
160
161    /// Parses the part after `-fsafety=`.
162    fn from_str(s: &str) -> Result<Self, ()> {
163        Ok(match s {
164            "off" => Safety::Off,
165            "detect" => Safety::Detect,
166            "enforce" => Safety::Enforce,
167            "kernel" => Safety::Kernel,
168            _ => return Err(()),
169        })
170    }
171}
172
173/// How far a name reaches outside a shared library when nothing in the source said.
174///
175/// `-fvisibility=`, which is written on every cmake project that cares about its exports and is
176/// the way a library ships a small documented interface instead of every name it happens to
177/// define. The attribute in the source wins wherever one was written, which is what makes the
178/// flag a default rather than an override and what lets `-fvisibility=hidden` be put on a whole
179/// tree and the dozen exported names marked one at a time.
180///
181/// Three answers to four spellings. `internal` is `hidden` plus a promise about never taking the
182/// address across a component boundary, and nothing here derives anything from that promise, so
183/// what it gets is the same symbol with a weaker claim on it.
184#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
185pub enum Visibility {
186    /// `-fvisibility=default`. Exported and interposable, which is what a name gets when the flag
187    /// is not written at all and what gcc does by default too.
188    #[default]
189    Default,
190    /// `-fvisibility=hidden` and `-fvisibility=internal`. Not in the dynamic symbol table.
191    Hidden,
192    /// `-fvisibility=protected`. In the dynamic symbol table, and a reference from inside the
193    /// library binds to the definition inside it.
194    Protected,
195}
196
197impl Visibility {
198    /// The spelling this is asked for by, without the flag in front of it.
199    ///
200    /// One spelling each, so `internal` is not here: it is a way of asking for `hidden` rather
201    /// than an answer of its own.
202    pub const fn as_str(self) -> &'static str {
203        match self {
204            Visibility::Default => "default",
205            Visibility::Hidden => "hidden",
206            Visibility::Protected => "protected",
207        }
208    }
209}
210
211impl fmt::Display for Visibility {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        f.write_str(self.as_str())
214    }
215}
216
217impl FromStr for Visibility {
218    type Err = ();
219
220    /// Parses the part after `-fvisibility=`.
221    fn from_str(s: &str) -> Result<Self, ()> {
222        Ok(match s {
223            "default" => Visibility::Default,
224            "hidden" | "internal" => Visibility::Hidden,
225            "protected" => Visibility::Protected,
226            _ => return Err(()),
227        })
228    }
229}
230
231/// Which of the two position independent questions the output is answering.
232///
233/// Everything this compiler writes is position independent, so this is not about whether there are
234/// absolute addresses in the text. It is about whether the link that reads the object is one that
235/// puts every name in the same program. An executable is such a link and a shared library is not,
236/// and the difference decides how a name is reached: from the instruction pointer where the
237/// distance is a number the linker has, and out of the global offset table where it is not.
238///
239/// The expensive answer is the one that has to be asked for, which is gcc's arrangement and is why
240/// `-fPIC` is on the compile line of every library and nowhere else. A name is only reached the
241/// expensive way when it is one another object may define or replace, so `-fPIC -fvisibility=hidden`
242/// costs no more than an executable does.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
244pub enum Pic {
245    /// `-fPIE`, `-fpie` and nothing at all. The link puts every name in one program, so a name this
246    /// file defines is at a distance from the instruction asking, and a name it declares ends up at
247    /// one too, because the linker answers a reference to a variable defined in a library by making
248    /// room for it here and copying it. That is what a distribution's default build is.
249    #[default]
250    Executable,
251    /// `-fPIC` and `-fpic`. The output may end up in a shared library, where a name the file
252    /// exports is one something loaded earlier may define too, and where a name defined elsewhere
253    /// is not copied in. Both are reached through the global offset table.
254    Library,
255}
256
257impl Pic {
258    /// The spelling this is asked for by, which is the one gcc's manual leads with.
259    pub const fn as_str(self) -> &'static str {
260        match self {
261            Pic::Executable => "-fPIE",
262            Pic::Library => "-fPIC",
263        }
264    }
265}
266
267impl fmt::Display for Pic {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        f.write_str(self.as_str())
270    }
271}
272
273/// What the compiler should produce.
274///
275/// The intermediate forms are not a debugging convenience bolted on later. Every one of them
276/// is a documented textual form that round-trips, which is what makes the per-stage testing
277/// in `spec/15-testing.md` section 15.2 possible.
278#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
279// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
280// match that needs to change, in this workspace and in anyone else's code. That is
281// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
282// target is a data change: the compiler tells you every place the data is read.
283pub enum EmitKind {
284    /// A linked executable. The default.
285    #[default]
286    Executable,
287    /// An object file, `-c`.
288    Object,
289    /// Assembly text, `-S`.
290    Asm,
291    /// Preprocessed source, `-E`.
292    Preprocessed,
293    /// The typed AST, `--emit=tast`.
294    Tast,
295    /// The IR, `--emit=ir`.
296    Ir,
297    /// The machine IR after register allocation, `--emit=mir-final`.
298    MirFinal,
299    /// The safety summary, `--emit=safety-summary`.
300    ///
301    /// Not an intermediate form of the program the way the three above are. It is the answer to
302    /// "what does this build's guarantee actually rest on", which
303    /// `spec/safe-memory/07-check-elimination.md` section 7.8 asks for and
304    /// `spec/safe-memory/10-boundaries.md` section 10.2 says why.
305    SafetySummary,
306    /// How the bytes of the translation unit's records fall into granules,
307    /// `--emit=type-granules`.
308    ///
309    /// Not an intermediate form either. It is the measurement
310    /// `spec/safe-memory/17-open-questions.md` question 6 asks for, which decides whether the
311    /// type plane fits inside Tier D's memory budget, and it needs nothing past the type
312    /// checker because it is a question about layouts rather than about code.
313    TypeGranules,
314}
315
316impl EmitKind {
317    /// The name used by `--emit=` and by `--print-config`.
318    pub const fn as_str(self) -> &'static str {
319        match self {
320            EmitKind::Executable => "exe",
321            EmitKind::Object => "obj",
322            EmitKind::Asm => "asm",
323            EmitKind::Preprocessed => "preprocessed",
324            EmitKind::Tast => "tast",
325            EmitKind::Ir => "ir",
326            EmitKind::MirFinal => "mir-final",
327            EmitKind::SafetySummary => "safety-summary",
328            EmitKind::TypeGranules => "type-granules",
329        }
330    }
331}
332
333impl FromStr for EmitKind {
334    type Err = ();
335
336    fn from_str(s: &str) -> Result<Self, ()> {
337        Ok(match s {
338            "exe" => EmitKind::Executable,
339            "obj" => EmitKind::Object,
340            "asm" => EmitKind::Asm,
341            "preprocessed" => EmitKind::Preprocessed,
342            "tast" => EmitKind::Tast,
343            "ir" => EmitKind::Ir,
344            "mir-final" => EmitKind::MirFinal,
345            "safety-summary" => EmitKind::SafetySummary,
346            "type-granules" => EmitKind::TypeGranules,
347            _ => return Err(()),
348        })
349    }
350}
351
352/// Which C the source is written in.
353///
354/// The GNU variants are the same language with `__STRICT_ANSI__` left undefined, so the
355/// dialect and the extension question are two fields rather than ten variants.
356#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
357pub enum Std {
358    /// `-std=c89`, and `-ansi`.
359    C89,
360    /// `-std=c99`.
361    C99,
362    /// `-std=c11`.
363    C11,
364    /// `-std=c17`, which is C11 with the defect reports applied.
365    C17,
366    /// `-std=c23`. The default, matching current GCC.
367    #[default]
368    C23,
369}
370
371impl Std {
372    /// What `__STDC_VERSION__` says, which C89 does not define at all.
373    pub const fn stdc_version(self) -> Option<&'static str> {
374        match self {
375            Std::C89 => None,
376            Std::C99 => Some("199901L"),
377            Std::C11 => Some("201112L"),
378            Std::C17 => Some("201710L"),
379            Std::C23 => Some("202311L"),
380        }
381    }
382
383    /// The name in `-std=`.
384    pub const fn as_str(self) -> &'static str {
385        match self {
386            Std::C89 => "c89",
387            Std::C99 => "c99",
388            Std::C11 => "c11",
389            Std::C17 => "c17",
390            Std::C23 => "c23",
391        }
392    }
393
394    /// Whether this dialect has `_Atomic`, `_Thread_local` and the rest of C11.
395    pub const fn has_c11(self) -> bool {
396        matches!(self, Std::C11 | Std::C17 | Std::C23)
397    }
398
399    /// Reads a `-std=` argument, and says whether the GNU extensions came with it.
400    ///
401    /// Every alias GCC takes is here, including the `iso9899` spellings and the year based
402    /// ones, because a build system that passes `-std=iso9899:1999` is passing what its
403    /// author tested against and rejecting it helps nobody. An unknown dialect is `None`
404    /// rather than a guess, since guessing means compiling a different language than the one
405    /// asked for.
406    #[must_use]
407    pub fn from_flag(name: &str) -> Option<(Std, bool)> {
408        let gnu = name.starts_with("gnu");
409        let std = match name {
410            "c89" | "c90" | "gnu89" | "gnu90" | "iso9899:1990" | "iso9899:199409" => Std::C89,
411            "c99" | "c9x" | "gnu99" | "gnu9x" | "iso9899:1999" | "iso9899:199x" => Std::C99,
412            "c11" | "c1x" | "gnu11" | "gnu1x" | "iso9899:2011" => Std::C11,
413            "c17" | "c18" | "gnu17" | "gnu18" | "iso9899:2017" | "iso9899:2018" => Std::C17,
414            "c23" | "c2x" | "gnu23" | "gnu2x" => Std::C23,
415            _ => return None,
416        };
417        Some((std, gnu))
418    }
419}
420
421/// The GCC release the compiler claims to be, as `__GNUC__`, `__GNUC_MINOR__` and
422/// `__GNUC_PATCHLEVEL__`.
423///
424/// Design: `spec/04-driver-and-cli.md` section 4.5, which makes this a knob rather than a
425/// constant and says to start conservative and raise it as the matrix in `rucc-gnu` fills in.
426///
427/// The default is seven, which is the lowest claim that gets a modern glibc. glibc gates most
428/// of what it hands a caller on `__GNUC_PREREQ`, so the claim decides which half of
429/// `sys/cdefs.h` we get, and below seven `bits/floatn-common.h` writes `typedef float _Float32;`
430/// over a keyword this compiler already has. Every header that reaches it stops there, which
431/// was most of them: on Ubuntu 24.04's glibc 2.39 the claim of 4.2.1 that stood here before got
432/// 180 of 214 headers through and seven gets 202, and the amalgamated sqlite goes from four
433/// errors to none.
434///
435/// It is still deliberately low. Claiming a version whose promises have not been kept means
436/// being handed syntax the compiler cannot parse, so this moves when there is a measurement
437/// saying it can. Thirteen and sixteen were measured alongside seven and came out identical on
438/// glibc, on the macOS SDK and on sqlite, so the next move up is cheap; it is a separate one
439/// because nothing yet needs it.
440#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
441pub struct GnucVersion {
442    /// `__GNUC__`.
443    pub major: u32,
444    /// `__GNUC_MINOR__`.
445    pub minor: u32,
446    /// `__GNUC_PATCHLEVEL__`.
447    pub patch: u32,
448}
449
450impl Default for GnucVersion {
451    fn default() -> GnucVersion {
452        GnucVersion { major: 7, minor: 0, patch: 0 }
453    }
454}
455
456impl FromStr for GnucVersion {
457    type Err = String;
458
459    /// Reads `-fgnuc-version=`, which is `15`, `15.1` or `15.1.0`.
460    ///
461    /// The short forms are not a convenience, they are what people write. A missing component
462    /// is zero, the same way GCC treats a release with no patchlevel.
463    fn from_str(text: &str) -> Result<GnucVersion, String> {
464        let mut parts = text.split('.');
465        let mut next = |what: &str| -> Result<u32, String> {
466            match parts.next() {
467                None => Ok(0),
468                Some(field) => {
469                    field.parse().map_err(|_| format!("`{text}` has a {what} that is not a number"))
470                }
471            }
472        };
473        let major = next("major")?;
474        let minor = next("minor")?;
475        let patch = next("patchlevel")?;
476        if parts.next().is_some() {
477            return Err(format!("`{text}` has more than three components"));
478        }
479        Ok(GnucVersion { major, minor, patch })
480    }
481}
482
483/// What the `-d` family asks to be dumped alongside, or instead of, the preprocessed output.
484///
485/// Design: `spec/04-driver-and-cli.md` section 4.4.
486///
487/// GCC spells these as letters packed into one flag, so `-dDI` is two of them, and a letter it
488/// does not know is ignored rather than rejected. That last part is deliberate on GCC's side
489/// and worth copying: the family is a debugging aid and a build that passes `-dumpbase` should
490/// not die on the `-d`.
491#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
492pub struct Dumps {
493    /// `-dM`. Print the macros that are defined at the end, and nothing else.
494    pub macros: bool,
495}
496
497impl Dumps {
498    /// The letters GCC's preprocessor takes after `-d`.
499    ///
500    /// `M` is the macros, `D` is the macros in place, `N` is their names only, `I` is the
501    /// `#include` lines and `U` is the macros as they are used. Only `M` does anything so far.
502    const LETTERS: &'static str = "MDNIU";
503
504    /// Whether `arg` is a flag from this family rather than something else beginning with
505    /// `-d`.
506    ///
507    /// The check is here rather than in the driver so that the set of letters and the set of
508    /// flags accepted cannot drift apart. It matters because `-dumpversion` also begins with
509    /// `-d`, and a family that swallowed every such flag would turn a flag we have not written
510    /// into a dump of nothing.
511    #[must_use]
512    pub fn is_family(arg: &str) -> bool {
513        match arg.strip_prefix("-d") {
514            Some("") | None => false,
515            Some(letters) => letters.chars().all(|c| Dumps::LETTERS.contains(c)),
516        }
517    }
518
519    /// Reads the letters after `-d`, ignoring the ones we do not implement yet.
520    pub fn add(&mut self, letters: &str) {
521        for letter in letters.chars() {
522            if letter == 'M' {
523                self.macros = true;
524            }
525        }
526    }
527
528    /// Whether anything at all was asked for.
529    #[must_use]
530    pub const fn any(self) -> bool {
531        self.macros
532    }
533}
534
535/// A file `-imacros` or `-include` named, read before the source file.
536///
537/// Design: `spec/04-driver-and-cli.md` section 4.4.
538///
539/// The flag a build reaches for when a whole tree has to see a definition that is not in any of
540/// its files. The kernel builds every object with `-include` of its own configuration header, and
541/// a configure script that has produced a `config.h` gets it into a third party source tree the
542/// same way, without a patch.
543#[derive(Debug, Clone, PartialEq, Eq)]
544pub struct Preinclude {
545    /// The name as it was written, which is looked for the way a quoted include is looked for.
546    pub name: String,
547    /// Whether only the definitions it makes are wanted, which is what `-imacros` asks for.
548    ///
549    /// The text of an `-imacros` file is read and thrown away, so a header full of declarations
550    /// contributes its macros and nothing else. That is what makes it usable on a file that has
551    /// already been included by the source: the definitions arrive early and the declarations do
552    /// not arrive twice.
553    pub macros_only: bool,
554}
555
556/// What the `-M` family asks for, which is a make rule saying what a source file was built from.
557///
558/// Design: `spec/04-driver-and-cli.md` section 4.4.
559///
560/// This is a compiler flag rather than a separate tool because the answer is the set of files the
561/// preprocessor opened, and nothing outside the preprocessor knows what that was. A build system
562/// that generates its own makefiles asks for it on every compilation, which is why section 4.4
563/// calls the family required rather than convenient.
564#[derive(Debug, Clone, PartialEq, Eq)]
565pub struct Deps {
566    /// Whether a rule is produced at all, which is any of `-M`, `-MM`, `-MD` and `-MMD`.
567    pub emit: bool,
568    /// Whether the rule is produced instead of compiling, which is `-M` and `-MM` and not the
569    /// two that end in `D`.
570    ///
571    /// The split is GCC's and it is about who reads the answer. The two that stop after the rule
572    /// write it to standard output for a person, and the two that do not write it to a file
573    /// beside the object for `make` to include on the next run.
574    pub instead_of_compiling: bool,
575    /// Whether a header found in a system directory is listed, which `-MM` and `-MMD` turn off.
576    ///
577    /// A build that lists them is a build that rebuilds the world when the C library is updated,
578    /// which is either what somebody wanted or the reason they reached for the other spelling.
579    ///
580    /// On unless a flag turned it off, and nothing turns it back on. That is GCC's behaviour and
581    /// not an oversight: `-MM -M` leaves the system headers out, because the flag that asks for
582    /// fewer of them is read as the answer to a question the other one never asked.
583    pub system_headers: bool,
584    /// Where the rule is written, from `-MF`, with `-` meaning standard output.
585    ///
586    /// `None` is the default, which is standard output when the rule replaces the compilation and
587    /// the output file with a `.d` suffix when it does not.
588    pub file: Option<String>,
589    /// What the rule's targets are, from `-MT` and `-MQ`, in the order they were given.
590    ///
591    /// Already escaped, because that is the whole of the difference between the two flags: `-MQ`
592    /// escapes what it is given and `-MT` writes it through untouched. Empty means the target is
593    /// worked out from the output file, which is what a build that passes neither expects.
594    pub targets: Vec<String>,
595    /// Whether every prerequisite except the source gets a target of its own with no recipe,
596    /// from `-MP`.
597    ///
598    /// This is what stops `make` failing outright when a header is deleted. Without it the old
599    /// rule names a file that is gone and no rule makes it, and the build stops on a header that
600    /// nothing needs any more.
601    pub phony: bool,
602}
603
604impl Default for Deps {
605    fn default() -> Deps {
606        Deps {
607            emit: false,
608            instead_of_compiling: false,
609            system_headers: true,
610            file: None,
611            targets: Vec::new(),
612            phony: false,
613        }
614    }
615}
616
617/// Whether `-save-temps` was given and where it puts the files it keeps.
618///
619/// Design: `spec/04-driver-and-cli.md` section 4.10.
620///
621/// The flag is how a build gets at the preprocessed source of the file that failed without running
622/// the compiler a second time under different flags, which is the one way to be sure the text being
623/// read is the text that was compiled. A bug report against a compiler is usually a preprocessed
624/// file and nothing else, and this is where that file comes from.
625#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
626pub enum SaveTemps {
627    /// Not asked for, and nothing is kept.
628    #[default]
629    No,
630    /// Beside the file the compilation produced, which is `-save-temps=obj`.
631    ///
632    /// This is what the bare `-save-temps` does as well. GCC's manual says the bare spelling is
633    /// `-save-temps=cwd`, and gcc 16 does not do that: `-save-temps -c a.c -o out/a.o` leaves
634    /// `out/a.i` and `out/a.s` rather than `a.i` and `a.s`. The measurement is what is followed
635    /// here, because a build that reads the manual and a build that reads the compiler both end up
636    /// looking for the files where the compiler put them.
637    Object,
638    /// In the working directory, which is `-save-temps=cwd`.
639    Cwd,
640}
641
642impl SaveTemps {
643    /// Whether anything is kept at all.
644    #[must_use]
645    pub const fn wanted(self) -> bool {
646        !matches!(self, SaveTemps::No)
647    }
648}
649
650impl FromStr for SaveTemps {
651    type Err = String;
652
653    /// Reads what came after the `=`, which is the only part that varies.
654    ///
655    /// # Errors
656    ///
657    /// Returns the offending word. GCC treats an unknown one as fatal rather than ignoring it,
658    /// which is right: a misspelled keyword here means the files a person went looking for are not
659    /// written and nothing said so.
660    fn from_str(s: &str) -> Result<SaveTemps, String> {
661        match s {
662            "obj" => Ok(SaveTemps::Object),
663            "cwd" => Ok(SaveTemps::Cwd),
664            _ => Err(format!("`{s}` is not a -save-temps option; accepted: cwd, obj")),
665        }
666    }
667}
668
669/// Everything a compilation was asked to do.
670///
671/// Options are a plain value with no interior mutability, so a caller can build one, clone
672/// it, tweak one field and run a second compilation, which is exactly what the differential
673/// testing in `spec/15-testing.md` needs.
674#[derive(Debug, Clone, PartialEq, Eq)]
675#[non_exhaustive]
676pub struct Options {
677    /// The target to generate code for.
678    pub target: Triple,
679    /// The optimisation level.
680    pub opt_level: OptLevel,
681    /// How much of the memory safety monitor is on, from `-fsafety=`.
682    ///
683    /// Off unless it was asked for. A program built without the flag is compiled by exactly the
684    /// pipeline it was compiled by before the monitor existed, which is the only way the feature
685    /// can be developed in the open without every build paying for it.
686    pub safety: Safety,
687    /// What to produce.
688    pub emit: EmitKind,
689    /// Whether to emit debug information.
690    pub debug_info: bool,
691    /// Whether every function keeps a frame pointer, from `-fno-omit-frame-pointer`.
692    ///
693    /// Off by default, which is what gcc does at every level above `-O0` and what leaves the
694    /// register free for the allocator. A profiler that walks the stack by following saved frame
695    /// pointers needs it on, and so does any code a debugger has to unwind without unwind tables.
696    pub frame_pointer: bool,
697    /// Whether the red zone may be used, from `-mno-red-zone` turned around.
698    ///
699    /// The 128 bytes below the stack pointer that the System V psABI promises no signal handler
700    /// will touch, which lets a small leaf function keep its locals without moving the stack
701    /// pointer at all. A kernel turns this off, because an interrupt taken on the kernel stack
702    /// makes the promise false, and every kernel build in the wild passes `-mno-red-zone` for
703    /// exactly that reason. A convention without a red zone ignores this.
704    pub red_zone: bool,
705    /// Whether warnings are errors.
706    pub warnings_are_errors: bool,
707    /// Whether a warning is raised at all, which is `-w` turned around.
708    ///
709    /// A build that passes this has decided it does not want to hear about anything that is not
710    /// fatal, and the flag is dropped at the one place every diagnostic goes through rather than
711    /// tested at each site that raises one. `-w` beats `-Werror` where both are given, because a
712    /// warning that was never raised cannot be promoted.
713    pub warnings: bool,
714    /// How many diagnostics to print before giving up. Past a certain point the output is
715    /// noise from a single earlier mistake, and GCC's default of no limit is not a kindness.
716    pub error_limit: u32,
717    /// The dialect, from `-std=`.
718    pub std: Std,
719    /// Whether the GNU extensions are on, which is `-std=gnu23` rather than `-std=c23`.
720    pub gnu_extensions: bool,
721    /// Whether `-pedantic` was given, which is what turns a use of an extension from silence
722    /// into a diagnostic. It is not the same knob as the dialect: `-std=c17 -pedantic` warns
723    /// about a construct that `-std=c17` alone accepts without a word.
724    pub pedantic: bool,
725    /// Whether `-fpermissive` was given, which turns the rules gcc 14 promoted from errors back
726    /// into warnings.
727    ///
728    /// Six of them, all about code written before the language settled: a declaration with no
729    /// type in it, a call to a function nothing declared, a parameter in an old style definition
730    /// with no type, a pointer made from an integer, a pointer assigned from a pointer to
731    /// something else, and a `return` whose value disagrees with what was promised. The flag says
732    /// nothing about any other diagnostic, and it does not say to compile something different: a
733    /// program it accepts is compiled the way the rule it broke says it means.
734    pub permissive: bool,
735    /// Whether the whole unit is under GNU's reading of `inline` rather than C's, which is
736    /// `-fgnu89-inline`.
737    ///
738    /// Under C's reading a definition every file-scope declaration wrote `inline` for and none
739    /// wrote `extern` for emits nothing, and under GNU's it is the definition alone that decides
740    /// and `extern inline` is the one that emits nothing. The C89 dialects are under GNU's
741    /// whatever this says, since that is where the older reading came from, so this is the flag a
742    /// program written against it reaches for when it is being compiled under a later dialect.
743    pub gnu89_inline: bool,
744    /// What a name that nothing in the source said anything about reaches, from `-fvisibility=`.
745    pub visibility: Visibility,
746    /// Whether the object may end up in a shared library, from `-fPIC` and `-fPIE`.
747    pub pic: Pic,
748    /// Whether a definition in this unit may be replaced at load time by one in another object,
749    /// from `-fsemantic-interposition` and `-fno-semantic-interposition`.
750    ///
751    /// True is the honest answer and is gcc's default, because that is what an exported name in a
752    /// shared library means: the dynamic linker takes the first definition it finds in load order,
753    /// so a function this unit defines and calls may not be the one that runs. Everything the
754    /// optimizer reads off a body has to stop at a name like that.
755    ///
756    /// False is a promise the build makes, and every distribution makes it, because otherwise a
757    /// library cannot inline its own functions into each other. It is a promise rather than a
758    /// deduction: nothing checks it, and a program that then interposes one of those names gets a
759    /// mixture of the two definitions. It says nothing about `-fPIE`, where no name is replaceable
760    /// to begin with, and it says nothing about how an address is reached, which is the separate
761    /// question `-fPIC` decides.
762    pub interposition: bool,
763    /// The GCC release claimed, from `-fgnuc-version=`.
764    pub gnuc: GnucVersion,
765    /// Whether there is a standard library, which is `-ffreestanding` turned around.
766    pub hosted: bool,
767    /// Whether a call to a C library function written under its own plain name may be taken to
768    /// mean that function, which is `-fno-builtin` turned around.
769    ///
770    /// The names are reserved, so `llabs` is the library's `llabs` and the compiler is allowed to
771    /// know what it does. A program that means something else by one of them is the reason the
772    /// flag exists, and `-ffreestanding` turns it off as well, because a freestanding program has
773    /// no C library for the name to be the name of. The `__builtin_` spellings are not affected by
774    /// either, since the prefix is the program saying which function it means.
775    pub builtins: bool,
776    /// The names `-fno-builtin-<name>` took away one at a time, without the prefix.
777    ///
778    /// A build that means its own `memcpy` and the library's everything else writes this rather
779    /// than the whole flag, which is what the kernel does for a handful of names.
780    pub no_builtin: Vec<String>,
781    /// `-D` in command line order. `FOO` means `FOO=1`, as GCC has it.
782    pub defines: Vec<String>,
783    /// `-U` in command line order, applied after the defines because `-U` wins.
784    pub undefines: Vec<String>,
785    /// Where a header is looked for.
786    pub search: SearchPath,
787    /// What `-imacros` and `-include` named, in command line order.
788    pub preincludes: Vec<Preinclude>,
789    /// Whether `-E` writes line markers, which `-P` turns off.
790    pub line_markers: bool,
791    /// What the `-d` family asks for.
792    pub dumps: Dumps,
793    /// What the `-M` family asks for.
794    pub deps: Deps,
795    /// Whether the intermediate files are kept, from `-save-temps`.
796    pub save_temps: SaveTemps,
797    /// Whether each step says how long it took, from `-time`.
798    pub time: bool,
799    /// What `-f<pass>` and `-fno-<pass>` said about an optimizer pass, in the order the command
800    /// line said it, so that the last mention of a pass is the one that decides.
801    ///
802    /// The pipeline the level chose is the starting point and this is what is added to and taken
803    /// away from it. The names are checked against the pass list while the arguments are parsed,
804    /// so anything in here is a pass the compiler has.
805    pub passes: Vec<(String, bool)>,
806    /// What `-fpass-fuel=<pass>=<n>` limited a pass to, by pass name.
807    ///
808    /// A pass with an entry here performs exactly that many transformations and then stops
809    /// transforming, which is what bisects a miscompilation to one rewrite. See section 9.10 of
810    /// `spec/09-optimizer.md`.
811    pub pass_fuel: Vec<(String, u32)>,
812    /// What `-fpass-fuel-global=<n>` limited the whole pipeline to, across every pass.
813    ///
814    /// The outer of the two searches in section 4.5 of `spec/optimizer/04-pass-manager.md`.
815    /// Halving this says which pass holds the bad rewrite, and halving `-fpass-fuel` for that
816    /// pass says which rewrite it is. Where both are given, a pass is stopped by whichever of
817    /// the two is tighter.
818    pub pass_fuel_global: Option<u32>,
819    /// What `-fdisable-<pass>[=<range>]` and `-fenable-<pass>[=<range>]` said, in the order the
820    /// command line said it, with `true` for the enabling half.
821    ///
822    /// A rule covers the functions it names and nothing else, and the last rule that covers a
823    /// function is the one that decides for it, so the order has to survive. This is the second
824    /// half of the bisection interface in section 41.6 of `spec/optimizer/41-correctness.md`:
825    /// `-fpass-fuel` finds the rewrite and this finds the function. The pass names are checked
826    /// against the pass list while the arguments are parsed.
827    pub pass_gates: Vec<(bool, String)>,
828    /// What `-fdump-ir=` asked to see, as it was written, which is `all`, `before-<pass>` or
829    /// `after-<pass>`.
830    pub dump_ir: Vec<String>,
831    /// What `-fopt-info` asked to hear about, as the keywords were written, with the leading
832    /// hyphen taken off, so a bare `-fopt-info` is the empty string in here.
833    ///
834    /// The keywords are `optimized`, `missed`, `note` and `all`, and two flags add up rather than
835    /// the second replacing the first. Checked while the arguments are parsed, so anything in
836    /// here is a spelling the optimizer understands. See section 42.2 of
837    /// `spec/optimizer/42-measurement.md` for why `missed` is the one that earns the feature.
838    pub opt_info: Vec<String>,
839    /// Where `-fopt-info=<file>` sends the remarks, or `None` for standard error.
840    ///
841    /// One file for the whole run rather than one per input, the way GCC does it, and the last
842    /// one on the command line is the one that decides. A harness that wants the remarks kept
843    /// away from the diagnostics gives a file, which is what the corpus in `tamnd/rucc-corpus`
844    /// does with GCC so that a rejection can still be matched against the diagnostic stream.
845    pub opt_info_file: Option<String>,
846    /// Whether the IR verifier runs after every pass that changed anything.
847    ///
848    /// On in a debug build without being asked, since that is where a broken pass should be
849    /// caught. `-Zverify-each` turns it on in a release build, which is what CI wants.
850    pub verify_each: bool,
851    /// Where `-Zrule-coverage=FILE` writes which lowering rules fired, if it was given.
852    ///
853    /// A measurement rather than a thing a build asks for, which is why it is spelled with a `-Z`
854    /// the way an unstable option is everywhere else: it is here for the harness in
855    /// `tamnd/rucc-compat` to union over a corpus and report, and nothing about the code that comes
856    /// out changes when it is on. One file per run of the compiler, holding the whole rule set with
857    /// the rules this run reached marked, whatever the run compiled and however many files it was.
858    pub rule_coverage: Option<String>,
859}
860
861impl Options {
862    /// Default options for `target`.
863    pub fn new(target: Triple) -> Self {
864        Self {
865            target,
866            opt_level: OptLevel::default(),
867            safety: Safety::default(),
868            emit: EmitKind::default(),
869            debug_info: false,
870            frame_pointer: false,
871            red_zone: true,
872            warnings_are_errors: false,
873            warnings: true,
874            error_limit: 20,
875            std: Std::default(),
876            gnu_extensions: true,
877            pedantic: false,
878            permissive: false,
879            gnu89_inline: false,
880            visibility: Visibility::default(),
881            pic: Pic::default(),
882            interposition: true,
883            gnuc: GnucVersion::default(),
884            hosted: true,
885            builtins: true,
886            no_builtin: Vec::new(),
887            defines: Vec::new(),
888            undefines: Vec::new(),
889            search: SearchPath::new(),
890            preincludes: Vec::new(),
891            line_markers: true,
892            dumps: Dumps::default(),
893            deps: Deps::default(),
894            save_temps: SaveTemps::default(),
895            time: false,
896            passes: Vec::new(),
897            pass_fuel: Vec::new(),
898            pass_fuel_global: None,
899            pass_gates: Vec::new(),
900            dump_ir: Vec::new(),
901            opt_info: Vec::new(),
902            opt_info_file: None,
903            verify_each: cfg!(debug_assertions),
904            rule_coverage: None,
905        }
906    }
907}
908
909/// One compilation.
910///
911/// Holds the options, the string interner and the diagnostics raised so far. Passing a
912/// `&mut Session` is how a stage reports a problem, and the return value of a stage says
913/// what it produced, never whether it succeeded: that question is answered by
914/// [`Session::has_errors`].
915#[derive(Debug)]
916pub struct Session {
917    /// What this compilation was asked to do.
918    pub opts: Options,
919    /// Everything known about the target.
920    pub target: TargetInfo,
921    /// The one interner for the compilation.
922    pub interner: Interner,
923    /// Every file read during the compilation, and the flat coordinate space their spans
924    /// live in.
925    ///
926    /// This is on the session rather than passed around separately because a span is only
927    /// meaningful against the map that issued it, and one map per compilation is the rule
928    /// that makes that true by construction.
929    pub sources: SourceMap,
930    diagnostics: Vec<Diagnostic>,
931    error_count: u32,
932    warning_count: u32,
933}
934
935impl Session {
936    /// A session for `opts`.
937    pub fn new(opts: Options) -> Self {
938        let target = TargetInfo::new(opts.target);
939        Self {
940            opts,
941            target,
942            interner: Interner::with_capacity(1024),
943            sources: SourceMap::new(),
944            diagnostics: Vec::new(),
945            error_count: 0,
946            warning_count: 0,
947        }
948    }
949
950    /// Records a diagnostic.
951    ///
952    /// Under `-Werror` a warning is promoted here, once, rather than at every site that
953    /// raises one, and under `-w` it is dropped here for the same reason. A warning that `-w`
954    /// dropped is not counted, so `-w -Werror` compiles rather than failing on a warning
955    /// nobody was going to see.
956    pub fn emit(&mut self, mut diag: Diagnostic) {
957        if !self.opts.warnings && diag.severity == Severity::Warning {
958            return;
959        }
960        if self.opts.warnings_are_errors && diag.severity == Severity::Warning {
961            diag.severity = Severity::Error;
962        }
963        match diag.severity {
964            Severity::Error | Severity::Ice => self.error_count += 1,
965            Severity::Warning => self.warning_count += 1,
966            Severity::Note | Severity::Help => {}
967        }
968        self.diagnostics.push(diag);
969    }
970
971    /// Everything raised so far, in the order it was raised.
972    pub fn diagnostics(&self) -> &[Diagnostic] {
973        &self.diagnostics
974    }
975
976    /// Whether anything fatal has been raised.
977    pub fn has_errors(&self) -> bool {
978        self.error_count > 0
979    }
980
981    /// How many errors have been raised.
982    pub fn error_count(&self) -> u32 {
983        self.error_count
984    }
985
986    /// How many warnings have been raised.
987    pub fn warning_count(&self) -> u32 {
988        self.warning_count
989    }
990
991    /// Whether the error limit has been reached and the caller should stop.
992    pub fn error_limit_reached(&self) -> bool {
993        self.opts.error_limit != 0 && self.error_count >= self.opts.error_limit
994    }
995}
996
997#[cfg(test)]
998mod tests {
999    use super::*;
1000
1001    fn session() -> Session {
1002        Session::new(Options::new("x86_64-unknown-linux-gnu".parse().unwrap()))
1003    }
1004
1005    #[test]
1006    fn a_version_claim_reads_the_way_gcc_prints_one() {
1007        // `gcc -dumpfullversion` gives all three, `gcc -dumpversion` gives one, and both are
1008        // things a script pastes straight into a flag.
1009        let all = |v: &str| v.parse::<GnucVersion>().unwrap();
1010        assert_eq!(all("15.1.0"), GnucVersion { major: 15, minor: 1, patch: 0 });
1011        assert_eq!(all("15"), GnucVersion { major: 15, minor: 0, patch: 0 });
1012        assert_eq!(all("4.2"), GnucVersion { major: 4, minor: 2, patch: 0 });
1013        assert!("".parse::<GnucVersion>().is_err());
1014        assert!("15.".parse::<GnucVersion>().is_err(), "a trailing dot is a typo, not a zero");
1015        assert!("1.2.3.4".parse::<GnucVersion>().is_err());
1016    }
1017
1018    #[test]
1019    fn optimisation_levels_parse_the_way_gcc_spells_them() {
1020        assert_eq!("".parse::<OptLevel>().unwrap(), OptLevel::O1);
1021        assert_eq!("0".parse::<OptLevel>().unwrap(), OptLevel::O0);
1022        assert_eq!("2".parse::<OptLevel>().unwrap(), OptLevel::O2);
1023        assert_eq!("9".parse::<OptLevel>().unwrap(), OptLevel::O3);
1024        assert_eq!("s".parse::<OptLevel>().unwrap(), OptLevel::Os);
1025        assert!("q".parse::<OptLevel>().is_err());
1026    }
1027
1028    #[test]
1029    fn only_o0_skips_the_optimizer() {
1030        assert!(!OptLevel::O0.runs_optimizer());
1031        assert!(OptLevel::O1.runs_optimizer());
1032        assert!(OptLevel::Oz.runs_optimizer());
1033    }
1034
1035    #[test]
1036    fn the_safety_tiers_round_trip_and_nothing_else_is_one() {
1037        for tier in [Safety::Off, Safety::Detect, Safety::Enforce, Safety::Kernel] {
1038            assert_eq!(tier.as_str().parse::<Safety>().unwrap(), tier);
1039        }
1040        // `on` is the obvious thing to try and it is not a tier, because which tier somebody
1041        // means by it is the whole question document 02 answers.
1042        assert!("on".parse::<Safety>().is_err());
1043        assert!("".parse::<Safety>().is_err());
1044    }
1045
1046    #[test]
1047    fn the_two_places_the_intermediate_files_can_go_are_the_two_words_that_are_taken() {
1048        assert_eq!("obj".parse::<SaveTemps>().unwrap(), SaveTemps::Object);
1049        assert_eq!("cwd".parse::<SaveTemps>().unwrap(), SaveTemps::Cwd);
1050        // The names of the two flags that mean the same thing as `=obj` are not themselves
1051        // arguments of it, and neither is silence.
1052        assert!("obj,cwd".parse::<SaveTemps>().is_err());
1053        assert!("".parse::<SaveTemps>().is_err());
1054        // Nothing is kept unless something asked, and both of the words that ask do ask.
1055        assert_eq!(SaveTemps::default(), SaveTemps::No);
1056        assert!(!SaveTemps::No.wanted());
1057        assert!(SaveTemps::Object.wanted());
1058        assert!(SaveTemps::Cwd.wanted());
1059    }
1060
1061    #[test]
1062    fn a_build_that_did_not_ask_for_the_monitor_does_not_get_it() {
1063        assert_eq!(Safety::default(), Safety::Off);
1064        assert!(!Safety::Off.instruments());
1065        assert!(Safety::Detect.instruments());
1066        assert!(Safety::Enforce.instruments());
1067        assert!(Safety::Kernel.instruments());
1068    }
1069
1070    #[test]
1071    fn emit_kinds_round_trip_through_their_names() {
1072        for k in [
1073            EmitKind::Executable,
1074            EmitKind::Object,
1075            EmitKind::Asm,
1076            EmitKind::Preprocessed,
1077            EmitKind::Tast,
1078            EmitKind::Ir,
1079            EmitKind::MirFinal,
1080        ] {
1081            assert_eq!(k.as_str().parse::<EmitKind>().unwrap(), k);
1082        }
1083    }
1084
1085    #[test]
1086    fn errors_are_counted_and_warnings_are_not() {
1087        let mut s = session();
1088        s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
1089        s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
1090        assert_eq!(s.error_count(), 1);
1091        assert_eq!(s.warning_count(), 1);
1092        assert!(s.has_errors());
1093        assert_eq!(s.diagnostics().len(), 2);
1094    }
1095
1096    #[test]
1097    fn werror_promotes_once_at_the_sink() {
1098        let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
1099        opts.warnings_are_errors = true;
1100        let mut s = Session::new(opts);
1101        s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
1102        assert_eq!(s.error_count(), 1);
1103        assert_eq!(s.warning_count(), 0);
1104        assert_eq!(s.diagnostics()[0].severity, Severity::Error);
1105    }
1106
1107    #[test]
1108    fn the_error_limit_can_be_switched_off() {
1109        let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
1110        opts.error_limit = 0;
1111        let mut s = Session::new(opts);
1112        for _ in 0..100 {
1113            s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
1114        }
1115        assert!(!s.error_limit_reached());
1116    }
1117
1118    #[test]
1119    fn the_session_carries_the_source_map_spans_are_resolved_against() {
1120        let mut s = session();
1121        let file = s.sources.add("a.c", b"int x;\n".to_vec()).unwrap();
1122        let start = s.sources.file(file).start;
1123        assert_eq!(s.sources.render_position(start + 4), "a.c:1:5");
1124    }
1125
1126    #[test]
1127    fn the_session_carries_the_resolved_target() {
1128        let s = session();
1129        assert_eq!(s.target.pointer_width, 64);
1130        assert!(s.target.char_is_signed);
1131    }
1132}