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.17")]
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 functions get a stack protector, which is what the `-fstack-protector` family asks.
232///
233/// A canary is a word the prologue copies into the frame above everything a local can be written
234/// through, and the epilogue compares it against the copy the runtime still holds before it
235/// returns. A write that runs off the end of a local and keeps going passes the canary on its way
236/// to the return address, so a function that returns with the word changed calls
237/// `__stack_chk_fail` instead of returning at all.
238///
239/// Which functions are worth the slot and the comparison is what the three levels disagree about,
240/// and the middle one is the one that matters: every distribution has built its packages with
241/// `-fstack-protector-strong` for a decade, so a compiler that cannot take the flag cannot be the
242/// `CC` of a package build whatever else it can do.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
244pub enum Protector {
245    /// `-fno-stack-protector`, and what a command line that says nothing gets. gcc's own default
246    /// is the same, and it is the distributions rather than the compiler that turn it on.
247    #[default]
248    None,
249    /// `-fstack-protector`. A function with a local array of at least eight bytes, or one whose
250    /// stack grows while it runs.
251    Buffers,
252    /// `-fstack-protector-strong`. Any of those, and any function with a local array at all, a
253    /// local holding one, or a local whose address is taken.
254    Strong,
255    /// `-fstack-protector-all`. Every function that has a frame.
256    All,
257}
258
259/// What overflows rather than being undefined, from `-fwrapv` and its relatives.
260///
261/// C says a signed addition that overflows and a pointer that walks off the end of the object it
262/// points into are both undefined, and an optimizer that believes it reads a great deal into every
263/// loop: that a counter going up one at a time never turns round, that an index widened to an
264/// address may be widened before the arithmetic rather than after, that a bound is reached. These
265/// flags withdraw exactly that. They do not make the program mean something else, they make it mean
266/// less, and the code that asks for them is code that overflows on purpose and wants the answer the
267/// machine gives rather than the answer the standard declines to give.
268///
269/// Two of them because gcc has two, and a build that wants one usually wants the other. Signed
270/// arithmetic and pointer arithmetic are separate assumptions and a kernel turns both off.
271///
272/// `-ftrapv` is the third answer to the first question and is here for that reason. Undefined,
273/// wrapping and stopping are the three things a signed overflow can be, and a command line picks
274/// one of them: the last of `-fwrapv` and `-ftrapv` wins, which is gcc's behaviour and what makes
275/// them one field rather than two that can both be set.
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
277pub struct Wrapping {
278    /// Whether signed arithmetic wraps, from `-fwrapv`.
279    pub signed: bool,
280    /// Whether pointer arithmetic wraps, from `-fwrapv-pointer`.
281    pub pointer: bool,
282    /// Whether a signed overflow stops the program instead, from `-ftrapv`.
283    ///
284    /// Never set at the same time as [`Wrapping::signed`], since a program cannot both wrap and
285    /// stop, and the driver is what keeps that true by clearing each when the other is asked for.
286    pub trap: bool,
287}
288
289impl Wrapping {
290    /// Both of them, which is what `-fno-strict-overflow` asks for.
291    ///
292    /// gcc says so itself: its help text for `-fstrict-overflow` reads "negated as `-fwrapv`
293    /// `-fwrapv-pointer`", so the older flag is a name for the pair rather than a third knob. And
294    /// asking for wrapping is asking for not stopping, so this is the whole answer and not two
295    /// thirds of one.
296    pub const ALL: Self = Self { signed: true, pointer: true, trap: false };
297
298    /// Neither, which is the default and what a command line that says nothing about any of this
299    /// gets.
300    pub const NONE: Self = Self { signed: false, pointer: false, trap: false };
301}
302
303impl Protector {
304    /// The spelling this is asked for by, which is the whole flag rather than a part of one,
305    /// because these are four flags and not one flag with an argument.
306    pub const fn as_str(self) -> &'static str {
307        match self {
308            Protector::None => "-fno-stack-protector",
309            Protector::Buffers => "-fstack-protector",
310            Protector::Strong => "-fstack-protector-strong",
311            Protector::All => "-fstack-protector-all",
312        }
313    }
314}
315
316impl fmt::Display for Protector {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        f.write_str(self.as_str())
319    }
320}
321
322/// Which control flow transfers are checked, which is what `-fcf-protection=` asks.
323///
324/// Two mechanisms and one flag, because the hardware turns them on together and a program built
325/// for one and not the other is a program with a hole in whichever half was left out. The forward
326/// edge is an indirect call or jump, and it is checked by a landing pad at every address one is
327/// allowed to arrive at, so a corrupted function pointer reaches somewhere somebody meant rather
328/// than any byte of the program. The backward edge is a return, and it is checked against a second
329/// copy of the return address the program cannot write to, which needs no instructions at all: the
330/// machine keeps the copy and the loader turns it on.
331///
332/// Which is why the marker matters as much as the code. An object says in a note which halves it
333/// was built for, the linker takes the intersection over every input, and the loader turns on what
334/// survives. One object built without the note is enough to turn the whole program's protection
335/// off, so the note goes in even for a mode that changes no instruction.
336#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
337pub enum Control {
338    /// `-fcf-protection=none` and `-fno-cf-protection`, and what a command line that says nothing
339    /// gets. gcc's own default is the same on the targets this compiler has a back end for.
340    #[default]
341    None,
342    /// `-fcf-protection=branch`. The forward edge alone: a landing pad at every function, and a
343    /// note that asks for the check on indirect transfers and not on returns.
344    Branch,
345    /// `-fcf-protection=return`. The backward edge alone, which is the note and nothing else,
346    /// since the copy of the return address is the machine's own and no instruction maintains it.
347    Return,
348    /// `-fcf-protection=full`, and what the bare `-fcf-protection` means. Both halves.
349    Full,
350    /// `-fcf-protection=check`. Asks that the compilation be checked for compatibility with the
351    /// mode rather than built in it, so nothing is instrumented and no note is written, which is
352    /// exactly what gcc emits for it.
353    Check,
354}
355
356impl Control {
357    /// Whether a landing pad goes at the top of every function.
358    #[must_use]
359    pub const fn branch(self) -> bool {
360        matches!(self, Control::Branch | Control::Full)
361    }
362
363    /// Whether returns are asked to be checked against the machine's own copy.
364    #[must_use]
365    pub const fn ret(self) -> bool {
366        matches!(self, Control::Return | Control::Full)
367    }
368
369    /// Whether anything at all is asked for, which is what decides whether the file says what it
370    /// was built for.
371    ///
372    /// False for the two modes that build nothing. [`Control::None`] asks for nothing and
373    /// [`Control::Check`] asks that the compilation be looked at rather than changed, and gcc
374    /// writes no note for either.
375    #[must_use]
376    pub const fn any(self) -> bool {
377        self.branch() || self.ret()
378    }
379
380    /// What the argument was spelled as, which is the part after the equals sign.
381    pub const fn as_str(self) -> &'static str {
382        match self {
383            Control::None => "none",
384            Control::Branch => "branch",
385            Control::Return => "return",
386            Control::Full => "full",
387            Control::Check => "check",
388        }
389    }
390}
391
392impl fmt::Display for Control {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        f.write_str(self.as_str())
395    }
396}
397
398impl FromStr for Control {
399    type Err = ();
400
401    /// Parses the part after `-fcf-protection=`.
402    fn from_str(s: &str) -> Result<Self, ()> {
403        Ok(match s {
404            "none" => Control::None,
405            "branch" => Control::Branch,
406            "return" => Control::Return,
407            "full" => Control::Full,
408            "check" => Control::Check,
409            _ => return Err(()),
410        })
411    }
412}
413
414/// Where the call `-pg` puts at the top of every function goes, which `-mfentry` chooses.
415///
416/// Two conventions for one job, and the difference is what the hook can see when it runs. See
417/// [`rucc_target::Trace`] for what each of them is and why a kernel needs the earlier one.
418///
419/// A third answer, because a command line that named neither has not asked a question: the
420/// platform's own answer is the one it gets, and that is a fact about the target rather than about
421/// the flags, so it is settled where the target is known and not here.
422#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
423pub enum Hook {
424    /// Whichever the platform puts first, which is what a command line that said neither gets.
425    #[default]
426    Platform,
427    /// `-mfentry`. In front of the prologue, so the return address is the top thing on the stack
428    /// and the arguments are still where the call left them.
429    Early,
430    /// `-mno-fentry`. Once the frame is taken, so the hook can walk back through the frame pointer,
431    /// which is why a function that has this one is given a frame pointer whatever else was said.
432    Late,
433}
434
435impl Hook {
436    /// That answer as it is written on a command line, which is what `--print-config` reports.
437    #[must_use]
438    pub const fn as_str(self) -> &'static str {
439        match self {
440            Hook::Platform => "platform",
441            Hook::Early => "fentry",
442            Hook::Late => "mcount",
443        }
444    }
445
446    /// Whether the call goes in front of the prologue, given what the platform puts first.
447    #[must_use]
448    pub const fn early(self, fentry: bool) -> bool {
449        match self {
450            Hook::Platform => fentry,
451            Hook::Early => true,
452            Hook::Late => false,
453        }
454    }
455}
456
457impl fmt::Display for Hook {
458    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459        f.write_str(self.as_str())
460    }
461}
462
463/// How much room at the top of every function is reserved for somebody to write over later, which
464/// `-fpatchable-function-entry=` asks for.
465///
466/// Room rather than instructions. What goes there is a run of the shortest instruction the machine
467/// has that does nothing, and the point of them is that they are never executed for long: a tracer
468/// or a live patcher overwrites them with a jump or a call once the program is running, and what it
469/// needs from the compiler is a known address, a known number of bytes, and a promise that nothing
470/// in the function jumps into the middle of them.
471///
472/// Two numbers because the room can be on either side of the function's own label, and the two
473/// sides are not the same thing. Room after the label is room inside the function, which is what a
474/// patcher that redirects a call into the function wants. Room in front of the label is outside it,
475/// so what goes there is reached only by something that already knows the address, and a patcher
476/// that wants somewhere to put a whole instruction it can reach from the first one needs it.
477///
478/// The address recorded for the function is the start of the room, which is the front of the part
479/// before the label when there is one and the front of the part after it when there is not.
480#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
481pub struct Patchable {
482    /// How many bytes in total, which is the first number and the one a command line must give.
483    pub total: u32,
484    /// How many of them go in front of the function's own label, which is the second number and is
485    /// zero on a command line that gave one number.
486    pub before: u32,
487}
488
489impl Patchable {
490    /// Whether any room at all was asked for, which is what decides whether a function gets a
491    /// record.
492    ///
493    /// `=0` is a command line that asked for none, and gcc accepts it and writes nothing, so the
494    /// question is about the number rather than about whether the flag was written.
495    #[must_use]
496    pub const fn any(self) -> bool {
497        self.total > 0
498    }
499
500    /// How many bytes go after the function's own label, which is the rest of them.
501    #[must_use]
502    pub const fn after(self) -> u32 {
503        self.total - self.before
504    }
505}
506
507impl FromStr for Patchable {
508    type Err = ();
509
510    /// Parses the part after `-fpatchable-function-entry=`, which is a number or two of them.
511    ///
512    /// A second number larger than the first is refused rather than clamped, because it asks for
513    /// more room in front of the label than there is room at all and there is no reading of that a
514    /// caller meant. So is a third, and so is anything that is not a number, which is what gcc does
515    /// with each of them.
516    fn from_str(s: &str) -> Result<Self, ()> {
517        let (total, before) = match s.split_once(',') {
518            Some((total, before)) => (total, before),
519            None => (s, "0"),
520        };
521        let total: u32 = total.parse().map_err(|_| ())?;
522        let before: u32 = before.parse().map_err(|_| ())?;
523        if before > total {
524            return Err(());
525        }
526        Ok(Patchable { total, before })
527    }
528}
529
530impl fmt::Display for Patchable {
531    /// Written the way it was asked for, which is one number when the second is zero.
532    ///
533    /// Not because the two forms mean different things, they do not, but because that is the form
534    /// a command line reaching for this feature writes and reading back what was written is what
535    /// `--print-config` is for.
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        match self.before {
538            0 => write!(f, "{}", self.total),
539            before => write!(f, "{},{before}", self.total),
540        }
541    }
542}
543
544/// Which of the two position independent questions the output is answering.
545///
546/// Everything this compiler writes is position independent, so this is not about whether there are
547/// absolute addresses in the text. It is about whether the link that reads the object is one that
548/// puts every name in the same program. An executable is such a link and a shared library is not,
549/// and the difference decides how a name is reached: from the instruction pointer where the
550/// distance is a number the linker has, and out of the global offset table where it is not.
551///
552/// The expensive answer is the one that has to be asked for, which is gcc's arrangement and is why
553/// `-fPIC` is on the compile line of every library and nowhere else. A name is only reached the
554/// expensive way when it is one another object may define or replace, so `-fPIC -fvisibility=hidden`
555/// costs no more than an executable does.
556#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
557pub enum Pic {
558    /// `-fPIE`, `-fpie` and nothing at all. The link puts every name in one program, so a name this
559    /// file defines is at a distance from the instruction asking, and a name it declares ends up at
560    /// one too, because the linker answers a reference to a variable defined in a library by making
561    /// room for it here and copying it. That is what a distribution's default build is.
562    #[default]
563    Executable,
564    /// `-fPIC` and `-fpic`. The output may end up in a shared library, where a name the file
565    /// exports is one something loaded earlier may define too, and where a name defined elsewhere
566    /// is not copied in. Both are reached through the global offset table.
567    Library,
568}
569
570impl Pic {
571    /// The spelling this is asked for by, which is the one gcc's manual leads with.
572    pub const fn as_str(self) -> &'static str {
573        match self {
574            Pic::Executable => "-fPIE",
575            Pic::Library => "-fPIC",
576        }
577    }
578}
579
580impl fmt::Display for Pic {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        f.write_str(self.as_str())
583    }
584}
585
586/// What the compiler should produce.
587///
588/// The intermediate forms are not a debugging convenience bolted on later. Every one of them
589/// is a documented textual form that round-trips, which is what makes the per-stage testing
590/// in `spec/15-testing.md` section 15.2 possible.
591#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
592// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
593// match that needs to change, in this workspace and in anyone else's code. That is
594// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
595// target is a data change: the compiler tells you every place the data is read.
596pub enum EmitKind {
597    /// A linked executable. The default.
598    #[default]
599    Executable,
600    /// An object file, `-c`.
601    Object,
602    /// Assembly text, `-S`.
603    Asm,
604    /// Preprocessed source, `-E`.
605    Preprocessed,
606    /// The typed AST, `--emit=tast`.
607    Tast,
608    /// The IR, `--emit=ir`.
609    Ir,
610    /// The machine IR after register allocation, `--emit=mir-final`.
611    MirFinal,
612    /// The safety summary, `--emit=safety-summary`.
613    ///
614    /// Not an intermediate form of the program the way the three above are. It is the answer to
615    /// "what does this build's guarantee actually rest on", which
616    /// `spec/safe-memory/07-check-elimination.md` section 7.8 asks for and
617    /// `spec/safe-memory/10-boundaries.md` section 10.2 says why.
618    SafetySummary,
619    /// How the bytes of the translation unit's records fall into granules,
620    /// `--emit=type-granules`.
621    ///
622    /// Not an intermediate form either. It is the measurement
623    /// `spec/safe-memory/17-open-questions.md` question 6 asks for, which decides whether the
624    /// type plane fits inside Tier D's memory budget, and it needs nothing past the type
625    /// checker because it is a question about layouts rather than about code.
626    TypeGranules,
627}
628
629impl EmitKind {
630    /// The name used by `--emit=` and by `--print-config`.
631    pub const fn as_str(self) -> &'static str {
632        match self {
633            EmitKind::Executable => "exe",
634            EmitKind::Object => "obj",
635            EmitKind::Asm => "asm",
636            EmitKind::Preprocessed => "preprocessed",
637            EmitKind::Tast => "tast",
638            EmitKind::Ir => "ir",
639            EmitKind::MirFinal => "mir-final",
640            EmitKind::SafetySummary => "safety-summary",
641            EmitKind::TypeGranules => "type-granules",
642        }
643    }
644}
645
646impl FromStr for EmitKind {
647    type Err = ();
648
649    fn from_str(s: &str) -> Result<Self, ()> {
650        Ok(match s {
651            "exe" => EmitKind::Executable,
652            "obj" => EmitKind::Object,
653            "asm" => EmitKind::Asm,
654            "preprocessed" => EmitKind::Preprocessed,
655            "tast" => EmitKind::Tast,
656            "ir" => EmitKind::Ir,
657            "mir-final" => EmitKind::MirFinal,
658            "safety-summary" => EmitKind::SafetySummary,
659            "type-granules" => EmitKind::TypeGranules,
660            _ => return Err(()),
661        })
662    }
663}
664
665/// Which C the source is written in.
666///
667/// The GNU variants are the same language with `__STRICT_ANSI__` left undefined, so the
668/// dialect and the extension question are two fields rather than ten variants.
669#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
670pub enum Std {
671    /// `-std=c89`, and `-ansi`.
672    C89,
673    /// `-std=c99`.
674    C99,
675    /// `-std=c11`.
676    C11,
677    /// `-std=c17`, which is C11 with the defect reports applied.
678    C17,
679    /// `-std=c23`. The default, matching current GCC.
680    #[default]
681    C23,
682}
683
684impl Std {
685    /// What `__STDC_VERSION__` says, which C89 does not define at all.
686    pub const fn stdc_version(self) -> Option<&'static str> {
687        match self {
688            Std::C89 => None,
689            Std::C99 => Some("199901L"),
690            Std::C11 => Some("201112L"),
691            Std::C17 => Some("201710L"),
692            Std::C23 => Some("202311L"),
693        }
694    }
695
696    /// The name in `-std=`.
697    pub const fn as_str(self) -> &'static str {
698        match self {
699            Std::C89 => "c89",
700            Std::C99 => "c99",
701            Std::C11 => "c11",
702            Std::C17 => "c17",
703            Std::C23 => "c23",
704        }
705    }
706
707    /// Whether this dialect has `_Atomic`, `_Thread_local` and the rest of C11.
708    pub const fn has_c11(self) -> bool {
709        matches!(self, Std::C11 | Std::C17 | Std::C23)
710    }
711
712    /// Reads a `-std=` argument, and says whether the GNU extensions came with it.
713    ///
714    /// Every alias GCC takes is here, including the `iso9899` spellings and the year based
715    /// ones, because a build system that passes `-std=iso9899:1999` is passing what its
716    /// author tested against and rejecting it helps nobody. An unknown dialect is `None`
717    /// rather than a guess, since guessing means compiling a different language than the one
718    /// asked for.
719    #[must_use]
720    pub fn from_flag(name: &str) -> Option<(Std, bool)> {
721        let gnu = name.starts_with("gnu");
722        let std = match name {
723            "c89" | "c90" | "gnu89" | "gnu90" | "iso9899:1990" | "iso9899:199409" => Std::C89,
724            "c99" | "c9x" | "gnu99" | "gnu9x" | "iso9899:1999" | "iso9899:199x" => Std::C99,
725            "c11" | "c1x" | "gnu11" | "gnu1x" | "iso9899:2011" => Std::C11,
726            "c17" | "c18" | "gnu17" | "gnu18" | "iso9899:2017" | "iso9899:2018" => Std::C17,
727            "c23" | "c2x" | "gnu23" | "gnu2x" => Std::C23,
728            _ => return None,
729        };
730        Some((std, gnu))
731    }
732}
733
734/// The GCC release the compiler claims to be, as `__GNUC__`, `__GNUC_MINOR__` and
735/// `__GNUC_PATCHLEVEL__`.
736///
737/// Design: `spec/04-driver-and-cli.md` section 4.5, which makes this a knob rather than a
738/// constant and says to start conservative and raise it as the matrix in `rucc-gnu` fills in.
739///
740/// The default is seven, which is the lowest claim that gets a modern glibc. glibc gates most
741/// of what it hands a caller on `__GNUC_PREREQ`, so the claim decides which half of
742/// `sys/cdefs.h` we get, and below seven `bits/floatn-common.h` writes `typedef float _Float32;`
743/// over a keyword this compiler already has. Every header that reaches it stops there, which
744/// was most of them: on Ubuntu 24.04's glibc 2.39 the claim of 4.2.1 that stood here before got
745/// 180 of 214 headers through and seven gets 202, and the amalgamated sqlite goes from four
746/// errors to none.
747///
748/// It is still deliberately low. Claiming a version whose promises have not been kept means
749/// being handed syntax the compiler cannot parse, so this moves when there is a measurement
750/// saying it can. Thirteen and sixteen were measured alongside seven and came out identical on
751/// glibc, on the macOS SDK and on sqlite, so the next move up is cheap; it is a separate one
752/// because nothing yet needs it.
753#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
754pub struct GnucVersion {
755    /// `__GNUC__`.
756    pub major: u32,
757    /// `__GNUC_MINOR__`.
758    pub minor: u32,
759    /// `__GNUC_PATCHLEVEL__`.
760    pub patch: u32,
761}
762
763impl Default for GnucVersion {
764    fn default() -> GnucVersion {
765        GnucVersion { major: 7, minor: 0, patch: 0 }
766    }
767}
768
769impl FromStr for GnucVersion {
770    type Err = String;
771
772    /// Reads `-fgnuc-version=`, which is `15`, `15.1` or `15.1.0`.
773    ///
774    /// The short forms are not a convenience, they are what people write. A missing component
775    /// is zero, the same way GCC treats a release with no patchlevel.
776    fn from_str(text: &str) -> Result<GnucVersion, String> {
777        let mut parts = text.split('.');
778        let mut next = |what: &str| -> Result<u32, String> {
779            match parts.next() {
780                None => Ok(0),
781                Some(field) => {
782                    field.parse().map_err(|_| format!("`{text}` has a {what} that is not a number"))
783                }
784            }
785        };
786        let major = next("major")?;
787        let minor = next("minor")?;
788        let patch = next("patchlevel")?;
789        if parts.next().is_some() {
790            return Err(format!("`{text}` has more than three components"));
791        }
792        Ok(GnucVersion { major, minor, patch })
793    }
794}
795
796/// What the `-d` family asks to be dumped alongside, or instead of, the preprocessed output.
797///
798/// Design: `spec/04-driver-and-cli.md` section 4.4.
799///
800/// GCC spells these as letters packed into one flag, so `-dDI` is two of them, and a letter it
801/// does not know is ignored rather than rejected. That last part is deliberate on GCC's side
802/// and worth copying: the family is a debugging aid and a build that passes `-dumpbase` should
803/// not die on the `-d`.
804#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
805pub struct Dumps {
806    /// `-dM`. Print the macros that are defined at the end, and nothing else.
807    pub macros: bool,
808}
809
810impl Dumps {
811    /// The letters GCC's preprocessor takes after `-d`.
812    ///
813    /// `M` is the macros, `D` is the macros in place, `N` is their names only, `I` is the
814    /// `#include` lines and `U` is the macros as they are used. Only `M` does anything so far.
815    const LETTERS: &'static str = "MDNIU";
816
817    /// Whether `arg` is a flag from this family rather than something else beginning with
818    /// `-d`.
819    ///
820    /// The check is here rather than in the driver so that the set of letters and the set of
821    /// flags accepted cannot drift apart. It matters because `-dumpversion` also begins with
822    /// `-d`, and a family that swallowed every such flag would turn a flag we have not written
823    /// into a dump of nothing.
824    #[must_use]
825    pub fn is_family(arg: &str) -> bool {
826        match arg.strip_prefix("-d") {
827            Some("") | None => false,
828            Some(letters) => letters.chars().all(|c| Dumps::LETTERS.contains(c)),
829        }
830    }
831
832    /// Reads the letters after `-d`, ignoring the ones we do not implement yet.
833    pub fn add(&mut self, letters: &str) {
834        for letter in letters.chars() {
835            if letter == 'M' {
836                self.macros = true;
837            }
838        }
839    }
840
841    /// Whether anything at all was asked for.
842    #[must_use]
843    pub const fn any(self) -> bool {
844        self.macros
845    }
846}
847
848/// A file `-imacros` or `-include` named, read before the source file.
849///
850/// Design: `spec/04-driver-and-cli.md` section 4.4.
851///
852/// The flag a build reaches for when a whole tree has to see a definition that is not in any of
853/// its files. The kernel builds every object with `-include` of its own configuration header, and
854/// a configure script that has produced a `config.h` gets it into a third party source tree the
855/// same way, without a patch.
856#[derive(Debug, Clone, PartialEq, Eq)]
857pub struct Preinclude {
858    /// The name as it was written, which is looked for the way a quoted include is looked for.
859    pub name: String,
860    /// Whether only the definitions it makes are wanted, which is what `-imacros` asks for.
861    ///
862    /// The text of an `-imacros` file is read and thrown away, so a header full of declarations
863    /// contributes its macros and nothing else. That is what makes it usable on a file that has
864    /// already been included by the source: the definitions arrive early and the declarations do
865    /// not arrive twice.
866    pub macros_only: bool,
867}
868
869/// What the `-M` family asks for, which is a make rule saying what a source file was built from.
870///
871/// Design: `spec/04-driver-and-cli.md` section 4.4.
872///
873/// This is a compiler flag rather than a separate tool because the answer is the set of files the
874/// preprocessor opened, and nothing outside the preprocessor knows what that was. A build system
875/// that generates its own makefiles asks for it on every compilation, which is why section 4.4
876/// calls the family required rather than convenient.
877#[derive(Debug, Clone, PartialEq, Eq)]
878pub struct Deps {
879    /// Whether a rule is produced at all, which is any of `-M`, `-MM`, `-MD` and `-MMD`.
880    pub emit: bool,
881    /// Whether the rule is produced instead of compiling, which is `-M` and `-MM` and not the
882    /// two that end in `D`.
883    ///
884    /// The split is GCC's and it is about who reads the answer. The two that stop after the rule
885    /// write it to standard output for a person, and the two that do not write it to a file
886    /// beside the object for `make` to include on the next run.
887    pub instead_of_compiling: bool,
888    /// Whether a header found in a system directory is listed, which `-MM` and `-MMD` turn off.
889    ///
890    /// A build that lists them is a build that rebuilds the world when the C library is updated,
891    /// which is either what somebody wanted or the reason they reached for the other spelling.
892    ///
893    /// On unless a flag turned it off, and nothing turns it back on. That is GCC's behaviour and
894    /// not an oversight: `-MM -M` leaves the system headers out, because the flag that asks for
895    /// fewer of them is read as the answer to a question the other one never asked.
896    pub system_headers: bool,
897    /// Where the rule is written, from `-MF`, with `-` meaning standard output.
898    ///
899    /// `None` is the default, which is standard output when the rule replaces the compilation and
900    /// the output file with a `.d` suffix when it does not.
901    pub file: Option<String>,
902    /// What the rule's targets are, from `-MT` and `-MQ`, in the order they were given.
903    ///
904    /// Already escaped, because that is the whole of the difference between the two flags: `-MQ`
905    /// escapes what it is given and `-MT` writes it through untouched. Empty means the target is
906    /// worked out from the output file, which is what a build that passes neither expects.
907    pub targets: Vec<String>,
908    /// Whether every prerequisite except the source gets a target of its own with no recipe,
909    /// from `-MP`.
910    ///
911    /// This is what stops `make` failing outright when a header is deleted. Without it the old
912    /// rule names a file that is gone and no rule makes it, and the build stops on a header that
913    /// nothing needs any more.
914    pub phony: bool,
915}
916
917impl Default for Deps {
918    fn default() -> Deps {
919        Deps {
920            emit: false,
921            instead_of_compiling: false,
922            system_headers: true,
923            file: None,
924            targets: Vec::new(),
925            phony: false,
926        }
927    }
928}
929
930/// Whether `-save-temps` was given and where it puts the files it keeps.
931///
932/// Design: `spec/04-driver-and-cli.md` section 4.10.
933///
934/// The flag is how a build gets at the preprocessed source of the file that failed without running
935/// the compiler a second time under different flags, which is the one way to be sure the text being
936/// read is the text that was compiled. A bug report against a compiler is usually a preprocessed
937/// file and nothing else, and this is where that file comes from.
938#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
939pub enum SaveTemps {
940    /// Not asked for, and nothing is kept.
941    #[default]
942    No,
943    /// Beside the file the compilation produced, which is `-save-temps=obj`.
944    ///
945    /// This is what the bare `-save-temps` does as well. GCC's manual says the bare spelling is
946    /// `-save-temps=cwd`, and gcc 16 does not do that: `-save-temps -c a.c -o out/a.o` leaves
947    /// `out/a.i` and `out/a.s` rather than `a.i` and `a.s`. The measurement is what is followed
948    /// here, because a build that reads the manual and a build that reads the compiler both end up
949    /// looking for the files where the compiler put them.
950    Object,
951    /// In the working directory, which is `-save-temps=cwd`.
952    Cwd,
953}
954
955impl SaveTemps {
956    /// Whether anything is kept at all.
957    #[must_use]
958    pub const fn wanted(self) -> bool {
959        !matches!(self, SaveTemps::No)
960    }
961}
962
963impl FromStr for SaveTemps {
964    type Err = String;
965
966    /// Reads what came after the `=`, which is the only part that varies.
967    ///
968    /// # Errors
969    ///
970    /// Returns the offending word. GCC treats an unknown one as fatal rather than ignoring it,
971    /// which is right: a misspelled keyword here means the files a person went looking for are not
972    /// written and nothing said so.
973    fn from_str(s: &str) -> Result<SaveTemps, String> {
974        match s {
975            "obj" => Ok(SaveTemps::Object),
976            "cwd" => Ok(SaveTemps::Cwd),
977            _ => Err(format!("`{s}` is not a -save-temps option; accepted: cwd, obj")),
978        }
979    }
980}
981
982/// Everything a compilation was asked to do.
983///
984/// Options are a plain value with no interior mutability, so a caller can build one, clone
985/// it, tweak one field and run a second compilation, which is exactly what the differential
986/// testing in `spec/15-testing.md` needs.
987#[derive(Debug, Clone, PartialEq, Eq)]
988#[non_exhaustive]
989pub struct Options {
990    /// The target to generate code for.
991    pub target: Triple,
992    /// The optimisation level.
993    pub opt_level: OptLevel,
994    /// How much of the memory safety monitor is on, from `-fsafety=`.
995    ///
996    /// Off unless it was asked for. A program built without the flag is compiled by exactly the
997    /// pipeline it was compiled by before the monitor existed, which is the only way the feature
998    /// can be developed in the open without every build paying for it.
999    pub safety: Safety,
1000    /// What to produce.
1001    pub emit: EmitKind,
1002    /// Whether to emit debug information.
1003    pub debug_info: bool,
1004    /// Whether every function keeps a frame pointer, from `-fno-omit-frame-pointer`.
1005    ///
1006    /// Off by default, which is what gcc does at every level above `-O0` and what leaves the
1007    /// register free for the allocator. A profiler that walks the stack by following saved frame
1008    /// pointers needs it on, and so does any code a debugger has to unwind without unwind tables.
1009    pub frame_pointer: bool,
1010    /// Whether the red zone may be used, from `-mno-red-zone` turned around.
1011    ///
1012    /// The 128 bytes below the stack pointer that the System V psABI promises no signal handler
1013    /// will touch, which lets a small leaf function keep its locals without moving the stack
1014    /// pointer at all. A kernel turns this off, because an interrupt taken on the kernel stack
1015    /// makes the promise false, and every kernel build in the wild passes `-mno-red-zone` for
1016    /// exactly that reason. A convention without a red zone ignores this.
1017    pub red_zone: bool,
1018    /// Which functions get a stack protector, from the `-fstack-protector` family.
1019    pub protector: Protector,
1020    /// Whether a prologue takes its frame a page at a time, from `-fstack-clash-protection`.
1021    ///
1022    /// An operating system leaves one page unmapped below every stack so that a stack growing
1023    /// into it faults. A function whose frame is larger than that page moves the stack pointer
1024    /// clean over it in one subtraction and can then write below it, into whatever the program
1025    /// mapped next, which is a way of reaching one allocation from another that costs an attacker
1026    /// nothing but a large local array. A prologue that takes the frame a page at a time and
1027    /// writes to each page as it arrives faults on the first one that is not there.
1028    ///
1029    /// Off by default, which is gcc's default. Distributions that build with it build everything
1030    /// with it, because the hole is in whichever function was left out.
1031    pub stack_clash: bool,
1032    /// Which control flow transfers are checked, from `-fcf-protection=`.
1033    ///
1034    /// See [`Control`]. Off by default, which is gcc's default on these targets, and on again in
1035    /// every distribution's global flags for the same reason the stack protector is.
1036    pub control: Control,
1037    /// Whether every function calls a profiler's hook on the way in, from `-pg` and `-p`.
1038    ///
1039    /// A profiler wants a count of which function called which, and the moment a function is
1040    /// entered is the only place a compiler can hand it one. It changes the link as well as the
1041    /// code, since the counts have to be started before `main` and written out after it, and the
1042    /// start file that does that is a different one.
1043    ///
1044    /// A tracer wants the same call for a different reason. The hook is one instruction the kernel
1045    /// can overwrite while the program runs, which is what makes a function traceable without
1046    /// rebuilding it, and it is why Linux is built this way rather than to be profiled.
1047    pub profile: bool,
1048    /// Where that call goes, from `-mfentry` and `-mno-fentry`.
1049    ///
1050    /// See [`Hook`]. Read even on a command line that did not ask for the call, since gcc accepts
1051    /// the flag on its own and does nothing with it.
1052    pub hook: Hook,
1053    /// How much room every function opens with for somebody to write over later, from
1054    /// `-fpatchable-function-entry=`.
1055    ///
1056    /// See [`Patchable`]. A kernel asks for this so that a function can be traced without being
1057    /// rebuilt: the room is a known number of bytes at a known address, and the addresses are
1058    /// collected into a section of their own so that whatever does the patching can find every one
1059    /// of them without reading the symbol table.
1060    pub patchable: Patchable,
1061    /// What happens rather than nothing being defined when arithmetic overflows, from `-fwrapv`,
1062    /// `-fwrapv-pointer`, `-fno-strict-overflow` and `-ftrapv`.
1063    ///
1064    /// See [`Wrapping`]. Nothing wraps and nothing stops by default, which is what C says and what
1065    /// lets the optimizer read a loop counter as a number rather than as a number that may turn
1066    /// round.
1067    pub wrapping: Wrapping,
1068    /// Whether warnings are errors.
1069    pub warnings_are_errors: bool,
1070    /// Whether a warning is raised at all, which is `-w` turned around.
1071    ///
1072    /// A build that passes this has decided it does not want to hear about anything that is not
1073    /// fatal, and the flag is dropped at the one place every diagnostic goes through rather than
1074    /// tested at each site that raises one. `-w` beats `-Werror` where both are given, because a
1075    /// warning that was never raised cannot be promoted.
1076    pub warnings: bool,
1077    /// How many diagnostics to print before giving up. Past a certain point the output is
1078    /// noise from a single earlier mistake, and GCC's default of no limit is not a kindness.
1079    pub error_limit: u32,
1080    /// The dialect, from `-std=`.
1081    pub std: Std,
1082    /// Whether the GNU extensions are on, which is `-std=gnu23` rather than `-std=c23`.
1083    pub gnu_extensions: bool,
1084    /// Whether `-pedantic` was given, which is what turns a use of an extension from silence
1085    /// into a diagnostic. It is not the same knob as the dialect: `-std=c17 -pedantic` warns
1086    /// about a construct that `-std=c17` alone accepts without a word.
1087    pub pedantic: bool,
1088    /// Whether `-fpermissive` was given, which turns the rules gcc 14 promoted from errors back
1089    /// into warnings.
1090    ///
1091    /// Six of them, all about code written before the language settled: a declaration with no
1092    /// type in it, a call to a function nothing declared, a parameter in an old style definition
1093    /// with no type, a pointer made from an integer, a pointer assigned from a pointer to
1094    /// something else, and a `return` whose value disagrees with what was promised. The flag says
1095    /// nothing about any other diagnostic, and it does not say to compile something different: a
1096    /// program it accepts is compiled the way the rule it broke says it means.
1097    pub permissive: bool,
1098    /// Whether the whole unit is under GNU's reading of `inline` rather than C's, which is
1099    /// `-fgnu89-inline`.
1100    ///
1101    /// Under C's reading a definition every file-scope declaration wrote `inline` for and none
1102    /// wrote `extern` for emits nothing, and under GNU's it is the definition alone that decides
1103    /// and `extern inline` is the one that emits nothing. The C89 dialects are under GNU's
1104    /// whatever this says, since that is where the older reading came from, so this is the flag a
1105    /// program written against it reaches for when it is being compiled under a later dialect.
1106    pub gnu89_inline: bool,
1107    /// What a name that nothing in the source said anything about reaches, from `-fvisibility=`.
1108    pub visibility: Visibility,
1109    /// Whether the object may end up in a shared library, from `-fPIC` and `-fPIE`.
1110    pub pic: Pic,
1111    /// Whether a definition in this unit may be replaced at load time by one in another object,
1112    /// from `-fsemantic-interposition` and `-fno-semantic-interposition`.
1113    ///
1114    /// True is the honest answer and is gcc's default, because that is what an exported name in a
1115    /// shared library means: the dynamic linker takes the first definition it finds in load order,
1116    /// so a function this unit defines and calls may not be the one that runs. Everything the
1117    /// optimizer reads off a body has to stop at a name like that.
1118    ///
1119    /// False is a promise the build makes, and every distribution makes it, because otherwise a
1120    /// library cannot inline its own functions into each other. It is a promise rather than a
1121    /// deduction: nothing checks it, and a program that then interposes one of those names gets a
1122    /// mixture of the two definitions. It says nothing about `-fPIE`, where no name is replaceable
1123    /// to begin with, and it says nothing about how an address is reached, which is the separate
1124    /// question `-fPIC` decides.
1125    pub interposition: bool,
1126    /// Whether a function is described to an unwinder at every instruction, from
1127    /// `-fasynchronous-unwind-tables` and `-fno-asynchronous-unwind-tables`.
1128    ///
1129    /// True is the default, which is gcc's wherever anything reads the table, and the reason is
1130    /// that the programs that read it are not the ones being compiled. C++ exceptions,
1131    /// `backtrace`, a profiler sampling a stack and a crash handler printing one all walk frames
1132    /// belonging to code that knew nothing about them, so a unit that opts out stops a walk that
1133    /// started somewhere else.
1134    ///
1135    /// What `asynchronous` asks for on top of a table is that the answer is right at every
1136    /// instruction and not only where a call is, because a signal can arrive anywhere, including
1137    /// the middle of a prologue. Rows come off the prologue as it is built here, so that is the
1138    /// only kind of table there is to write and the weaker request below is answered with it.
1139    ///
1140    /// False is for a build that knows nothing will ever walk it, which in practice is a kernel or
1141    /// a freestanding image, and what it saves is the section rather than any instruction.
1142    pub async_unwind_tables: bool,
1143    /// Whether a function is described to an unwinder at all, from `-funwind-tables` and
1144    /// `-fno-unwind-tables`.
1145    ///
1146    /// The weaker of the two requests and off by default, because the one above is on and implies
1147    /// it. A table is written when either of them is standing, which is what [`Self::unwinds`]
1148    /// answers and is how gcc resolves a line that asks for a table and against an asynchronous
1149    /// one.
1150    ///
1151    /// Neither of them is about anything but ELF. Mach-O and COFF have their own arrangements and
1152    /// neither is written yet, so on those targets nothing reads these.
1153    pub unwind_tables: bool,
1154    /// Whether each function gets a section of its own, from `-ffunction-sections`.
1155    ///
1156    /// A linker can leave out a section nothing reaches and cannot leave out half of one, so this
1157    /// is what makes `--gc-sections` able to drop a function this file defines and nothing calls.
1158    /// A kernel and an embedded image are both linked that way and are both a good deal larger
1159    /// without it, and the cost is one section header per function.
1160    pub function_sections: bool,
1161    /// Whether each variable gets a section of its own, from `-fdata-sections`.
1162    ///
1163    /// The same bargain for the data, and a separate flag because gcc has two of them: a build
1164    /// that wants one and not the other is a build that measured something. Splitting the data can
1165    /// cost more than it saves, since two variables a loop reads together are no longer certain to
1166    /// land in the same page.
1167    pub data_sections: bool,
1168    /// The GCC release claimed, from `-fgnuc-version=`.
1169    pub gnuc: GnucVersion,
1170    /// Whether there is a standard library, which is `-ffreestanding` turned around.
1171    pub hosted: bool,
1172    /// Whether a call to a C library function written under its own plain name may be taken to
1173    /// mean that function, which is `-fno-builtin` turned around.
1174    ///
1175    /// The names are reserved, so `llabs` is the library's `llabs` and the compiler is allowed to
1176    /// know what it does. A program that means something else by one of them is the reason the
1177    /// flag exists, and `-ffreestanding` turns it off as well, because a freestanding program has
1178    /// no C library for the name to be the name of. The `__builtin_` spellings are not affected by
1179    /// either, since the prefix is the program saying which function it means.
1180    pub builtins: bool,
1181    /// The names `-fno-builtin-<name>` took away one at a time, without the prefix.
1182    ///
1183    /// A build that means its own `memcpy` and the library's everything else writes this rather
1184    /// than the whole flag, which is what the kernel does for a handful of names.
1185    pub no_builtin: Vec<String>,
1186    /// `-D` in command line order. `FOO` means `FOO=1`, as GCC has it.
1187    pub defines: Vec<String>,
1188    /// `-U` in command line order, applied after the defines because `-U` wins.
1189    pub undefines: Vec<String>,
1190    /// Where a header is looked for.
1191    pub search: SearchPath,
1192    /// What `-imacros` and `-include` named, in command line order.
1193    pub preincludes: Vec<Preinclude>,
1194    /// Whether `-E` writes line markers, which `-P` turns off.
1195    pub line_markers: bool,
1196    /// What the `-d` family asks for.
1197    pub dumps: Dumps,
1198    /// What the `-M` family asks for.
1199    pub deps: Deps,
1200    /// Whether the intermediate files are kept, from `-save-temps`.
1201    pub save_temps: SaveTemps,
1202    /// Whether each step says how long it took, from `-time`.
1203    pub time: bool,
1204    /// What `-f<pass>` and `-fno-<pass>` said about an optimizer pass, in the order the command
1205    /// line said it, so that the last mention of a pass is the one that decides.
1206    ///
1207    /// The pipeline the level chose is the starting point and this is what is added to and taken
1208    /// away from it. The names are checked against the pass list while the arguments are parsed,
1209    /// so anything in here is a pass the compiler has.
1210    pub passes: Vec<(String, bool)>,
1211    /// What `-fpass-fuel=<pass>=<n>` limited a pass to, by pass name.
1212    ///
1213    /// A pass with an entry here performs exactly that many transformations and then stops
1214    /// transforming, which is what bisects a miscompilation to one rewrite. See section 9.10 of
1215    /// `spec/09-optimizer.md`.
1216    pub pass_fuel: Vec<(String, u32)>,
1217    /// What `-fpass-fuel-global=<n>` limited the whole pipeline to, across every pass.
1218    ///
1219    /// The outer of the two searches in section 4.5 of `spec/optimizer/04-pass-manager.md`.
1220    /// Halving this says which pass holds the bad rewrite, and halving `-fpass-fuel` for that
1221    /// pass says which rewrite it is. Where both are given, a pass is stopped by whichever of
1222    /// the two is tighter.
1223    pub pass_fuel_global: Option<u32>,
1224    /// What `-fdisable-<pass>[=<range>]` and `-fenable-<pass>[=<range>]` said, in the order the
1225    /// command line said it, with `true` for the enabling half.
1226    ///
1227    /// A rule covers the functions it names and nothing else, and the last rule that covers a
1228    /// function is the one that decides for it, so the order has to survive. This is the second
1229    /// half of the bisection interface in section 41.6 of `spec/optimizer/41-correctness.md`:
1230    /// `-fpass-fuel` finds the rewrite and this finds the function. The pass names are checked
1231    /// against the pass list while the arguments are parsed.
1232    pub pass_gates: Vec<(bool, String)>,
1233    /// What `-fdump-ir=` asked to see, as it was written, which is `all`, `before-<pass>` or
1234    /// `after-<pass>`.
1235    pub dump_ir: Vec<String>,
1236    /// What `-fopt-info` asked to hear about, as the keywords were written, with the leading
1237    /// hyphen taken off, so a bare `-fopt-info` is the empty string in here.
1238    ///
1239    /// The keywords are `optimized`, `missed`, `note` and `all`, and two flags add up rather than
1240    /// the second replacing the first. Checked while the arguments are parsed, so anything in
1241    /// here is a spelling the optimizer understands. See section 42.2 of
1242    /// `spec/optimizer/42-measurement.md` for why `missed` is the one that earns the feature.
1243    pub opt_info: Vec<String>,
1244    /// Where `-fopt-info=<file>` sends the remarks, or `None` for standard error.
1245    ///
1246    /// One file for the whole run rather than one per input, the way GCC does it, and the last
1247    /// one on the command line is the one that decides. A harness that wants the remarks kept
1248    /// away from the diagnostics gives a file, which is what the corpus in `tamnd/rucc-corpus`
1249    /// does with GCC so that a rejection can still be matched against the diagnostic stream.
1250    pub opt_info_file: Option<String>,
1251    /// Whether the IR verifier runs after every pass that changed anything.
1252    ///
1253    /// On in a debug build without being asked, since that is where a broken pass should be
1254    /// caught. `-Zverify-each` turns it on in a release build, which is what CI wants.
1255    pub verify_each: bool,
1256    /// Where `-Zrule-coverage=FILE` writes which lowering rules fired, if it was given.
1257    ///
1258    /// A measurement rather than a thing a build asks for, which is why it is spelled with a `-Z`
1259    /// the way an unstable option is everywhere else: it is here for the harness in
1260    /// `tamnd/rucc-compat` to union over a corpus and report, and nothing about the code that comes
1261    /// out changes when it is on. One file per run of the compiler, holding the whole rule set with
1262    /// the rules this run reached marked, whatever the run compiled and however many files it was.
1263    pub rule_coverage: Option<String>,
1264    /// Where `-Zregister-pressure=FILE` writes what the allocator had to put on the stack.
1265    ///
1266    /// A measurement and spelled with a `-Z` for the same reason as the one above: nothing about
1267    /// the code that comes out changes when it is on. One file per run of the compiler, one line
1268    /// per function, holding how many values went to the stack and how many stores and reloads
1269    /// that cost. What reads it is `cargo xtask pressure`, which compiles the benchmarks in
1270    /// `bench/safety` with the monitor off and on and reports the difference, since
1271    /// `spec/safe-memory/13-performance.md` section 13.1 asks for that number and section 5.2.1
1272    /// says why: a capability in flight is four words, and if materializing one spills something
1273    /// else in a hot loop then check elimination cannot save it.
1274    pub register_pressure: Option<String>,
1275}
1276
1277impl Options {
1278    /// Default options for `target`.
1279    pub fn new(target: Triple) -> Self {
1280        Self {
1281            target,
1282            opt_level: OptLevel::default(),
1283            safety: Safety::default(),
1284            emit: EmitKind::default(),
1285            debug_info: false,
1286            frame_pointer: false,
1287            red_zone: true,
1288            protector: Protector::default(),
1289            stack_clash: false,
1290            control: Control::default(),
1291            profile: false,
1292            hook: Hook::default(),
1293            patchable: Patchable::default(),
1294            wrapping: Wrapping::NONE,
1295            warnings_are_errors: false,
1296            warnings: true,
1297            error_limit: 20,
1298            std: Std::default(),
1299            gnu_extensions: true,
1300            pedantic: false,
1301            permissive: false,
1302            gnu89_inline: false,
1303            visibility: Visibility::default(),
1304            pic: Pic::default(),
1305            interposition: true,
1306            async_unwind_tables: true,
1307            unwind_tables: false,
1308            function_sections: false,
1309            data_sections: false,
1310            gnuc: GnucVersion::default(),
1311            hosted: true,
1312            builtins: true,
1313            no_builtin: Vec::new(),
1314            defines: Vec::new(),
1315            undefines: Vec::new(),
1316            search: SearchPath::new(),
1317            preincludes: Vec::new(),
1318            line_markers: true,
1319            dumps: Dumps::default(),
1320            deps: Deps::default(),
1321            save_temps: SaveTemps::default(),
1322            time: false,
1323            passes: Vec::new(),
1324            pass_fuel: Vec::new(),
1325            pass_fuel_global: None,
1326            pass_gates: Vec::new(),
1327            dump_ir: Vec::new(),
1328            opt_info: Vec::new(),
1329            opt_info_file: None,
1330            verify_each: cfg!(debug_assertions),
1331            rule_coverage: None,
1332            register_pressure: None,
1333        }
1334    }
1335
1336    /// Whether a function in this unit is described to an unwinder.
1337    ///
1338    /// Either request is answered with the same table, so what decides is whether either of them
1339    /// is standing. Asked here rather than worked out at the two places that write a table, since
1340    /// those two writing different answers for one function is what `spec/11-asm-objects-debug.md`
1341    /// section 11.1 says must not be possible.
1342    #[must_use]
1343    pub const fn unwinds(&self) -> bool {
1344        self.async_unwind_tables || self.unwind_tables
1345    }
1346}
1347
1348/// One compilation.
1349///
1350/// Holds the options, the string interner and the diagnostics raised so far. Passing a
1351/// `&mut Session` is how a stage reports a problem, and the return value of a stage says
1352/// what it produced, never whether it succeeded: that question is answered by
1353/// [`Session::has_errors`].
1354#[derive(Debug)]
1355pub struct Session {
1356    /// What this compilation was asked to do.
1357    pub opts: Options,
1358    /// Everything known about the target.
1359    pub target: TargetInfo,
1360    /// The one interner for the compilation.
1361    pub interner: Interner,
1362    /// Every file read during the compilation, and the flat coordinate space their spans
1363    /// live in.
1364    ///
1365    /// This is on the session rather than passed around separately because a span is only
1366    /// meaningful against the map that issued it, and one map per compilation is the rule
1367    /// that makes that true by construction.
1368    pub sources: SourceMap,
1369    diagnostics: Vec<Diagnostic>,
1370    error_count: u32,
1371    warning_count: u32,
1372}
1373
1374impl Session {
1375    /// A session for `opts`.
1376    pub fn new(opts: Options) -> Self {
1377        let target = TargetInfo::new(opts.target);
1378        Self {
1379            opts,
1380            target,
1381            interner: Interner::with_capacity(1024),
1382            sources: SourceMap::new(),
1383            diagnostics: Vec::new(),
1384            error_count: 0,
1385            warning_count: 0,
1386        }
1387    }
1388
1389    /// Records a diagnostic.
1390    ///
1391    /// Under `-Werror` a warning is promoted here, once, rather than at every site that
1392    /// raises one, and under `-w` it is dropped here for the same reason. A warning that `-w`
1393    /// dropped is not counted, so `-w -Werror` compiles rather than failing on a warning
1394    /// nobody was going to see.
1395    pub fn emit(&mut self, mut diag: Diagnostic) {
1396        if !self.opts.warnings && diag.severity == Severity::Warning {
1397            return;
1398        }
1399        if self.opts.warnings_are_errors && diag.severity == Severity::Warning {
1400            diag.severity = Severity::Error;
1401        }
1402        match diag.severity {
1403            Severity::Error | Severity::Ice => self.error_count += 1,
1404            Severity::Warning => self.warning_count += 1,
1405            Severity::Note | Severity::Help => {}
1406        }
1407        self.diagnostics.push(diag);
1408    }
1409
1410    /// Everything raised so far, in the order it was raised.
1411    pub fn diagnostics(&self) -> &[Diagnostic] {
1412        &self.diagnostics
1413    }
1414
1415    /// Whether anything fatal has been raised.
1416    pub fn has_errors(&self) -> bool {
1417        self.error_count > 0
1418    }
1419
1420    /// How many errors have been raised.
1421    pub fn error_count(&self) -> u32 {
1422        self.error_count
1423    }
1424
1425    /// How many warnings have been raised.
1426    pub fn warning_count(&self) -> u32 {
1427        self.warning_count
1428    }
1429
1430    /// Whether the error limit has been reached and the caller should stop.
1431    pub fn error_limit_reached(&self) -> bool {
1432        self.opts.error_limit != 0 && self.error_count >= self.opts.error_limit
1433    }
1434}
1435
1436#[cfg(test)]
1437mod tests {
1438    use super::*;
1439
1440    fn session() -> Session {
1441        Session::new(Options::new("x86_64-unknown-linux-gnu".parse().unwrap()))
1442    }
1443
1444    #[test]
1445    fn a_version_claim_reads_the_way_gcc_prints_one() {
1446        // `gcc -dumpfullversion` gives all three, `gcc -dumpversion` gives one, and both are
1447        // things a script pastes straight into a flag.
1448        let all = |v: &str| v.parse::<GnucVersion>().unwrap();
1449        assert_eq!(all("15.1.0"), GnucVersion { major: 15, minor: 1, patch: 0 });
1450        assert_eq!(all("15"), GnucVersion { major: 15, minor: 0, patch: 0 });
1451        assert_eq!(all("4.2"), GnucVersion { major: 4, minor: 2, patch: 0 });
1452        assert!("".parse::<GnucVersion>().is_err());
1453        assert!("15.".parse::<GnucVersion>().is_err(), "a trailing dot is a typo, not a zero");
1454        assert!("1.2.3.4".parse::<GnucVersion>().is_err());
1455    }
1456
1457    #[test]
1458    fn optimisation_levels_parse_the_way_gcc_spells_them() {
1459        assert_eq!("".parse::<OptLevel>().unwrap(), OptLevel::O1);
1460        assert_eq!("0".parse::<OptLevel>().unwrap(), OptLevel::O0);
1461        assert_eq!("2".parse::<OptLevel>().unwrap(), OptLevel::O2);
1462        assert_eq!("9".parse::<OptLevel>().unwrap(), OptLevel::O3);
1463        assert_eq!("s".parse::<OptLevel>().unwrap(), OptLevel::Os);
1464        assert!("q".parse::<OptLevel>().is_err());
1465    }
1466
1467    #[test]
1468    fn only_o0_skips_the_optimizer() {
1469        assert!(!OptLevel::O0.runs_optimizer());
1470        assert!(OptLevel::O1.runs_optimizer());
1471        assert!(OptLevel::Oz.runs_optimizer());
1472    }
1473
1474    #[test]
1475    fn the_safety_tiers_round_trip_and_nothing_else_is_one() {
1476        for tier in [Safety::Off, Safety::Detect, Safety::Enforce, Safety::Kernel] {
1477            assert_eq!(tier.as_str().parse::<Safety>().unwrap(), tier);
1478        }
1479        // `on` is the obvious thing to try and it is not a tier, because which tier somebody
1480        // means by it is the whole question document 02 answers.
1481        assert!("on".parse::<Safety>().is_err());
1482        assert!("".parse::<Safety>().is_err());
1483    }
1484
1485    #[test]
1486    fn room_for_a_patcher_is_written_the_way_it_was_asked_for() {
1487        for (written, total, before) in
1488            [("0", 0, 0), ("2", 2, 0), ("16", 16, 0), ("5,3", 5, 3), ("3,3", 3, 3)]
1489        {
1490            let room: Patchable = written.parse().unwrap();
1491            assert_eq!(room, Patchable { total, before });
1492            assert_eq!(room.to_string(), written);
1493            assert_eq!(room.after(), total - before);
1494            assert_eq!(room.any(), total > 0);
1495        }
1496        // A second number of zero is the same request as no second number, and it is written back
1497        // the shorter way, which is the way somebody reaching for the flag writes it.
1498        assert_eq!("2,0".parse::<Patchable>().unwrap().to_string(), "2");
1499    }
1500
1501    #[test]
1502    fn more_room_in_front_of_the_label_than_there_is_room_at_all_is_refused() {
1503        // Rather than clamped, because there is no reading of it a caller meant. gcc says the same
1504        // about each of these.
1505        assert!("1,2".parse::<Patchable>().is_err());
1506        assert!("1,2,3".parse::<Patchable>().is_err());
1507        assert!("a".parse::<Patchable>().is_err());
1508        assert!("".parse::<Patchable>().is_err());
1509        assert!("-1".parse::<Patchable>().is_err());
1510    }
1511
1512    #[test]
1513    fn the_two_places_the_intermediate_files_can_go_are_the_two_words_that_are_taken() {
1514        assert_eq!("obj".parse::<SaveTemps>().unwrap(), SaveTemps::Object);
1515        assert_eq!("cwd".parse::<SaveTemps>().unwrap(), SaveTemps::Cwd);
1516        // The names of the two flags that mean the same thing as `=obj` are not themselves
1517        // arguments of it, and neither is silence.
1518        assert!("obj,cwd".parse::<SaveTemps>().is_err());
1519        assert!("".parse::<SaveTemps>().is_err());
1520        // Nothing is kept unless something asked, and both of the words that ask do ask.
1521        assert_eq!(SaveTemps::default(), SaveTemps::No);
1522        assert!(!SaveTemps::No.wanted());
1523        assert!(SaveTemps::Object.wanted());
1524        assert!(SaveTemps::Cwd.wanted());
1525    }
1526
1527    #[test]
1528    fn a_build_that_did_not_ask_for_the_monitor_does_not_get_it() {
1529        assert_eq!(Safety::default(), Safety::Off);
1530        assert!(!Safety::Off.instruments());
1531        assert!(Safety::Detect.instruments());
1532        assert!(Safety::Enforce.instruments());
1533        assert!(Safety::Kernel.instruments());
1534    }
1535
1536    #[test]
1537    fn emit_kinds_round_trip_through_their_names() {
1538        for k in [
1539            EmitKind::Executable,
1540            EmitKind::Object,
1541            EmitKind::Asm,
1542            EmitKind::Preprocessed,
1543            EmitKind::Tast,
1544            EmitKind::Ir,
1545            EmitKind::MirFinal,
1546        ] {
1547            assert_eq!(k.as_str().parse::<EmitKind>().unwrap(), k);
1548        }
1549    }
1550
1551    #[test]
1552    fn errors_are_counted_and_warnings_are_not() {
1553        let mut s = session();
1554        s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
1555        s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
1556        assert_eq!(s.error_count(), 1);
1557        assert_eq!(s.warning_count(), 1);
1558        assert!(s.has_errors());
1559        assert_eq!(s.diagnostics().len(), 2);
1560    }
1561
1562    #[test]
1563    fn werror_promotes_once_at_the_sink() {
1564        let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
1565        opts.warnings_are_errors = true;
1566        let mut s = Session::new(opts);
1567        s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
1568        assert_eq!(s.error_count(), 1);
1569        assert_eq!(s.warning_count(), 0);
1570        assert_eq!(s.diagnostics()[0].severity, Severity::Error);
1571    }
1572
1573    #[test]
1574    fn the_error_limit_can_be_switched_off() {
1575        let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
1576        opts.error_limit = 0;
1577        let mut s = Session::new(opts);
1578        for _ in 0..100 {
1579            s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
1580        }
1581        assert!(!s.error_limit_reached());
1582    }
1583
1584    #[test]
1585    fn the_session_carries_the_source_map_spans_are_resolved_against() {
1586        let mut s = session();
1587        let file = s.sources.add("a.c", b"int x;\n".to_vec()).unwrap();
1588        let start = s.sources.file(file).start;
1589        assert_eq!(s.sources.render_position(start + 4), "a.c:1:5");
1590    }
1591
1592    #[test]
1593    fn the_session_carries_the_resolved_target() {
1594        let s = session();
1595        assert_eq!(s.target.pointer_width, 64);
1596        assert!(s.target.char_is_signed);
1597    }
1598}