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