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 3, 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.8.1")]
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/// What the compiler should produce.
174///
175/// The intermediate forms are not a debugging convenience bolted on later. Every one of them
176/// is a documented textual form that round-trips, which is what makes the per-stage testing
177/// in `spec/15-testing.md` section 15.2 possible.
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
179// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
180// match that needs to change, in this workspace and in anyone else's code. That is
181// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
182// target is a data change: the compiler tells you every place the data is read.
183pub enum EmitKind {
184 /// A linked executable. The default.
185 #[default]
186 Executable,
187 /// An object file, `-c`.
188 Object,
189 /// Assembly text, `-S`.
190 Asm,
191 /// Preprocessed source, `-E`.
192 Preprocessed,
193 /// The typed AST, `--emit=tast`.
194 Tast,
195 /// The IR, `--emit=ir`.
196 Ir,
197 /// The machine IR after register allocation, `--emit=mir-final`.
198 MirFinal,
199 /// The safety summary, `--emit=safety-summary`.
200 ///
201 /// Not an intermediate form of the program the way the three above are. It is the answer to
202 /// "what does this build's guarantee actually rest on", which
203 /// `spec/safe-memory/07-check-elimination.md` section 7.8 asks for and
204 /// `spec/safe-memory/10-boundaries.md` section 10.2 says why.
205 SafetySummary,
206 /// How the bytes of the translation unit's records fall into granules,
207 /// `--emit=type-granules`.
208 ///
209 /// Not an intermediate form either. It is the measurement
210 /// `spec/safe-memory/17-open-questions.md` question 6 asks for, which decides whether the
211 /// type plane fits inside Tier D's memory budget, and it needs nothing past the type
212 /// checker because it is a question about layouts rather than about code.
213 TypeGranules,
214}
215
216impl EmitKind {
217 /// The name used by `--emit=` and by `--print-config`.
218 pub const fn as_str(self) -> &'static str {
219 match self {
220 EmitKind::Executable => "exe",
221 EmitKind::Object => "obj",
222 EmitKind::Asm => "asm",
223 EmitKind::Preprocessed => "preprocessed",
224 EmitKind::Tast => "tast",
225 EmitKind::Ir => "ir",
226 EmitKind::MirFinal => "mir-final",
227 EmitKind::SafetySummary => "safety-summary",
228 EmitKind::TypeGranules => "type-granules",
229 }
230 }
231}
232
233impl FromStr for EmitKind {
234 type Err = ();
235
236 fn from_str(s: &str) -> Result<Self, ()> {
237 Ok(match s {
238 "exe" => EmitKind::Executable,
239 "obj" => EmitKind::Object,
240 "asm" => EmitKind::Asm,
241 "preprocessed" => EmitKind::Preprocessed,
242 "tast" => EmitKind::Tast,
243 "ir" => EmitKind::Ir,
244 "mir-final" => EmitKind::MirFinal,
245 "safety-summary" => EmitKind::SafetySummary,
246 "type-granules" => EmitKind::TypeGranules,
247 _ => return Err(()),
248 })
249 }
250}
251
252/// Which C the source is written in.
253///
254/// The GNU variants are the same language with `__STRICT_ANSI__` left undefined, so the
255/// dialect and the extension question are two fields rather than ten variants.
256#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
257pub enum Std {
258 /// `-std=c89`, and `-ansi`.
259 C89,
260 /// `-std=c99`.
261 C99,
262 /// `-std=c11`.
263 C11,
264 /// `-std=c17`, which is C11 with the defect reports applied.
265 C17,
266 /// `-std=c23`. The default, matching current GCC.
267 #[default]
268 C23,
269}
270
271impl Std {
272 /// What `__STDC_VERSION__` says, which C89 does not define at all.
273 pub const fn stdc_version(self) -> Option<&'static str> {
274 match self {
275 Std::C89 => None,
276 Std::C99 => Some("199901L"),
277 Std::C11 => Some("201112L"),
278 Std::C17 => Some("201710L"),
279 Std::C23 => Some("202311L"),
280 }
281 }
282
283 /// The name in `-std=`.
284 pub const fn as_str(self) -> &'static str {
285 match self {
286 Std::C89 => "c89",
287 Std::C99 => "c99",
288 Std::C11 => "c11",
289 Std::C17 => "c17",
290 Std::C23 => "c23",
291 }
292 }
293
294 /// Whether this dialect has `_Atomic`, `_Thread_local` and the rest of C11.
295 pub const fn has_c11(self) -> bool {
296 matches!(self, Std::C11 | Std::C17 | Std::C23)
297 }
298
299 /// Reads a `-std=` argument, and says whether the GNU extensions came with it.
300 ///
301 /// Every alias GCC takes is here, including the `iso9899` spellings and the year based
302 /// ones, because a build system that passes `-std=iso9899:1999` is passing what its
303 /// author tested against and rejecting it helps nobody. An unknown dialect is `None`
304 /// rather than a guess, since guessing means compiling a different language than the one
305 /// asked for.
306 #[must_use]
307 pub fn from_flag(name: &str) -> Option<(Std, bool)> {
308 let gnu = name.starts_with("gnu");
309 let std = match name {
310 "c89" | "c90" | "gnu89" | "gnu90" | "iso9899:1990" | "iso9899:199409" => Std::C89,
311 "c99" | "c9x" | "gnu99" | "gnu9x" | "iso9899:1999" | "iso9899:199x" => Std::C99,
312 "c11" | "c1x" | "gnu11" | "gnu1x" | "iso9899:2011" => Std::C11,
313 "c17" | "c18" | "gnu17" | "gnu18" | "iso9899:2017" | "iso9899:2018" => Std::C17,
314 "c23" | "c2x" | "gnu23" | "gnu2x" => Std::C23,
315 _ => return None,
316 };
317 Some((std, gnu))
318 }
319}
320
321/// The GCC release the compiler claims to be, as `__GNUC__`, `__GNUC_MINOR__` and
322/// `__GNUC_PATCHLEVEL__`.
323///
324/// Design: `spec/04-driver-and-cli.md` section 4.5, which makes this a knob rather than a
325/// constant and says to start conservative and raise it as the matrix in `rucc-gnu` fills in.
326///
327/// The default is seven, which is the lowest claim that gets a modern glibc. glibc gates most
328/// of what it hands a caller on `__GNUC_PREREQ`, so the claim decides which half of
329/// `sys/cdefs.h` we get, and below seven `bits/floatn-common.h` writes `typedef float _Float32;`
330/// over a keyword this compiler already has. Every header that reaches it stops there, which
331/// was most of them: on Ubuntu 24.04's glibc 2.39 the claim of 4.2.1 that stood here before got
332/// 180 of 214 headers through and seven gets 202, and the amalgamated sqlite goes from four
333/// errors to none.
334///
335/// It is still deliberately low. Claiming a version whose promises have not been kept means
336/// being handed syntax the compiler cannot parse, so this moves when there is a measurement
337/// saying it can. Thirteen and sixteen were measured alongside seven and came out identical on
338/// glibc, on the macOS SDK and on sqlite, so the next move up is cheap; it is a separate one
339/// because nothing yet needs it.
340#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
341pub struct GnucVersion {
342 /// `__GNUC__`.
343 pub major: u32,
344 /// `__GNUC_MINOR__`.
345 pub minor: u32,
346 /// `__GNUC_PATCHLEVEL__`.
347 pub patch: u32,
348}
349
350impl Default for GnucVersion {
351 fn default() -> GnucVersion {
352 GnucVersion { major: 7, minor: 0, patch: 0 }
353 }
354}
355
356impl FromStr for GnucVersion {
357 type Err = String;
358
359 /// Reads `-fgnuc-version=`, which is `15`, `15.1` or `15.1.0`.
360 ///
361 /// The short forms are not a convenience, they are what people write. A missing component
362 /// is zero, the same way GCC treats a release with no patchlevel.
363 fn from_str(text: &str) -> Result<GnucVersion, String> {
364 let mut parts = text.split('.');
365 let mut next = |what: &str| -> Result<u32, String> {
366 match parts.next() {
367 None => Ok(0),
368 Some(field) => {
369 field.parse().map_err(|_| format!("`{text}` has a {what} that is not a number"))
370 }
371 }
372 };
373 let major = next("major")?;
374 let minor = next("minor")?;
375 let patch = next("patchlevel")?;
376 if parts.next().is_some() {
377 return Err(format!("`{text}` has more than three components"));
378 }
379 Ok(GnucVersion { major, minor, patch })
380 }
381}
382
383/// What the `-d` family asks to be dumped alongside, or instead of, the preprocessed output.
384///
385/// Design: `spec/04-driver-and-cli.md` section 4.4.
386///
387/// GCC spells these as letters packed into one flag, so `-dDI` is two of them, and a letter it
388/// does not know is ignored rather than rejected. That last part is deliberate on GCC's side
389/// and worth copying: the family is a debugging aid and a build that passes `-dumpbase` should
390/// not die on the `-d`.
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
392pub struct Dumps {
393 /// `-dM`. Print the macros that are defined at the end, and nothing else.
394 pub macros: bool,
395}
396
397impl Dumps {
398 /// The letters GCC's preprocessor takes after `-d`.
399 ///
400 /// `M` is the macros, `D` is the macros in place, `N` is their names only, `I` is the
401 /// `#include` lines and `U` is the macros as they are used. Only `M` does anything so far.
402 const LETTERS: &'static str = "MDNIU";
403
404 /// Whether `arg` is a flag from this family rather than something else beginning with
405 /// `-d`.
406 ///
407 /// The check is here rather than in the driver so that the set of letters and the set of
408 /// flags accepted cannot drift apart. It matters because `-dumpversion` also begins with
409 /// `-d`, and a family that swallowed every such flag would turn a flag we have not written
410 /// into a dump of nothing.
411 #[must_use]
412 pub fn is_family(arg: &str) -> bool {
413 match arg.strip_prefix("-d") {
414 Some("") | None => false,
415 Some(letters) => letters.chars().all(|c| Dumps::LETTERS.contains(c)),
416 }
417 }
418
419 /// Reads the letters after `-d`, ignoring the ones we do not implement yet.
420 pub fn add(&mut self, letters: &str) {
421 for letter in letters.chars() {
422 if letter == 'M' {
423 self.macros = true;
424 }
425 }
426 }
427
428 /// Whether anything at all was asked for.
429 #[must_use]
430 pub const fn any(self) -> bool {
431 self.macros
432 }
433}
434
435/// Everything a compilation was asked to do.
436///
437/// Options are a plain value with no interior mutability, so a caller can build one, clone
438/// it, tweak one field and run a second compilation, which is exactly what the differential
439/// testing in `spec/15-testing.md` needs.
440#[derive(Debug, Clone, PartialEq, Eq)]
441#[non_exhaustive]
442pub struct Options {
443 /// The target to generate code for.
444 pub target: Triple,
445 /// The optimisation level.
446 pub opt_level: OptLevel,
447 /// How much of the memory safety monitor is on, from `-fsafety=`.
448 ///
449 /// Off unless it was asked for. A program built without the flag is compiled by exactly the
450 /// pipeline it was compiled by before the monitor existed, which is the only way the feature
451 /// can be developed in the open without every build paying for it.
452 pub safety: Safety,
453 /// What to produce.
454 pub emit: EmitKind,
455 /// Whether to emit debug information.
456 pub debug_info: bool,
457 /// Whether every function keeps a frame pointer, from `-fno-omit-frame-pointer`.
458 ///
459 /// Off by default, which is what gcc does at every level above `-O0` and what leaves the
460 /// register free for the allocator. A profiler that walks the stack by following saved frame
461 /// pointers needs it on, and so does any code a debugger has to unwind without unwind tables.
462 pub frame_pointer: bool,
463 /// Whether the red zone may be used, from `-mno-red-zone` turned around.
464 ///
465 /// The 128 bytes below the stack pointer that the System V psABI promises no signal handler
466 /// will touch, which lets a small leaf function keep its locals without moving the stack
467 /// pointer at all. A kernel turns this off, because an interrupt taken on the kernel stack
468 /// makes the promise false, and every kernel build in the wild passes `-mno-red-zone` for
469 /// exactly that reason. A convention without a red zone ignores this.
470 pub red_zone: bool,
471 /// Whether warnings are errors.
472 pub warnings_are_errors: bool,
473 /// Whether a warning is raised at all, which is `-w` turned around.
474 ///
475 /// A build that passes this has decided it does not want to hear about anything that is not
476 /// fatal, and the flag is dropped at the one place every diagnostic goes through rather than
477 /// tested at each site that raises one. `-w` beats `-Werror` where both are given, because a
478 /// warning that was never raised cannot be promoted.
479 pub warnings: bool,
480 /// How many diagnostics to print before giving up. Past a certain point the output is
481 /// noise from a single earlier mistake, and GCC's default of no limit is not a kindness.
482 pub error_limit: u32,
483 /// The dialect, from `-std=`.
484 pub std: Std,
485 /// Whether the GNU extensions are on, which is `-std=gnu23` rather than `-std=c23`.
486 pub gnu_extensions: bool,
487 /// Whether `-pedantic` was given, which is what turns a use of an extension from silence
488 /// into a diagnostic. It is not the same knob as the dialect: `-std=c17 -pedantic` warns
489 /// about a construct that `-std=c17` alone accepts without a word.
490 pub pedantic: bool,
491 /// Whether `-fpermissive` was given, which turns the rules gcc 14 promoted from errors back
492 /// into warnings.
493 ///
494 /// Six of them, all about code written before the language settled: a declaration with no
495 /// type in it, a call to a function nothing declared, a parameter in an old style definition
496 /// with no type, a pointer made from an integer, a pointer assigned from a pointer to
497 /// something else, and a `return` whose value disagrees with what was promised. The flag says
498 /// nothing about any other diagnostic, and it does not say to compile something different: a
499 /// program it accepts is compiled the way the rule it broke says it means.
500 pub permissive: bool,
501 /// Whether the whole unit is under GNU's reading of `inline` rather than C's, which is
502 /// `-fgnu89-inline`.
503 ///
504 /// Under C's reading a definition every file-scope declaration wrote `inline` for and none
505 /// wrote `extern` for emits nothing, and under GNU's it is the definition alone that decides
506 /// and `extern inline` is the one that emits nothing. The C89 dialects are under GNU's
507 /// whatever this says, since that is where the older reading came from, so this is the flag a
508 /// program written against it reaches for when it is being compiled under a later dialect.
509 pub gnu89_inline: bool,
510 /// The GCC release claimed, from `-fgnuc-version=`.
511 pub gnuc: GnucVersion,
512 /// Whether there is a standard library, which is `-ffreestanding` turned around.
513 pub hosted: bool,
514 /// Whether a call to a C library function written under its own plain name may be taken to
515 /// mean that function, which is `-fno-builtin` turned around.
516 ///
517 /// The names are reserved, so `llabs` is the library's `llabs` and the compiler is allowed to
518 /// know what it does. A program that means something else by one of them is the reason the
519 /// flag exists, and `-ffreestanding` turns it off as well, because a freestanding program has
520 /// no C library for the name to be the name of. The `__builtin_` spellings are not affected by
521 /// either, since the prefix is the program saying which function it means.
522 pub builtins: bool,
523 /// The names `-fno-builtin-<name>` took away one at a time, without the prefix.
524 ///
525 /// A build that means its own `memcpy` and the library's everything else writes this rather
526 /// than the whole flag, which is what the kernel does for a handful of names.
527 pub no_builtin: Vec<String>,
528 /// `-D` in command line order. `FOO` means `FOO=1`, as GCC has it.
529 pub defines: Vec<String>,
530 /// `-U` in command line order, applied after the defines because `-U` wins.
531 pub undefines: Vec<String>,
532 /// Where a header is looked for.
533 pub search: SearchPath,
534 /// Whether `-E` writes line markers, which `-P` turns off.
535 pub line_markers: bool,
536 /// What the `-d` family asks for.
537 pub dumps: Dumps,
538 /// What `-f<pass>` and `-fno-<pass>` said about an optimizer pass, in the order the command
539 /// line said it, so that the last mention of a pass is the one that decides.
540 ///
541 /// The pipeline the level chose is the starting point and this is what is added to and taken
542 /// away from it. The names are checked against the pass list while the arguments are parsed,
543 /// so anything in here is a pass the compiler has.
544 pub passes: Vec<(String, bool)>,
545 /// What `-fpass-fuel=<pass>=<n>` limited a pass to, by pass name.
546 ///
547 /// A pass with an entry here performs exactly that many transformations and then stops
548 /// transforming, which is what bisects a miscompilation to one rewrite. See section 9.10 of
549 /// `spec/09-optimizer.md`.
550 pub pass_fuel: Vec<(String, u32)>,
551 /// What `-fpass-fuel-global=<n>` limited the whole pipeline to, across every pass.
552 ///
553 /// The outer of the two searches in section 4.5 of `spec/optimizer/04-pass-manager.md`.
554 /// Halving this says which pass holds the bad rewrite, and halving `-fpass-fuel` for that
555 /// pass says which rewrite it is. Where both are given, a pass is stopped by whichever of
556 /// the two is tighter.
557 pub pass_fuel_global: Option<u32>,
558 /// What `-fdisable-<pass>[=<range>]` and `-fenable-<pass>[=<range>]` said, in the order the
559 /// command line said it, with `true` for the enabling half.
560 ///
561 /// A rule covers the functions it names and nothing else, and the last rule that covers a
562 /// function is the one that decides for it, so the order has to survive. This is the second
563 /// half of the bisection interface in section 41.6 of `spec/optimizer/41-correctness.md`:
564 /// `-fpass-fuel` finds the rewrite and this finds the function. The pass names are checked
565 /// against the pass list while the arguments are parsed.
566 pub pass_gates: Vec<(bool, String)>,
567 /// What `-fdump-ir=` asked to see, as it was written, which is `all`, `before-<pass>` or
568 /// `after-<pass>`.
569 pub dump_ir: Vec<String>,
570 /// What `-fopt-info` asked to hear about, as the keywords were written, with the leading
571 /// hyphen taken off, so a bare `-fopt-info` is the empty string in here.
572 ///
573 /// The keywords are `optimized`, `missed`, `note` and `all`, and two flags add up rather than
574 /// the second replacing the first. Checked while the arguments are parsed, so anything in
575 /// here is a spelling the optimizer understands. See section 42.2 of
576 /// `spec/optimizer/42-measurement.md` for why `missed` is the one that earns the feature.
577 pub opt_info: Vec<String>,
578 /// Where `-fopt-info=<file>` sends the remarks, or `None` for standard error.
579 ///
580 /// One file for the whole run rather than one per input, the way GCC does it, and the last
581 /// one on the command line is the one that decides. A harness that wants the remarks kept
582 /// away from the diagnostics gives a file, which is what the corpus in `tamnd/rucc-corpus`
583 /// does with GCC so that a rejection can still be matched against the diagnostic stream.
584 pub opt_info_file: Option<String>,
585 /// Whether the IR verifier runs after every pass that changed anything.
586 ///
587 /// On in a debug build without being asked, since that is where a broken pass should be
588 /// caught. `-Zverify-each` turns it on in a release build, which is what CI wants.
589 pub verify_each: bool,
590 /// Where `-Zrule-coverage=FILE` writes which lowering rules fired, if it was given.
591 ///
592 /// A measurement rather than a thing a build asks for, which is why it is spelled with a `-Z`
593 /// the way an unstable option is everywhere else: it is here for the harness in
594 /// `tamnd/rucc-compat` to union over a corpus and report, and nothing about the code that comes
595 /// out changes when it is on. One file per run of the compiler, holding the whole rule set with
596 /// the rules this run reached marked, whatever the run compiled and however many files it was.
597 pub rule_coverage: Option<String>,
598}
599
600impl Options {
601 /// Default options for `target`.
602 pub fn new(target: Triple) -> Self {
603 Self {
604 target,
605 opt_level: OptLevel::default(),
606 safety: Safety::default(),
607 emit: EmitKind::default(),
608 debug_info: false,
609 frame_pointer: false,
610 red_zone: true,
611 warnings_are_errors: false,
612 warnings: true,
613 error_limit: 20,
614 std: Std::default(),
615 gnu_extensions: true,
616 pedantic: false,
617 permissive: false,
618 gnu89_inline: false,
619 gnuc: GnucVersion::default(),
620 hosted: true,
621 builtins: true,
622 no_builtin: Vec::new(),
623 defines: Vec::new(),
624 undefines: Vec::new(),
625 search: SearchPath::new(),
626 line_markers: true,
627 dumps: Dumps::default(),
628 passes: Vec::new(),
629 pass_fuel: Vec::new(),
630 pass_fuel_global: None,
631 pass_gates: Vec::new(),
632 dump_ir: Vec::new(),
633 opt_info: Vec::new(),
634 opt_info_file: None,
635 verify_each: cfg!(debug_assertions),
636 rule_coverage: None,
637 }
638 }
639}
640
641/// One compilation.
642///
643/// Holds the options, the string interner and the diagnostics raised so far. Passing a
644/// `&mut Session` is how a stage reports a problem, and the return value of a stage says
645/// what it produced, never whether it succeeded: that question is answered by
646/// [`Session::has_errors`].
647#[derive(Debug)]
648pub struct Session {
649 /// What this compilation was asked to do.
650 pub opts: Options,
651 /// Everything known about the target.
652 pub target: TargetInfo,
653 /// The one interner for the compilation.
654 pub interner: Interner,
655 /// Every file read during the compilation, and the flat coordinate space their spans
656 /// live in.
657 ///
658 /// This is on the session rather than passed around separately because a span is only
659 /// meaningful against the map that issued it, and one map per compilation is the rule
660 /// that makes that true by construction.
661 pub sources: SourceMap,
662 diagnostics: Vec<Diagnostic>,
663 error_count: u32,
664 warning_count: u32,
665}
666
667impl Session {
668 /// A session for `opts`.
669 pub fn new(opts: Options) -> Self {
670 let target = TargetInfo::new(opts.target);
671 Self {
672 opts,
673 target,
674 interner: Interner::with_capacity(1024),
675 sources: SourceMap::new(),
676 diagnostics: Vec::new(),
677 error_count: 0,
678 warning_count: 0,
679 }
680 }
681
682 /// Records a diagnostic.
683 ///
684 /// Under `-Werror` a warning is promoted here, once, rather than at every site that
685 /// raises one, and under `-w` it is dropped here for the same reason. A warning that `-w`
686 /// dropped is not counted, so `-w -Werror` compiles rather than failing on a warning
687 /// nobody was going to see.
688 pub fn emit(&mut self, mut diag: Diagnostic) {
689 if !self.opts.warnings && diag.severity == Severity::Warning {
690 return;
691 }
692 if self.opts.warnings_are_errors && diag.severity == Severity::Warning {
693 diag.severity = Severity::Error;
694 }
695 match diag.severity {
696 Severity::Error | Severity::Ice => self.error_count += 1,
697 Severity::Warning => self.warning_count += 1,
698 Severity::Note | Severity::Help => {}
699 }
700 self.diagnostics.push(diag);
701 }
702
703 /// Everything raised so far, in the order it was raised.
704 pub fn diagnostics(&self) -> &[Diagnostic] {
705 &self.diagnostics
706 }
707
708 /// Whether anything fatal has been raised.
709 pub fn has_errors(&self) -> bool {
710 self.error_count > 0
711 }
712
713 /// How many errors have been raised.
714 pub fn error_count(&self) -> u32 {
715 self.error_count
716 }
717
718 /// How many warnings have been raised.
719 pub fn warning_count(&self) -> u32 {
720 self.warning_count
721 }
722
723 /// Whether the error limit has been reached and the caller should stop.
724 pub fn error_limit_reached(&self) -> bool {
725 self.opts.error_limit != 0 && self.error_count >= self.opts.error_limit
726 }
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732
733 fn session() -> Session {
734 Session::new(Options::new("x86_64-unknown-linux-gnu".parse().unwrap()))
735 }
736
737 #[test]
738 fn a_version_claim_reads_the_way_gcc_prints_one() {
739 // `gcc -dumpfullversion` gives all three, `gcc -dumpversion` gives one, and both are
740 // things a script pastes straight into a flag.
741 let all = |v: &str| v.parse::<GnucVersion>().unwrap();
742 assert_eq!(all("15.1.0"), GnucVersion { major: 15, minor: 1, patch: 0 });
743 assert_eq!(all("15"), GnucVersion { major: 15, minor: 0, patch: 0 });
744 assert_eq!(all("4.2"), GnucVersion { major: 4, minor: 2, patch: 0 });
745 assert!("".parse::<GnucVersion>().is_err());
746 assert!("15.".parse::<GnucVersion>().is_err(), "a trailing dot is a typo, not a zero");
747 assert!("1.2.3.4".parse::<GnucVersion>().is_err());
748 }
749
750 #[test]
751 fn optimisation_levels_parse_the_way_gcc_spells_them() {
752 assert_eq!("".parse::<OptLevel>().unwrap(), OptLevel::O1);
753 assert_eq!("0".parse::<OptLevel>().unwrap(), OptLevel::O0);
754 assert_eq!("2".parse::<OptLevel>().unwrap(), OptLevel::O2);
755 assert_eq!("9".parse::<OptLevel>().unwrap(), OptLevel::O3);
756 assert_eq!("s".parse::<OptLevel>().unwrap(), OptLevel::Os);
757 assert!("q".parse::<OptLevel>().is_err());
758 }
759
760 #[test]
761 fn only_o0_skips_the_optimizer() {
762 assert!(!OptLevel::O0.runs_optimizer());
763 assert!(OptLevel::O1.runs_optimizer());
764 assert!(OptLevel::Oz.runs_optimizer());
765 }
766
767 #[test]
768 fn the_safety_tiers_round_trip_and_nothing_else_is_one() {
769 for tier in [Safety::Off, Safety::Detect, Safety::Enforce, Safety::Kernel] {
770 assert_eq!(tier.as_str().parse::<Safety>().unwrap(), tier);
771 }
772 // `on` is the obvious thing to try and it is not a tier, because which tier somebody
773 // means by it is the whole question document 02 answers.
774 assert!("on".parse::<Safety>().is_err());
775 assert!("".parse::<Safety>().is_err());
776 }
777
778 #[test]
779 fn a_build_that_did_not_ask_for_the_monitor_does_not_get_it() {
780 assert_eq!(Safety::default(), Safety::Off);
781 assert!(!Safety::Off.instruments());
782 assert!(Safety::Detect.instruments());
783 assert!(Safety::Enforce.instruments());
784 assert!(Safety::Kernel.instruments());
785 }
786
787 #[test]
788 fn emit_kinds_round_trip_through_their_names() {
789 for k in [
790 EmitKind::Executable,
791 EmitKind::Object,
792 EmitKind::Asm,
793 EmitKind::Preprocessed,
794 EmitKind::Tast,
795 EmitKind::Ir,
796 EmitKind::MirFinal,
797 ] {
798 assert_eq!(k.as_str().parse::<EmitKind>().unwrap(), k);
799 }
800 }
801
802 #[test]
803 fn errors_are_counted_and_warnings_are_not() {
804 let mut s = session();
805 s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
806 s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
807 assert_eq!(s.error_count(), 1);
808 assert_eq!(s.warning_count(), 1);
809 assert!(s.has_errors());
810 assert_eq!(s.diagnostics().len(), 2);
811 }
812
813 #[test]
814 fn werror_promotes_once_at_the_sink() {
815 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
816 opts.warnings_are_errors = true;
817 let mut s = Session::new(opts);
818 s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
819 assert_eq!(s.error_count(), 1);
820 assert_eq!(s.warning_count(), 0);
821 assert_eq!(s.diagnostics()[0].severity, Severity::Error);
822 }
823
824 #[test]
825 fn the_error_limit_can_be_switched_off() {
826 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
827 opts.error_limit = 0;
828 let mut s = Session::new(opts);
829 for _ in 0..100 {
830 s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
831 }
832 assert!(!s.error_limit_reached());
833 }
834
835 #[test]
836 fn the_session_carries_the_source_map_spans_are_resolved_against() {
837 let mut s = session();
838 let file = s.sources.add("a.c", b"int x;\n".to_vec()).unwrap();
839 let start = s.sources.file(file).start;
840 assert_eq!(s.sources.render_position(start + 4), "a.c:1:5");
841 }
842
843 #[test]
844 fn the_session_carries_the_resolved_target() {
845 let s = session();
846 assert_eq!(s.target.pointer_width, 64);
847 assert!(s.target.char_is_signed);
848 }
849}