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.21")]
23
24mod fs;
25pub mod runtime;
26
27pub use crate::fs::{Dir, FileSystem, Found, IncludeForm, MemoryFileSystem, SearchPath, path_key};
28
29use std::borrow::Cow;
30use std::fmt;
31use std::str::FromStr;
32
33use rucc_base::Interner;
34use rucc_diag::{Diagnostic, Severity, SourceMap};
35use rucc_target::{TargetInfo, Triple};
36
37/// An optimisation level.
38///
39/// `spec/16-performance.md` section 16.4 gives each level a throughput budget and a code
40/// quality budget, and the levels exist to make that tradeoff explicit rather than to be a
41/// dial. There is no `-O4`, because a level nobody can state the contract for is a level
42/// nobody can test.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
44pub enum OptLevel {
45 /// `-O0`. Compile as fast as possible and keep every variable inspectable.
46 #[default]
47 O0,
48 /// `-O1`. The cheap wins, at roughly the cost of `-O0`.
49 O1,
50 /// `-O2`. The full pipeline. This is the level the code quality claim is about.
51 O2,
52 /// `-O3`. `-O2` plus the transformations that trade size for speed.
53 O3,
54 /// `-Os`. Optimise for size, at roughly `-O2` compile time.
55 Os,
56 /// `-Oz`. Optimise for size, aggressively.
57 Oz,
58}
59
60impl OptLevel {
61 /// The flag that selects this level.
62 pub const fn as_flag(self) -> &'static str {
63 match self {
64 OptLevel::O0 => "-O0",
65 OptLevel::O1 => "-O1",
66 OptLevel::O2 => "-O2",
67 OptLevel::O3 => "-O3",
68 OptLevel::Os => "-Os",
69 OptLevel::Oz => "-Oz",
70 }
71 }
72
73 /// Whether this level optimises for size rather than speed.
74 pub const fn is_size(self) -> bool {
75 matches!(self, OptLevel::Os | OptLevel::Oz)
76 }
77
78 /// Whether the middle end runs at all.
79 pub const fn runs_optimizer(self) -> bool {
80 !matches!(self, OptLevel::O0)
81 }
82}
83
84impl fmt::Display for OptLevel {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.write_str(self.as_flag())
87 }
88}
89
90impl FromStr for OptLevel {
91 type Err = ();
92
93 /// Parses the part after `-O`, so `""` is `-O` which GCC treats as `-O1`.
94 fn from_str(s: &str) -> Result<Self, ()> {
95 Ok(match s {
96 "0" => OptLevel::O0,
97 "" | "1" => OptLevel::O1,
98 "2" => OptLevel::O2,
99 // GCC accepts `-O4` and above and treats them as `-O3`. Build systems in the
100 // wild do pass them, so matching that is cheaper than being right.
101 "3" | "4" | "5" | "6" | "7" | "8" | "9" => OptLevel::O3,
102 "s" => OptLevel::Os,
103 "z" => OptLevel::Oz,
104 _ => return Err(()),
105 })
106 }
107}
108
109/// How much of the memory safety monitor is on, from `-fsafety=`.
110///
111/// Design: `spec/safe-memory/15-integration.md` section 15.4. One flag rather than a plane at a
112/// time, because the tiers of `spec/safe-memory/02-threat-model.md` are the product and the
113/// modifiers are how somebody who has read that document departs from one.
114///
115/// The tiers agree about which accesses are checked and disagree about what happens when a check
116/// says no and about how much of the boundary is covered. That is why they are one value here and
117/// not three booleans: a build asks for a tier, and everything else follows from it.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
119pub enum Safety {
120 /// `-fsafety=off`. No checks and no runtime. The default, and what every existing build gets.
121 #[default]
122 Off,
123 /// `-fsafety=detect`. Tier D: report and carry on, for a test run or a fuzzer.
124 Detect,
125 /// `-fsafety=enforce`. Tier E: report and stop, for a program that faces the network.
126 Enforce,
127 /// `-fsafety=kernel`. Tier K: what a kernel can afford, with the allocator and the libc
128 /// wrappers taken out because a kernel has neither.
129 Kernel,
130}
131
132impl Safety {
133 /// The spelling this tier is asked for by, without the flag in front of it.
134 pub const fn as_str(self) -> &'static str {
135 match self {
136 Safety::Off => "off",
137 Safety::Detect => "detect",
138 Safety::Enforce => "enforce",
139 Safety::Kernel => "kernel",
140 }
141 }
142
143 /// Whether checks are inserted at all.
144 ///
145 /// The three tiers that are not `off` all insert the same checks at this milestone. What
146 /// separates them is the reporter and the boundary, which are milestones S2 and S3 in
147 /// `spec/safe-memory/16-milestones.md`.
148 pub const fn instruments(self) -> bool {
149 !matches!(self, Safety::Off)
150 }
151}
152
153impl fmt::Display for Safety {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 f.write_str(self.as_str())
156 }
157}
158
159impl FromStr for Safety {
160 type Err = ();
161
162 /// Parses the part after `-fsafety=`.
163 fn from_str(s: &str) -> Result<Self, ()> {
164 Ok(match s {
165 "off" => Safety::Off,
166 "detect" => Safety::Detect,
167 "enforce" => Safety::Enforce,
168 "kernel" => Safety::Kernel,
169 _ => return Err(()),
170 })
171 }
172}
173
174/// Whether padding participates in the init plane, from `-fsafety-init=`.
175///
176/// Design: `spec/safe-memory/09-type-init-and-races.md` section 9.3.
177///
178/// The correct rule is that a store which writes an object as a whole initializes it as a whole,
179/// padding included, and that a fill done a member at a time leaves the padding alone. That rule
180/// reports a structure filled member by member and then hashed, compared or written to a file,
181/// and it is right to: that is CWE-200 and it is the kernel infoleak KMSAN was built to find.
182///
183/// It is also every third program in a userspace corpus, where the bytes never leave the process
184/// and nobody is hunting an infoleak. So section 9.3 makes it a flag and splits the default:
185/// padding participates for the kernel profile, where the leak is the thing being looked for, and
186/// does not for library code, where it would be a torrent of reports about programs nobody is
187/// worried about. Document 12's scoreboard reports the two configurations separately for the same
188/// reason.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
190pub enum Padding {
191 /// `-fsafety-init=nopadding`. A store through a member says the padding after it holds
192 /// something too, so a record filled a member at a time comes out entirely written.
193 #[default]
194 Ignored,
195 /// `-fsafety-init=padding`. A store through a member says only what it wrote, which is
196 /// section 9.3's rule and is what makes the infoleak visible.
197 Tracked,
198}
199
200impl Padding {
201 /// The spelling this is asked for by, without the flag in front of it.
202 pub const fn as_str(self) -> &'static str {
203 match self {
204 Padding::Ignored => "nopadding",
205 Padding::Tracked => "padding",
206 }
207 }
208}
209
210impl fmt::Display for Padding {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.write_str(self.as_str())
213 }
214}
215
216impl FromStr for Padding {
217 type Err = ();
218
219 /// Parses the part after `-fsafety-init=`.
220 fn from_str(s: &str) -> Result<Self, ()> {
221 Ok(match s {
222 "nopadding" => Padding::Ignored,
223 "padding" => Padding::Tracked,
224 _ => return Err(()),
225 })
226 }
227}
228
229/// Whether an access has to stay inside the member it names, from `-fsafety-subobject`.
230///
231/// Design: `spec/safe-memory/09-type-init-and-races.md` section 9.4, which is row S4 of document
232/// 03 and is the class Fil-C, CHERI by default and ARM MTE all miss. Their metadata is per
233/// allocation and a member is not an allocation, so an overflow from one member of a structure
234/// into the next is invisible to all three. The type plane is byte granular, so it is not
235/// invisible here.
236///
237/// A flag rather than a default because of what a store means. C 6.5 says a store to allocated
238/// storage sets that storage's effective type, so a write that leaves one member and lands in the
239/// next is, read literally, a program retyping bytes it owns. Every buffer that gets reused for a
240/// second kind of value does the same thing on purpose. So the question a store asks is only asked
241/// when somebody has said they want it asked, and what they get in return is the write half of
242/// S4 that nothing else catches.
243///
244/// The read half is not behind this and never was: a read that disagrees with the plane is
245/// judgement J1 at every tier, because reading bytes back through a type they were not stored
246/// through is undefined however the pointer got there.
247#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
248pub enum Subobject {
249 /// No `-fsafety-subobject`. A store records what it wrote and is asked nothing.
250 #[default]
251 Off,
252 /// `-fsafety-subobject`. A store asks the plane whether the bytes it is about to write agree
253 /// with the type it writes them through, which catches an overflow out of a member into a
254 /// member of a different type.
255 ///
256 /// Two adjacent members of the same type are indistinguishable to this, which section 9.4
257 /// states plainly: `struct { int a; int b; }` overflowing from `a` into `b` writes `int` over
258 /// `int` and there is nothing for the plane to disagree with. That is what
259 /// `-fsafety-subobject=strict` is for and it is not here yet.
260 Members,
261}
262
263impl Subobject {
264 /// The spelling this is asked for by, without the flag in front of it.
265 pub const fn as_str(self) -> &'static str {
266 match self {
267 Subobject::Off => "off",
268 Subobject::Members => "members",
269 }
270 }
271
272 /// Whether a store asks the type plane anything.
273 pub const fn asks(self) -> bool {
274 matches!(self, Subobject::Members)
275 }
276}
277
278impl fmt::Display for Subobject {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 f.write_str(self.as_str())
281 }
282}
283
284/// Whether the `restrict` contract is checked, from `-fsafety-restrict`.
285///
286/// Design: `spec/safe-memory/09-type-init-and-races.md` section 9.6, which is row Y8 of document
287/// 03 and is judgement J8. C 6.7.3.1 says that if an object reachable through a `restrict` pointer
288/// declared in a block is modified anywhere in that block, every access to that object in that
289/// block goes through that pointer. Nothing about one access decides it, which is why document 04
290/// section 4.6 keeps it out of J1.
291///
292/// A flag rather than a default for two reasons, and neither of them is the one
293/// [`Subobject`] has. The first is cost, and it is a bad distribution rather than a large number:
294/// an access inside a block that declares `restrict` pointers pays a scan of that block's record,
295/// and blocks that declare them are the numeric kernels and the `mem` functions, which is exactly
296/// where the hot loops are. Code with no `restrict` in it pays nothing at all. The second is that
297/// the record is the union of what each pointer reached, so two pointers striding through one array
298/// without ever landing on the same byte are reported, and by the letter of the standard those are
299/// different objects and that is not a violation.
300///
301/// The second one is not an imprecision to apologise for. This check exists because a violated
302/// `restrict` is a miscompilation, and what the optimizer acts on is that the ranges are disjoint,
303/// so a program the union rule reports is a program the optimizer is entitled to break. It is
304/// still a report about a program the standard permits, which is a decision that belongs to the
305/// build rather than to this compiler.
306#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
307pub enum Promise {
308 /// No `-fsafety-restrict`. An access says which `restrict` pointer it went through, because
309 /// the alias analysis reads that, and nothing asks whether two of them met.
310 #[default]
311 Off,
312 /// `-fsafety-restrict`. Every block that declares `restrict` pointers keeps a record of what
313 /// each of them reached, and every access through one asks whether another got there first.
314 Blocks,
315}
316
317impl Promise {
318 /// The spelling this is asked for by, without the flag in front of it.
319 pub const fn as_str(self) -> &'static str {
320 match self {
321 Promise::Off => "off",
322 Promise::Blocks => "blocks",
323 }
324 }
325
326 /// Whether a block keeps a record and an access asks about it.
327 pub const fn checks(self) -> bool {
328 matches!(self, Promise::Blocks)
329 }
330}
331
332impl fmt::Display for Promise {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 f.write_str(self.as_str())
335 }
336}
337
338/// How far a name reaches outside a shared library when nothing in the source said.
339///
340/// `-fvisibility=`, which is written on every cmake project that cares about its exports and is
341/// the way a library ships a small documented interface instead of every name it happens to
342/// define. The attribute in the source wins wherever one was written, which is what makes the
343/// flag a default rather than an override and what lets `-fvisibility=hidden` be put on a whole
344/// tree and the dozen exported names marked one at a time.
345///
346/// Three answers to four spellings. `internal` is `hidden` plus a promise about never taking the
347/// address across a component boundary, and nothing here derives anything from that promise, so
348/// what it gets is the same symbol with a weaker claim on it.
349#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
350pub enum Visibility {
351 /// `-fvisibility=default`. Exported and interposable, which is what a name gets when the flag
352 /// is not written at all and what gcc does by default too.
353 #[default]
354 Default,
355 /// `-fvisibility=hidden` and `-fvisibility=internal`. Not in the dynamic symbol table.
356 Hidden,
357 /// `-fvisibility=protected`. In the dynamic symbol table, and a reference from inside the
358 /// library binds to the definition inside it.
359 Protected,
360}
361
362impl Visibility {
363 /// The spelling this is asked for by, without the flag in front of it.
364 ///
365 /// One spelling each, so `internal` is not here: it is a way of asking for `hidden` rather
366 /// than an answer of its own.
367 pub const fn as_str(self) -> &'static str {
368 match self {
369 Visibility::Default => "default",
370 Visibility::Hidden => "hidden",
371 Visibility::Protected => "protected",
372 }
373 }
374}
375
376impl fmt::Display for Visibility {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 f.write_str(self.as_str())
379 }
380}
381
382impl FromStr for Visibility {
383 type Err = ();
384
385 /// Parses the part after `-fvisibility=`.
386 fn from_str(s: &str) -> Result<Self, ()> {
387 Ok(match s {
388 "default" => Visibility::Default,
389 "hidden" | "internal" => Visibility::Hidden,
390 "protected" => Visibility::Protected,
391 _ => return Err(()),
392 })
393 }
394}
395
396/// How the debug sections are compressed, which is what `-gz` asks.
397///
398/// Debug information is much larger than the code it describes and almost never read, so an ELF
399/// section holding it may be stored compressed: the section keeps its name, gains the
400/// `SHF_COMPRESSED` flag and starts with a header saying what it decompresses to, and every reader
401/// that understands the flag unpacks it on the way in. A distribution that ships debug symbols for
402/// everything it builds saves more from this than from anything else it passes.
403///
404/// This compiler writes no debug sections at all yet, so every answer here produces the same bytes,
405/// and an object built with `-gz=zstd` is identical to one built without the flag. It is recorded
406/// rather than dropped for the reason section 4.1 gives for the rest of the family: the answer has
407/// to be sitting in the options on the day `rucc-debug` has something to compress, and a build that
408/// asked for it and got silence would have no way of noticing the difference.
409#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
410pub enum Compress {
411 /// `-gz=none`, and what a command line that says nothing gets. gcc's default is the same.
412 #[default]
413 None,
414 /// `-gz` and `-gz=zlib`. The ELF way, with the `SHF_COMPRESSED` flag and an `Elf64_Chdr` in
415 /// front of the data. Bare `-gz` means this one, which is worth knowing because the manual
416 /// describes the flag without saying so.
417 Zlib,
418 /// `-gz=zlib-gnu`. The older way, where the section is renamed from `.debug_info` to
419 /// `.zdebug_info` and carries `ZLIB` and a length instead of a real header. Kept because
420 /// binutils still reads it and some build systems still ask for it by name.
421 ZlibGnu,
422 /// `-gz=zstd`. The same arrangement as `Zlib` with a different algorithm in the header, which
423 /// packs debug information smaller and unpacks it faster.
424 Zstd,
425}
426
427impl Compress {
428 /// The spelling this is asked for by, without the `-gz=` in front of it.
429 pub const fn as_str(self) -> &'static str {
430 match self {
431 Compress::None => "none",
432 Compress::Zlib => "zlib",
433 Compress::ZlibGnu => "zlib-gnu",
434 Compress::Zstd => "zstd",
435 }
436 }
437}
438
439impl fmt::Display for Compress {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441 f.write_str(self.as_str())
442 }
443}
444
445impl FromStr for Compress {
446 type Err = ();
447
448 /// Parses the part after `-gz=`. Bare `-gz` is not this function's business because there is
449 /// nothing after the flag to hand it.
450 fn from_str(s: &str) -> Result<Self, ()> {
451 Ok(match s {
452 "none" => Compress::None,
453 "zlib" => Compress::Zlib,
454 "zlib-gnu" => Compress::ZlibGnu,
455 "zstd" => Compress::Zstd,
456 _ => return Err(()),
457 })
458 }
459}
460
461/// How many processes the link time work is spread over, which is what `-flto=` takes.
462///
463/// Named for the flag rather than for what it counts, because `Jobs` in the driver is already the
464/// answer to how many files are compiled at once and the two numbers are not the same number.
465///
466/// The link time half of link time optimization is where all of the time goes, because it is the
467/// half that has the whole program in front of it, and gcc's answer is to cut the program into
468/// pieces and generate code for the pieces at once. This says how many at once.
469#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
470pub enum LtoJobs {
471 /// Bare `-flto`, and `-flto=1`. One process, which is what gcc does when the flag is written
472 /// without a number after it.
473 #[default]
474 One,
475 /// `-flto=auto`. As many as the machine has, worked out when the link runs.
476 Auto,
477 /// `-flto=jobserver`. As many as `make` is willing to hand out, asked for through the
478 /// jobserver pipe it puts in the environment, which is the only answer that does not fight
479 /// with the rest of a parallel build for the same cores.
480 Jobserver,
481 /// `-flto=<n>`. Exactly that many. gcc refuses a zero, so this is never one.
482 Count(u32),
483}
484
485impl FromStr for LtoJobs {
486 type Err = ();
487
488 /// Parses the part after `-flto=`. A number has to be positive, which is gcc's rule: `-flto=0`
489 /// is refused rather than read as `-fno-lto`.
490 fn from_str(s: &str) -> Result<Self, ()> {
491 Ok(match s {
492 "auto" => LtoJobs::Auto,
493 "jobserver" => LtoJobs::Jobserver,
494 _ => match s.parse::<u32>() {
495 Ok(1) => LtoJobs::One,
496 Ok(n) if n > 1 => LtoJobs::Count(n),
497 _ => return Err(()),
498 },
499 })
500 }
501}
502
503impl fmt::Display for LtoJobs {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 match self {
506 LtoJobs::One => f.write_str("1"),
507 LtoJobs::Auto => f.write_str("auto"),
508 LtoJobs::Jobserver => f.write_str("jobserver"),
509 LtoJobs::Count(n) => write!(f, "{n}"),
510 }
511 }
512}
513
514/// How the program is cut up before the link time work is spread over it, from `-flto-partition=`.
515///
516/// A partition is a set of functions that are generated together, and where the cuts fall decides
517/// both how well the work spreads and how much is visible from inside one piece. The names are
518/// gcc's and so are the shapes: one piece per input file, pieces balanced by size, one piece for
519/// the whole program, a piece per function, or no partitioning at all.
520#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
521pub enum Partition {
522 /// `-flto-partition=balanced`, and what gcc does when nothing asks. Pieces of roughly equal
523 /// size, which is the answer that spreads the work best and is why it is the default.
524 #[default]
525 Balanced,
526 /// `-flto-partition=1to1`. One piece per input file, which keeps the generated code in the
527 /// same order the inputs were in and is what a build comparing two outputs wants.
528 OneToOne,
529 /// `-flto-partition=one`. The whole program in one piece, which is the most the optimizer can
530 /// see at once and the least the work can be spread over.
531 One,
532 /// `-flto-partition=max`. A piece per function, which is the other end of the same trade.
533 Max,
534 /// `-flto-partition=none`. No partitioning, and no streaming back out to be generated in
535 /// pieces either.
536 None,
537}
538
539impl Partition {
540 /// The spelling this is asked for by, without the `-flto-partition=` in front of it.
541 pub const fn as_str(self) -> &'static str {
542 match self {
543 Partition::Balanced => "balanced",
544 Partition::OneToOne => "1to1",
545 Partition::One => "one",
546 Partition::Max => "max",
547 Partition::None => "none",
548 }
549 }
550}
551
552impl fmt::Display for Partition {
553 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
554 f.write_str(self.as_str())
555 }
556}
557
558impl FromStr for Partition {
559 type Err = ();
560
561 /// Parses the part after `-flto-partition=`.
562 fn from_str(s: &str) -> Result<Self, ()> {
563 Ok(match s {
564 "balanced" => Partition::Balanced,
565 "1to1" => Partition::OneToOne,
566 "one" => Partition::One,
567 "max" => Partition::Max,
568 "none" => Partition::None,
569 _ => return Err(()),
570 })
571 }
572}
573
574/// What the `-flto` family asked for, which is a whole optimization this compiler does not do yet.
575///
576/// Link time optimization is the optimizer run once over the whole program instead of once per
577/// translation unit, which is the only way an inliner ever sees across a file boundary and is
578/// where most of what is left on the table after `-O2` is. `spec/09-optimizer.md` says how it will
579/// work here: the IR goes into a section of the object, the driver finds those sections at link
580/// time, merges them into one module and generates code with everything visible.
581///
582/// None of that exists, so the whole family is read, checked and recorded rather than acted on.
583/// That is a different answer from the one `-gsplit-dwarf` gets in the same specification, and the
584/// difference is what ignoring each of them does. Ignoring `-gsplit-dwarf` means a file a build
585/// asked for never appears. Ignoring this means a program that is correct and slower than it could
586/// have been, which is what section 4.1 means by a hint about speed, and which is also what every
587/// compilation at `-O0` already is.
588///
589/// The other half of the argument is about the object. gcc's `-flto` object holds the bytecode and
590/// no machine code at all, so it is only useful to a link that knows about it; the objects here
591/// always hold the code, which is what `-ffat-lto-objects` asks gcc for. So a build that passes
592/// `-flto` to this compiler gets objects that are strictly more usable than the ones it would have
593/// got, rather than different ones.
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
595pub struct Lto {
596 /// Whether the last of `-flto` and `-fno-lto` on the command line was the first of the two.
597 pub requested: bool,
598 /// How many processes to spread the link time work over.
599 pub jobs: LtoJobs,
600 /// How the program is cut up before the work is spread.
601 pub partition: Partition,
602 /// How hard to compress the IR on its way into the object, from `-flto-compression-level=`,
603 /// where `None` means whatever the compressor does when nobody says. Between 0 and 19, which
604 /// is zstd's range and is the range gcc checks against.
605 pub compression: Option<u8>,
606}
607
608/// What the profile reading half of the `-fprofile` family asked for.
609///
610/// A profile is a count per edge, gathered by running a build of the program that was instrumented
611/// to count, and read back on a second compilation so that the optimizer knows which way each
612/// branch actually went. It is worth more than any single optimization, because almost everything
613/// the optimizer decides is a guess about a frequency that the counts simply state.
614///
615/// Nothing here reads one yet, so this is recorded rather than acted on, and the family splits in
616/// two rather than being taken or refused as a whole. The half recorded here is the half that only
617/// costs speed when it is ignored: a build that asks to read a profile and is not read one gets the
618/// program it would have got anyway, which is what section 4.1 means by a hint about speed. The
619/// other half writes files, and that half is refused by the driver rather than landing here, on the
620/// same reading `-gsplit-dwarf` gets: a program instrumented by `-fprofile-generate` writes a
621/// `.gcda` when it runs and `-ftest-coverage` writes a `.gcno` beside the object, and ignoring
622/// either means a build waits for a file that never arrives and then quietly optimizes against no
623/// counts at all.
624///
625/// gcc's own measurement is the argument for the split. `-fprofile-use` on a file with no counts
626/// beside it produces an object byte for byte identical to the one no flag produces, and warns; the
627/// same file under `-fprofile-generate` grows from 71 bytes of code to 375 with 296 bytes of
628/// counters beside it. So one half of the family is already a no-op in gcc when there is nothing to
629/// read, and the other half is never one.
630#[derive(Debug, Clone, PartialEq, Eq, Default)]
631pub struct Profile {
632 /// Whether the last of `-fprofile-use` and `-fno-profile-use` on the command line was the
633 /// first of the two.
634 pub requested: bool,
635 /// Where to read the counts from, from `-fprofile-use=<path>`, where `None` means beside the
636 /// object the way gcc looks when nobody says. A directory or a file, which is gcc's rule and
637 /// is not something this can tell apart without looking at the filesystem.
638 pub path: Option<String>,
639 /// Where the whole family's files live, from `-fprofile-dir=`. Separate from `path` because
640 /// gcc keeps them separate: this one moves the counts for the generating half as well.
641 pub dir: Option<String>,
642 /// Whether the path recorded in those files is made absolute, from `-fprofile-abs-path`. It is
643 /// what a build with several object directories under one source tree needs so that two files
644 /// of the same name do not land on one set of counts.
645 pub absolute: bool,
646 /// Whether counts that do not add up are repaired rather than refused, from
647 /// `-fprofile-correction`. A program that forked or was killed while it ran leaves counts that
648 /// no single execution could have produced, and this says to make the best of them.
649 pub correction: bool,
650 /// Whether the parts of the program the training run never reached are optimized as if they
651 /// were cold rather than as if nothing were known about them, from `-fprofile-partial-training`.
652 pub partial_training: bool,
653}
654
655/// Which functions get a stack protector, which is what the `-fstack-protector` family asks.
656///
657/// A canary is a word the prologue copies into the frame above everything a local can be written
658/// through, and the epilogue compares it against the copy the runtime still holds before it
659/// returns. A write that runs off the end of a local and keeps going passes the canary on its way
660/// to the return address, so a function that returns with the word changed calls
661/// `__stack_chk_fail` instead of returning at all.
662///
663/// Which functions are worth the slot and the comparison is what the three levels disagree about,
664/// and the middle one is the one that matters: every distribution has built its packages with
665/// `-fstack-protector-strong` for a decade, so a compiler that cannot take the flag cannot be the
666/// `CC` of a package build whatever else it can do.
667#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
668pub enum Protector {
669 /// `-fno-stack-protector`, and what a command line that says nothing gets. gcc's own default
670 /// is the same, and it is the distributions rather than the compiler that turn it on.
671 #[default]
672 None,
673 /// `-fstack-protector`. A function with a local array of at least eight bytes, or one whose
674 /// stack grows while it runs.
675 Buffers,
676 /// `-fstack-protector-strong`. Any of those, and any function with a local array at all, a
677 /// local holding one, or a local whose address is taken.
678 Strong,
679 /// `-fstack-protector-all`. Every function that has a frame.
680 All,
681}
682
683/// What overflows rather than being undefined, from `-fwrapv` and its relatives.
684///
685/// C says a signed addition that overflows and a pointer that walks off the end of the object it
686/// points into are both undefined, and an optimizer that believes it reads a great deal into every
687/// loop: that a counter going up one at a time never turns round, that an index widened to an
688/// address may be widened before the arithmetic rather than after, that a bound is reached. These
689/// flags withdraw exactly that. They do not make the program mean something else, they make it mean
690/// less, and the code that asks for them is code that overflows on purpose and wants the answer the
691/// machine gives rather than the answer the standard declines to give.
692///
693/// Two of them because gcc has two, and a build that wants one usually wants the other. Signed
694/// arithmetic and pointer arithmetic are separate assumptions and a kernel turns both off.
695///
696/// `-ftrapv` is the third answer to the first question and is here for that reason. Undefined,
697/// wrapping and stopping are the three things a signed overflow can be, and a command line picks
698/// one of them: the last of `-fwrapv` and `-ftrapv` wins, which is gcc's behaviour and what makes
699/// them one field rather than two that can both be set.
700#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
701pub struct Wrapping {
702 /// Whether signed arithmetic wraps, from `-fwrapv`.
703 pub signed: bool,
704 /// Whether pointer arithmetic wraps, from `-fwrapv-pointer`.
705 pub pointer: bool,
706 /// Whether a signed overflow stops the program instead, from `-ftrapv`.
707 ///
708 /// Never set at the same time as [`Wrapping::signed`], since a program cannot both wrap and
709 /// stop, and the driver is what keeps that true by clearing each when the other is asked for.
710 pub trap: bool,
711}
712
713impl Wrapping {
714 /// Both of them, which is what `-fno-strict-overflow` asks for.
715 ///
716 /// gcc says so itself: its help text for `-fstrict-overflow` reads "negated as `-fwrapv`
717 /// `-fwrapv-pointer`", so the older flag is a name for the pair rather than a third knob. And
718 /// asking for wrapping is asking for not stopping, so this is the whole answer and not two
719 /// thirds of one.
720 pub const ALL: Self = Self { signed: true, pointer: true, trap: false };
721
722 /// Neither, which is the default and what a command line that says nothing about any of this
723 /// gets.
724 pub const NONE: Self = Self { signed: false, pointer: false, trap: false };
725}
726
727/// A list of `old=new` rewrites to apply to a path before it is written into the output, which is
728/// what the `-f*-prefix-map=` family asks for.
729///
730/// The point of them is a build whose output does not depend on where it was built. A path is the
731/// last thing in an object that a second machine cannot reproduce: two people who check out the
732/// same commit and run the same compiler get the same instructions and different `__FILE__`
733/// strings, and a distribution that wants to prove its binaries came from its sources has to make
734/// that difference go away. So the build says what its root is called, and every path that would
735/// name the real one names that instead.
736///
737/// The rule is a plain string prefix and nothing more, which is worth saying because it looks like
738/// it ought to be about directories. gcc compares the characters, so `s=B` turns `sub/h.h` into
739/// `Bub/h.h`, and an empty `old` matches everything and puts `new` in front of it. The path
740/// compared against is the one the search found, so a header reached through a relative `-I` is
741/// mapped as a relative path and the same header reached through an absolute one is mapped as an
742/// absolute path.
743#[derive(Debug, Clone, Default, PartialEq, Eq)]
744pub struct PrefixMap {
745 /// The rewrites, in the order the command line gave them.
746 entries: Vec<(String, String)>,
747}
748
749impl PrefixMap {
750 /// No rewrites, which is what a command line that says nothing about this gets.
751 #[must_use]
752 pub fn new() -> Self {
753 Self::default()
754 }
755
756 /// Whether nothing was asked for, which is the case worth not spending anything on.
757 #[must_use]
758 pub fn is_empty(&self) -> bool {
759 self.entries.is_empty()
760 }
761
762 /// Adds a rewrite, which is what one flag on the command line is.
763 pub fn push(&mut self, old: impl Into<String>, new: impl Into<String>) {
764 self.entries.push((old.into(), new.into()));
765 }
766
767 /// The two halves of one flag's argument, split at the last `=` rather than the first.
768 ///
769 /// That is where gcc splits it, and it is the answer that makes a path containing an `=`
770 /// mappable: `-ffile-prefix-map=/home/a=b=/src` maps the directory `/home/a=b`. The cost is
771 /// that a replacement cannot contain one, which is the rarer thing to want. `None` when there
772 /// is no `=` at all, which gcc refuses rather than reading as a mapping to nothing.
773 #[must_use]
774 pub fn split(arg: &str) -> Option<(&str, &str)> {
775 arg.rsplit_once('=')
776 }
777
778 /// `path` with the last rewrite that matches it applied, or `path` where none does.
779 ///
780 /// The last rather than the first, because that is gcc's answer and because it is the one a
781 /// build relies on: a mapping set for the whole project and a narrower one set for one
782 /// directory is a command line where the second is meant to win.
783 #[must_use]
784 pub fn apply<'a>(&self, path: &'a str) -> Cow<'a, str> {
785 for (old, new) in self.entries.iter().rev() {
786 if let Some(rest) = path.strip_prefix(old.as_str()) {
787 return Cow::Owned(format!("{new}{rest}"));
788 }
789 }
790 Cow::Borrowed(path)
791 }
792}
793
794/// The three answers to the question the `-f*-prefix-map=` family asks, which is one question
795/// asked about three kinds of output.
796///
797/// They are separate because gcc's flags are separate and a build uses that: a distribution maps
798/// its debug paths to something a debugger can find the sources under and leaves `__FILE__` alone,
799/// or maps `__FILE__` so that an assertion message does not name a build directory and leaves the
800/// debug info pointing at the real tree. `-ffile-prefix-map=` is the shorthand for all three and is
801/// what a build that simply wants to be reproducible writes.
802#[derive(Debug, Clone, Default, PartialEq, Eq)]
803pub struct PrefixMaps {
804 /// What `__FILE__` and `__BASE_FILE__` are rewritten by, from `-fmacro-prefix-map=`.
805 ///
806 /// The only one of the three this compiler acts on today, because it is the only one whose
807 /// output exists: `__FILE__` is a string literal in the binary and an assertion message a user
808 /// reads.
809 pub macros: PrefixMap,
810 /// What a path in the debug info is rewritten by, from `-fdebug-prefix-map=`.
811 ///
812 /// Nothing reads this yet, because no debug info is generated yet. It is kept rather than
813 /// dropped so that the crate that generates it has the answer waiting rather than a flag to
814 /// go and add, and `crates/rucc-debug` says so where the work will start.
815 pub debug: PrefixMap,
816 /// What a path in the profile data is rewritten by, from `-fprofile-prefix-map=`.
817 ///
818 /// Nothing reads this yet either, and for the same reason: there is no profile data.
819 pub profile: PrefixMap,
820}
821
822/// How far a multiply and an addition may be fused into one rounding, from `-ffp-contract=`.
823///
824/// A fused multiply add computes `a * b + c` with one rounding instead of two, which is both
825/// faster and closer to the exact answer, and is therefore a different answer. C lets an
826/// implementation do it within one expression and lets a program turn it off with the
827/// `FP_CONTRACT` pragma, gcc does it across a whole function by default, and code that cares about
828/// reproducing a result bit for bit turns it off everywhere.
829///
830/// This is the command line's answer to that question, and it is carried into the IR as an
831/// attribute on each function so that the code generator still has it by the time it would matter.
832/// It is a separate question from the flag on one instruction: a licence granted to an expression
833/// the optimizer has since taken apart is a licence about operations that no longer sit together,
834/// and only the function level answer survives that.
835#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
836pub enum Contract {
837 /// `-ffp-contract=off`. Never, so every rounding the source asked for happens.
838 ///
839 /// The default here, which is not gcc's. gcc defaults to `fast` under its own dialects and to
840 /// `off` under a strict `-std=`, and the reason the default is this one anyway is that nothing
841 /// in this compiler fuses anything: the two settings are the same program today, and of the two
842 /// this is the one that does not write a licence nobody reads onto every function in the file.
843 /// The day the code generator learns to fuse, the default moves to gcc's, and that is a change
844 /// to the code generator rather than to this flag.
845 #[default]
846 Off,
847 /// `-ffp-contract=on`. Within one expression, which is what C allows an implementation to do
848 /// without being asked.
849 On,
850 /// `-ffp-contract=fast`. Anywhere in the function, across statements and across whatever the
851 /// optimizer has rearranged, which is what gcc does under its own dialects.
852 Fast,
853}
854
855impl Contract {
856 /// The spelling after the `=`.
857 pub const fn as_str(self) -> &'static str {
858 match self {
859 Contract::Off => "off",
860 Contract::On => "on",
861 Contract::Fast => "fast",
862 }
863 }
864}
865
866impl fmt::Display for Contract {
867 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
868 f.write_str(self.as_str())
869 }
870}
871
872impl FromStr for Contract {
873 type Err = ();
874
875 fn from_str(s: &str) -> Result<Self, ()> {
876 Ok(match s {
877 "off" => Contract::Off,
878 "on" => Contract::On,
879 "fast" => Contract::Fast,
880 _ => return Err(()),
881 })
882 }
883}
884
885impl Protector {
886 /// The spelling this is asked for by, which is the whole flag rather than a part of one,
887 /// because these are four flags and not one flag with an argument.
888 pub const fn as_str(self) -> &'static str {
889 match self {
890 Protector::None => "-fno-stack-protector",
891 Protector::Buffers => "-fstack-protector",
892 Protector::Strong => "-fstack-protector-strong",
893 Protector::All => "-fstack-protector-all",
894 }
895 }
896}
897
898impl fmt::Display for Protector {
899 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
900 f.write_str(self.as_str())
901 }
902}
903
904/// Which control flow transfers are checked, which is what `-fcf-protection=` asks.
905///
906/// Two mechanisms and one flag, because the hardware turns them on together and a program built
907/// for one and not the other is a program with a hole in whichever half was left out. The forward
908/// edge is an indirect call or jump, and it is checked by a landing pad at every address one is
909/// allowed to arrive at, so a corrupted function pointer reaches somewhere somebody meant rather
910/// than any byte of the program. The backward edge is a return, and it is checked against a second
911/// copy of the return address the program cannot write to, which needs no instructions at all: the
912/// machine keeps the copy and the loader turns it on.
913///
914/// Which is why the marker matters as much as the code. An object says in a note which halves it
915/// was built for, the linker takes the intersection over every input, and the loader turns on what
916/// survives. One object built without the note is enough to turn the whole program's protection
917/// off, so the note goes in even for a mode that changes no instruction.
918#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
919pub enum Control {
920 /// `-fcf-protection=none` and `-fno-cf-protection`, and what a command line that says nothing
921 /// gets. gcc's own default is the same on the targets this compiler has a back end for.
922 #[default]
923 None,
924 /// `-fcf-protection=branch`. The forward edge alone: a landing pad at every function, and a
925 /// note that asks for the check on indirect transfers and not on returns.
926 Branch,
927 /// `-fcf-protection=return`. The backward edge alone, which is the note and nothing else,
928 /// since the copy of the return address is the machine's own and no instruction maintains it.
929 Return,
930 /// `-fcf-protection=full`, and what the bare `-fcf-protection` means. Both halves.
931 Full,
932 /// `-fcf-protection=check`. Asks that the compilation be checked for compatibility with the
933 /// mode rather than built in it, so nothing is instrumented and no note is written, which is
934 /// exactly what gcc emits for it.
935 Check,
936}
937
938impl Control {
939 /// Whether a landing pad goes at the top of every function.
940 #[must_use]
941 pub const fn branch(self) -> bool {
942 matches!(self, Control::Branch | Control::Full)
943 }
944
945 /// Whether returns are asked to be checked against the machine's own copy.
946 #[must_use]
947 pub const fn ret(self) -> bool {
948 matches!(self, Control::Return | Control::Full)
949 }
950
951 /// Whether anything at all is asked for, which is what decides whether the file says what it
952 /// was built for.
953 ///
954 /// False for the two modes that build nothing. [`Control::None`] asks for nothing and
955 /// [`Control::Check`] asks that the compilation be looked at rather than changed, and gcc
956 /// writes no note for either.
957 #[must_use]
958 pub const fn any(self) -> bool {
959 self.branch() || self.ret()
960 }
961
962 /// What the argument was spelled as, which is the part after the equals sign.
963 pub const fn as_str(self) -> &'static str {
964 match self {
965 Control::None => "none",
966 Control::Branch => "branch",
967 Control::Return => "return",
968 Control::Full => "full",
969 Control::Check => "check",
970 }
971 }
972}
973
974impl fmt::Display for Control {
975 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
976 f.write_str(self.as_str())
977 }
978}
979
980impl FromStr for Control {
981 type Err = ();
982
983 /// Parses the part after `-fcf-protection=`.
984 fn from_str(s: &str) -> Result<Self, ()> {
985 Ok(match s {
986 "none" => Control::None,
987 "branch" => Control::Branch,
988 "return" => Control::Return,
989 "full" => Control::Full,
990 "check" => Control::Check,
991 _ => return Err(()),
992 })
993 }
994}
995
996/// Where the call `-pg` puts at the top of every function goes, which `-mfentry` chooses.
997///
998/// Two conventions for one job, and the difference is what the hook can see when it runs. See
999/// [`rucc_target::Trace`] for what each of them is and why a kernel needs the earlier one.
1000///
1001/// A third answer, because a command line that named neither has not asked a question: the
1002/// platform's own answer is the one it gets, and that is a fact about the target rather than about
1003/// the flags, so it is settled where the target is known and not here.
1004#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1005pub enum Hook {
1006 /// Whichever the platform puts first, which is what a command line that said neither gets.
1007 #[default]
1008 Platform,
1009 /// `-mfentry`. In front of the prologue, so the return address is the top thing on the stack
1010 /// and the arguments are still where the call left them.
1011 Early,
1012 /// `-mno-fentry`. Once the frame is taken, so the hook can walk back through the frame pointer,
1013 /// which is why a function that has this one is given a frame pointer whatever else was said.
1014 Late,
1015}
1016
1017impl Hook {
1018 /// That answer as it is written on a command line, which is what `--print-config` reports.
1019 #[must_use]
1020 pub const fn as_str(self) -> &'static str {
1021 match self {
1022 Hook::Platform => "platform",
1023 Hook::Early => "fentry",
1024 Hook::Late => "mcount",
1025 }
1026 }
1027
1028 /// Whether the call goes in front of the prologue, given what the platform puts first.
1029 #[must_use]
1030 pub const fn early(self, fentry: bool) -> bool {
1031 match self {
1032 Hook::Platform => fentry,
1033 Hook::Early => true,
1034 Hook::Late => false,
1035 }
1036 }
1037}
1038
1039impl fmt::Display for Hook {
1040 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1041 f.write_str(self.as_str())
1042 }
1043}
1044
1045/// How much room at the top of every function is reserved for somebody to write over later, which
1046/// `-fpatchable-function-entry=` asks for.
1047///
1048/// Room rather than instructions. What goes there is a run of the shortest instruction the machine
1049/// has that does nothing, and the point of them is that they are never executed for long: a tracer
1050/// or a live patcher overwrites them with a jump or a call once the program is running, and what it
1051/// needs from the compiler is a known address, a known number of bytes, and a promise that nothing
1052/// in the function jumps into the middle of them.
1053///
1054/// Two numbers because the room can be on either side of the function's own label, and the two
1055/// sides are not the same thing. Room after the label is room inside the function, which is what a
1056/// patcher that redirects a call into the function wants. Room in front of the label is outside it,
1057/// so what goes there is reached only by something that already knows the address, and a patcher
1058/// that wants somewhere to put a whole instruction it can reach from the first one needs it.
1059///
1060/// The address recorded for the function is the start of the room, which is the front of the part
1061/// before the label when there is one and the front of the part after it when there is not.
1062#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1063pub struct Patchable {
1064 /// How many bytes in total, which is the first number and the one a command line must give.
1065 pub total: u32,
1066 /// How many of them go in front of the function's own label, which is the second number and is
1067 /// zero on a command line that gave one number.
1068 pub before: u32,
1069}
1070
1071impl Patchable {
1072 /// Whether any room at all was asked for, which is what decides whether a function gets a
1073 /// record.
1074 ///
1075 /// `=0` is a command line that asked for none, and gcc accepts it and writes nothing, so the
1076 /// question is about the number rather than about whether the flag was written.
1077 #[must_use]
1078 pub const fn any(self) -> bool {
1079 self.total > 0
1080 }
1081
1082 /// How many bytes go after the function's own label, which is the rest of them.
1083 #[must_use]
1084 pub const fn after(self) -> u32 {
1085 self.total - self.before
1086 }
1087}
1088
1089impl FromStr for Patchable {
1090 type Err = ();
1091
1092 /// Parses the part after `-fpatchable-function-entry=`, which is a number or two of them.
1093 ///
1094 /// A second number larger than the first is refused rather than clamped, because it asks for
1095 /// more room in front of the label than there is room at all and there is no reading of that a
1096 /// caller meant. So is a third, and so is anything that is not a number, which is what gcc does
1097 /// with each of them.
1098 fn from_str(s: &str) -> Result<Self, ()> {
1099 let (total, before) = match s.split_once(',') {
1100 Some((total, before)) => (total, before),
1101 None => (s, "0"),
1102 };
1103 let total: u32 = total.parse().map_err(|_| ())?;
1104 let before: u32 = before.parse().map_err(|_| ())?;
1105 if before > total {
1106 return Err(());
1107 }
1108 Ok(Patchable { total, before })
1109 }
1110}
1111
1112impl fmt::Display for Patchable {
1113 /// Written the way it was asked for, which is one number when the second is zero.
1114 ///
1115 /// Not because the two forms mean different things, they do not, but because that is the form
1116 /// a command line reaching for this feature writes and reading back what was written is what
1117 /// `--print-config` is for.
1118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1119 match self.before {
1120 0 => write!(f, "{}", self.total),
1121 before => write!(f, "{},{before}", self.total),
1122 }
1123 }
1124}
1125
1126/// Which of the two position independent questions the output is answering.
1127///
1128/// Everything this compiler writes is position independent, so this is not about whether there are
1129/// absolute addresses in the text. It is about whether the link that reads the object is one that
1130/// puts every name in the same program. An executable is such a link and a shared library is not,
1131/// and the difference decides how a name is reached: from the instruction pointer where the
1132/// distance is a number the linker has, and out of the global offset table where it is not.
1133///
1134/// The expensive answer is the one that has to be asked for, which is gcc's arrangement and is why
1135/// `-fPIC` is on the compile line of every library and nowhere else. A name is only reached the
1136/// expensive way when it is one another object may define or replace, so `-fPIC -fvisibility=hidden`
1137/// costs no more than an executable does.
1138#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1139pub enum Pic {
1140 /// `-fPIE`, `-fpie` and nothing at all. The link puts every name in one program, so a name this
1141 /// file defines is at a distance from the instruction asking, and a name it declares ends up at
1142 /// one too, because the linker answers a reference to a variable defined in a library by making
1143 /// room for it here and copying it. That is what a distribution's default build is.
1144 #[default]
1145 Executable,
1146 /// `-fPIC` and `-fpic`. The output may end up in a shared library, where a name the file
1147 /// exports is one something loaded earlier may define too, and where a name defined elsewhere
1148 /// is not copied in. Both are reached through the global offset table.
1149 Library,
1150}
1151
1152impl Pic {
1153 /// The spelling this is asked for by, which is the one gcc's manual leads with.
1154 pub const fn as_str(self) -> &'static str {
1155 match self {
1156 Pic::Executable => "-fPIE",
1157 Pic::Library => "-fPIC",
1158 }
1159 }
1160}
1161
1162impl fmt::Display for Pic {
1163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1164 f.write_str(self.as_str())
1165 }
1166}
1167
1168/// What the compiler should produce.
1169///
1170/// The intermediate forms are not a debugging convenience bolted on later. Every one of them
1171/// is a documented textual form that round-trips, which is what makes the per-stage testing
1172/// in `spec/15-testing.md` section 15.2 possible.
1173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1174// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
1175// match that needs to change, in this workspace and in anyone else's code. That is
1176// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
1177// target is a data change: the compiler tells you every place the data is read.
1178pub enum EmitKind {
1179 /// A linked executable. The default.
1180 #[default]
1181 Executable,
1182 /// An object file, `-c`.
1183 Object,
1184 /// Assembly text, `-S`.
1185 Asm,
1186 /// Preprocessed source, `-E`.
1187 Preprocessed,
1188 /// The typed AST, `--emit=tast`.
1189 Tast,
1190 /// The IR, `--emit=ir`.
1191 Ir,
1192 /// The machine IR after register allocation, `--emit=mir-final`.
1193 MirFinal,
1194 /// The safety summary, `--emit=safety-summary`.
1195 ///
1196 /// Not an intermediate form of the program the way the three above are. It is the answer to
1197 /// "what does this build's guarantee actually rest on", which
1198 /// `spec/safe-memory/07-check-elimination.md` section 7.8 asks for and
1199 /// `spec/safe-memory/10-boundaries.md` section 10.2 says why.
1200 SafetySummary,
1201 /// How the bytes of the translation unit's records fall into granules,
1202 /// `--emit=type-granules`.
1203 ///
1204 /// Not an intermediate form either. It is the measurement
1205 /// `spec/safe-memory/17-open-questions.md` question 6 asks for, which decides whether the
1206 /// type plane fits inside Tier D's memory budget, and it needs nothing past the type
1207 /// checker because it is a question about layouts rather than about code.
1208 TypeGranules,
1209}
1210
1211impl EmitKind {
1212 /// The name used by `--emit=` and by `--print-config`.
1213 pub const fn as_str(self) -> &'static str {
1214 match self {
1215 EmitKind::Executable => "exe",
1216 EmitKind::Object => "obj",
1217 EmitKind::Asm => "asm",
1218 EmitKind::Preprocessed => "preprocessed",
1219 EmitKind::Tast => "tast",
1220 EmitKind::Ir => "ir",
1221 EmitKind::MirFinal => "mir-final",
1222 EmitKind::SafetySummary => "safety-summary",
1223 EmitKind::TypeGranules => "type-granules",
1224 }
1225 }
1226}
1227
1228impl FromStr for EmitKind {
1229 type Err = ();
1230
1231 fn from_str(s: &str) -> Result<Self, ()> {
1232 Ok(match s {
1233 "exe" => EmitKind::Executable,
1234 "obj" => EmitKind::Object,
1235 "asm" => EmitKind::Asm,
1236 "preprocessed" => EmitKind::Preprocessed,
1237 "tast" => EmitKind::Tast,
1238 "ir" => EmitKind::Ir,
1239 "mir-final" => EmitKind::MirFinal,
1240 "safety-summary" => EmitKind::SafetySummary,
1241 "type-granules" => EmitKind::TypeGranules,
1242 _ => return Err(()),
1243 })
1244 }
1245}
1246
1247/// Which C the source is written in.
1248///
1249/// The GNU variants are the same language with `__STRICT_ANSI__` left undefined, so the
1250/// dialect and the extension question are two fields rather than ten variants.
1251#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1252pub enum Std {
1253 /// `-std=c89`, and `-ansi`.
1254 C89,
1255 /// `-std=c99`.
1256 C99,
1257 /// `-std=c11`.
1258 C11,
1259 /// `-std=c17`, which is C11 with the defect reports applied.
1260 C17,
1261 /// `-std=c23`. The default, matching current GCC.
1262 #[default]
1263 C23,
1264}
1265
1266impl Std {
1267 /// What `__STDC_VERSION__` says, which C89 does not define at all.
1268 pub const fn stdc_version(self) -> Option<&'static str> {
1269 match self {
1270 Std::C89 => None,
1271 Std::C99 => Some("199901L"),
1272 Std::C11 => Some("201112L"),
1273 Std::C17 => Some("201710L"),
1274 Std::C23 => Some("202311L"),
1275 }
1276 }
1277
1278 /// The name in `-std=`.
1279 pub const fn as_str(self) -> &'static str {
1280 match self {
1281 Std::C89 => "c89",
1282 Std::C99 => "c99",
1283 Std::C11 => "c11",
1284 Std::C17 => "c17",
1285 Std::C23 => "c23",
1286 }
1287 }
1288
1289 /// Whether this dialect has `_Atomic`, `_Thread_local` and the rest of C11.
1290 pub const fn has_c11(self) -> bool {
1291 matches!(self, Std::C11 | Std::C17 | Std::C23)
1292 }
1293
1294 /// Reads a `-std=` argument, and says whether the GNU extensions came with it.
1295 ///
1296 /// Every alias GCC takes is here, including the `iso9899` spellings and the year based
1297 /// ones, because a build system that passes `-std=iso9899:1999` is passing what its
1298 /// author tested against and rejecting it helps nobody. An unknown dialect is `None`
1299 /// rather than a guess, since guessing means compiling a different language than the one
1300 /// asked for.
1301 #[must_use]
1302 pub fn from_flag(name: &str) -> Option<(Std, bool)> {
1303 let gnu = name.starts_with("gnu");
1304 let std = match name {
1305 "c89" | "c90" | "gnu89" | "gnu90" | "iso9899:1990" | "iso9899:199409" => Std::C89,
1306 "c99" | "c9x" | "gnu99" | "gnu9x" | "iso9899:1999" | "iso9899:199x" => Std::C99,
1307 "c11" | "c1x" | "gnu11" | "gnu1x" | "iso9899:2011" => Std::C11,
1308 "c17" | "c18" | "gnu17" | "gnu18" | "iso9899:2017" | "iso9899:2018" => Std::C17,
1309 "c23" | "c2x" | "gnu23" | "gnu2x" => Std::C23,
1310 _ => return None,
1311 };
1312 Some((std, gnu))
1313 }
1314}
1315
1316/// The GCC release the compiler claims to be, as `__GNUC__`, `__GNUC_MINOR__` and
1317/// `__GNUC_PATCHLEVEL__`.
1318///
1319/// Design: `spec/04-driver-and-cli.md` section 4.5, which makes this a knob rather than a
1320/// constant and says to start conservative and raise it as the matrix in `rucc-gnu` fills in.
1321///
1322/// The default is seven, which is the lowest claim that gets a modern glibc. glibc gates most
1323/// of what it hands a caller on `__GNUC_PREREQ`, so the claim decides which half of
1324/// `sys/cdefs.h` we get, and below seven `bits/floatn-common.h` writes `typedef float _Float32;`
1325/// over a keyword this compiler already has. Every header that reaches it stops there, which
1326/// was most of them: on Ubuntu 24.04's glibc 2.39 the claim of 4.2.1 that stood here before got
1327/// 180 of 214 headers through and seven gets 202, and the amalgamated sqlite goes from four
1328/// errors to none.
1329///
1330/// It is still deliberately low. Claiming a version whose promises have not been kept means
1331/// being handed syntax the compiler cannot parse, so this moves when there is a measurement
1332/// saying it can. Thirteen and sixteen were measured alongside seven and came out identical on
1333/// glibc, on the macOS SDK and on sqlite, so the next move up is cheap; it is a separate one
1334/// because nothing yet needs it.
1335#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1336pub struct GnucVersion {
1337 /// `__GNUC__`.
1338 pub major: u32,
1339 /// `__GNUC_MINOR__`.
1340 pub minor: u32,
1341 /// `__GNUC_PATCHLEVEL__`.
1342 pub patch: u32,
1343}
1344
1345impl Default for GnucVersion {
1346 fn default() -> GnucVersion {
1347 GnucVersion { major: 7, minor: 0, patch: 0 }
1348 }
1349}
1350
1351impl FromStr for GnucVersion {
1352 type Err = String;
1353
1354 /// Reads `-fgnuc-version=`, which is `15`, `15.1` or `15.1.0`.
1355 ///
1356 /// The short forms are not a convenience, they are what people write. A missing component
1357 /// is zero, the same way GCC treats a release with no patchlevel.
1358 fn from_str(text: &str) -> Result<GnucVersion, String> {
1359 let mut parts = text.split('.');
1360 let mut next = |what: &str| -> Result<u32, String> {
1361 match parts.next() {
1362 None => Ok(0),
1363 Some(field) => {
1364 field.parse().map_err(|_| format!("`{text}` has a {what} that is not a number"))
1365 }
1366 }
1367 };
1368 let major = next("major")?;
1369 let minor = next("minor")?;
1370 let patch = next("patchlevel")?;
1371 if parts.next().is_some() {
1372 return Err(format!("`{text}` has more than three components"));
1373 }
1374 Ok(GnucVersion { major, minor, patch })
1375 }
1376}
1377
1378/// What the `-d` family asks to be dumped alongside, or instead of, the preprocessed output.
1379///
1380/// Design: `spec/04-driver-and-cli.md` section 4.4.
1381///
1382/// GCC spells these as letters packed into one flag, so `-dDI` is two of them, and a letter it
1383/// does not know is ignored rather than rejected. That last part is deliberate on GCC's side
1384/// and worth copying: the family is a debugging aid and a build that passes `-dumpbase` should
1385/// not die on the `-d`.
1386#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1387pub struct Dumps {
1388 /// `-dM`. Print the macros that are defined at the end, and nothing else.
1389 pub macros: bool,
1390}
1391
1392impl Dumps {
1393 /// The letters GCC's preprocessor takes after `-d`.
1394 ///
1395 /// `M` is the macros, `D` is the macros in place, `N` is their names only, `I` is the
1396 /// `#include` lines and `U` is the macros as they are used. Only `M` does anything so far.
1397 const LETTERS: &'static str = "MDNIU";
1398
1399 /// Whether `arg` is a flag from this family rather than something else beginning with
1400 /// `-d`.
1401 ///
1402 /// The check is here rather than in the driver so that the set of letters and the set of
1403 /// flags accepted cannot drift apart. It matters because `-dumpversion` also begins with
1404 /// `-d`, and a family that swallowed every such flag would turn a flag we have not written
1405 /// into a dump of nothing.
1406 #[must_use]
1407 pub fn is_family(arg: &str) -> bool {
1408 match arg.strip_prefix("-d") {
1409 Some("") | None => false,
1410 Some(letters) => letters.chars().all(|c| Dumps::LETTERS.contains(c)),
1411 }
1412 }
1413
1414 /// Reads the letters after `-d`, ignoring the ones we do not implement yet.
1415 pub fn add(&mut self, letters: &str) {
1416 for letter in letters.chars() {
1417 if letter == 'M' {
1418 self.macros = true;
1419 }
1420 }
1421 }
1422
1423 /// Whether anything at all was asked for.
1424 #[must_use]
1425 pub const fn any(self) -> bool {
1426 self.macros
1427 }
1428}
1429
1430/// A file `-imacros` or `-include` named, read before the source file.
1431///
1432/// Design: `spec/04-driver-and-cli.md` section 4.4.
1433///
1434/// The flag a build reaches for when a whole tree has to see a definition that is not in any of
1435/// its files. The kernel builds every object with `-include` of its own configuration header, and
1436/// a configure script that has produced a `config.h` gets it into a third party source tree the
1437/// same way, without a patch.
1438#[derive(Debug, Clone, PartialEq, Eq)]
1439pub struct Preinclude {
1440 /// The name as it was written, which is looked for the way a quoted include is looked for.
1441 pub name: String,
1442 /// Whether only the definitions it makes are wanted, which is what `-imacros` asks for.
1443 ///
1444 /// The text of an `-imacros` file is read and thrown away, so a header full of declarations
1445 /// contributes its macros and nothing else. That is what makes it usable on a file that has
1446 /// already been included by the source: the definitions arrive early and the declarations do
1447 /// not arrive twice.
1448 pub macros_only: bool,
1449}
1450
1451/// What the `-M` family asks for, which is a make rule saying what a source file was built from.
1452///
1453/// Design: `spec/04-driver-and-cli.md` section 4.4.
1454///
1455/// This is a compiler flag rather than a separate tool because the answer is the set of files the
1456/// preprocessor opened, and nothing outside the preprocessor knows what that was. A build system
1457/// that generates its own makefiles asks for it on every compilation, which is why section 4.4
1458/// calls the family required rather than convenient.
1459#[derive(Debug, Clone, PartialEq, Eq)]
1460pub struct Deps {
1461 /// Whether a rule is produced at all, which is any of `-M`, `-MM`, `-MD` and `-MMD`.
1462 pub emit: bool,
1463 /// Whether the rule is produced instead of compiling, which is `-M` and `-MM` and not the
1464 /// two that end in `D`.
1465 ///
1466 /// The split is GCC's and it is about who reads the answer. The two that stop after the rule
1467 /// write it to standard output for a person, and the two that do not write it to a file
1468 /// beside the object for `make` to include on the next run.
1469 pub instead_of_compiling: bool,
1470 /// Whether a header found in a system directory is listed, which `-MM` and `-MMD` turn off.
1471 ///
1472 /// A build that lists them is a build that rebuilds the world when the C library is updated,
1473 /// which is either what somebody wanted or the reason they reached for the other spelling.
1474 ///
1475 /// On unless a flag turned it off, and nothing turns it back on. That is GCC's behaviour and
1476 /// not an oversight: `-MM -M` leaves the system headers out, because the flag that asks for
1477 /// fewer of them is read as the answer to a question the other one never asked.
1478 pub system_headers: bool,
1479 /// Where the rule is written, from `-MF`, with `-` meaning standard output.
1480 ///
1481 /// `None` is the default, which is standard output when the rule replaces the compilation and
1482 /// the output file with a `.d` suffix when it does not.
1483 pub file: Option<String>,
1484 /// What the rule's targets are, from `-MT` and `-MQ`, in the order they were given.
1485 ///
1486 /// Already escaped, because that is the whole of the difference between the two flags: `-MQ`
1487 /// escapes what it is given and `-MT` writes it through untouched. Empty means the target is
1488 /// worked out from the output file, which is what a build that passes neither expects.
1489 pub targets: Vec<String>,
1490 /// Whether every prerequisite except the source gets a target of its own with no recipe,
1491 /// from `-MP`.
1492 ///
1493 /// This is what stops `make` failing outright when a header is deleted. Without it the old
1494 /// rule names a file that is gone and no rule makes it, and the build stops on a header that
1495 /// nothing needs any more.
1496 pub phony: bool,
1497}
1498
1499impl Default for Deps {
1500 fn default() -> Deps {
1501 Deps {
1502 emit: false,
1503 instead_of_compiling: false,
1504 system_headers: true,
1505 file: None,
1506 targets: Vec::new(),
1507 phony: false,
1508 }
1509 }
1510}
1511
1512/// Whether `-save-temps` was given and where it puts the files it keeps.
1513///
1514/// Design: `spec/04-driver-and-cli.md` section 4.10.
1515///
1516/// The flag is how a build gets at the preprocessed source of the file that failed without running
1517/// the compiler a second time under different flags, which is the one way to be sure the text being
1518/// read is the text that was compiled. A bug report against a compiler is usually a preprocessed
1519/// file and nothing else, and this is where that file comes from.
1520#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1521pub enum SaveTemps {
1522 /// Not asked for, and nothing is kept.
1523 #[default]
1524 No,
1525 /// Beside the file the compilation produced, which is `-save-temps=obj`.
1526 ///
1527 /// This is what the bare `-save-temps` does as well. GCC's manual says the bare spelling is
1528 /// `-save-temps=cwd`, and gcc 16 does not do that: `-save-temps -c a.c -o out/a.o` leaves
1529 /// `out/a.i` and `out/a.s` rather than `a.i` and `a.s`. The measurement is what is followed
1530 /// here, because a build that reads the manual and a build that reads the compiler both end up
1531 /// looking for the files where the compiler put them.
1532 Object,
1533 /// In the working directory, which is `-save-temps=cwd`.
1534 Cwd,
1535}
1536
1537impl SaveTemps {
1538 /// Whether anything is kept at all.
1539 #[must_use]
1540 pub const fn wanted(self) -> bool {
1541 !matches!(self, SaveTemps::No)
1542 }
1543}
1544
1545impl FromStr for SaveTemps {
1546 type Err = String;
1547
1548 /// Reads what came after the `=`, which is the only part that varies.
1549 ///
1550 /// # Errors
1551 ///
1552 /// Returns the offending word. GCC treats an unknown one as fatal rather than ignoring it,
1553 /// which is right: a misspelled keyword here means the files a person went looking for are not
1554 /// written and nothing said so.
1555 fn from_str(s: &str) -> Result<SaveTemps, String> {
1556 match s {
1557 "obj" => Ok(SaveTemps::Object),
1558 "cwd" => Ok(SaveTemps::Cwd),
1559 _ => Err(format!("`{s}` is not a -save-temps option; accepted: cwd, obj")),
1560 }
1561 }
1562}
1563
1564/// Everything a compilation was asked to do.
1565///
1566/// Options are a plain value with no interior mutability, so a caller can build one, clone
1567/// it, tweak one field and run a second compilation, which is exactly what the differential
1568/// testing in `spec/15-testing.md` needs.
1569#[derive(Debug, Clone, PartialEq, Eq)]
1570#[non_exhaustive]
1571pub struct Options {
1572 /// The target to generate code for.
1573 pub target: Triple,
1574 /// The optimisation level.
1575 pub opt_level: OptLevel,
1576 /// How much of the memory safety monitor is on, from `-fsafety=`.
1577 ///
1578 /// Off unless it was asked for. A program built without the flag is compiled by exactly the
1579 /// pipeline it was compiled by before the monitor existed, which is the only way the feature
1580 /// can be developed in the open without every build paying for it.
1581 pub safety: Safety,
1582 /// Whether padding participates in the init plane, from `-fsafety-init=`.
1583 ///
1584 /// Means nothing unless `safety` asked for a tier. The default is the one section 9.3 gives
1585 /// library code, which is that it does not, so a record filled a member at a time is not
1586 /// reported when something later reads it whole.
1587 pub padding: Padding,
1588 /// Whether an access has to stay inside the member it names, from `-fsafety-subobject`.
1589 ///
1590 /// Means nothing unless `safety` asked for a tier. Off by default, which section 9.4 argues
1591 /// for: this is the row most likely to fire on code that is doing what its author meant.
1592 pub subobject: Subobject,
1593 /// Whether the `restrict` contract is checked, from `-fsafety-restrict`.
1594 ///
1595 /// Means nothing unless `safety` asked for a tier. Off by default, which section 9.6 argues
1596 /// for: the cost lands entirely inside the loops `restrict` is written for.
1597 pub promise: Promise,
1598 /// What to produce.
1599 pub emit: EmitKind,
1600 /// Whether to emit debug information.
1601 pub debug_info: bool,
1602 /// How the debug sections are compressed, from `-gz`.
1603 ///
1604 /// Nothing reads this yet because nothing writes a debug section yet. It is the same shape of
1605 /// answer `prefix_map.debug` is, and it is waiting for the same crate.
1606 pub compress: Compress,
1607 /// What the `-flto` family asked for, which nothing does yet.
1608 pub lto: Lto,
1609 /// What the profile reading half of the `-fprofile` family asked for, which nothing reads yet.
1610 ///
1611 /// Named for the data rather than for the flag, because `profile` next door is already the
1612 /// answer to whether `-pg` asked for a call to a profiler on the way into every function, and
1613 /// the two are different questions about the same word.
1614 pub profile_data: Profile,
1615 /// Whether every function keeps a frame pointer, from `-fno-omit-frame-pointer`.
1616 ///
1617 /// Off by default, which is what gcc does at every level above `-O0` and what leaves the
1618 /// register free for the allocator. A profiler that walks the stack by following saved frame
1619 /// pointers needs it on, and so does any code a debugger has to unwind without unwind tables.
1620 pub frame_pointer: bool,
1621 /// Whether the red zone may be used, from `-mno-red-zone` turned around.
1622 ///
1623 /// The 128 bytes below the stack pointer that the System V psABI promises no signal handler
1624 /// will touch, which lets a small leaf function keep its locals without moving the stack
1625 /// pointer at all. A kernel turns this off, because an interrupt taken on the kernel stack
1626 /// makes the promise false, and every kernel build in the wild passes `-mno-red-zone` for
1627 /// exactly that reason. A convention without a red zone ignores this.
1628 pub red_zone: bool,
1629 /// Which functions get a stack protector, from the `-fstack-protector` family.
1630 pub protector: Protector,
1631 /// Whether a prologue takes its frame a page at a time, from `-fstack-clash-protection`.
1632 ///
1633 /// An operating system leaves one page unmapped below every stack so that a stack growing
1634 /// into it faults. A function whose frame is larger than that page moves the stack pointer
1635 /// clean over it in one subtraction and can then write below it, into whatever the program
1636 /// mapped next, which is a way of reaching one allocation from another that costs an attacker
1637 /// nothing but a large local array. A prologue that takes the frame a page at a time and
1638 /// writes to each page as it arrives faults on the first one that is not there.
1639 ///
1640 /// Off by default, which is gcc's default. Distributions that build with it build everything
1641 /// with it, because the hole is in whichever function was left out.
1642 pub stack_clash: bool,
1643 /// Which control flow transfers are checked, from `-fcf-protection=`.
1644 ///
1645 /// See [`Control`]. Off by default, which is gcc's default on these targets, and on again in
1646 /// every distribution's global flags for the same reason the stack protector is.
1647 pub control: Control,
1648 /// Whether every function calls a profiler's hook on the way in, from `-pg` and `-p`.
1649 ///
1650 /// A profiler wants a count of which function called which, and the moment a function is
1651 /// entered is the only place a compiler can hand it one. It changes the link as well as the
1652 /// code, since the counts have to be started before `main` and written out after it, and the
1653 /// start file that does that is a different one.
1654 ///
1655 /// A tracer wants the same call for a different reason. The hook is one instruction the kernel
1656 /// can overwrite while the program runs, which is what makes a function traceable without
1657 /// rebuilding it, and it is why Linux is built this way rather than to be profiled.
1658 pub profile: bool,
1659 /// Where that call goes, from `-mfentry` and `-mno-fentry`.
1660 ///
1661 /// See [`Hook`]. Read even on a command line that did not ask for the call, since gcc accepts
1662 /// the flag on its own and does nothing with it.
1663 pub hook: Hook,
1664 /// How much room every function opens with for somebody to write over later, from
1665 /// `-fpatchable-function-entry=`.
1666 ///
1667 /// See [`Patchable`]. A kernel asks for this so that a function can be traced without being
1668 /// rebuilt: the room is a known number of bytes at a known address, and the addresses are
1669 /// collected into a section of their own so that whatever does the patching can find every one
1670 /// of them without reading the symbol table.
1671 pub patchable: Patchable,
1672 /// What happens rather than nothing being defined when arithmetic overflows, from `-fwrapv`,
1673 /// `-fwrapv-pointer`, `-fno-strict-overflow` and `-ftrapv`.
1674 ///
1675 /// See [`Wrapping`]. Nothing wraps and nothing stops by default, which is what C says and what
1676 /// lets the optimizer read a loop counter as a number rather than as a number that may turn
1677 /// round.
1678 pub wrapping: Wrapping,
1679 /// What a plain `char` is, from `-fsigned-char` and `-funsigned-char`, with nothing meaning
1680 /// the answer the target's ABI gives.
1681 ///
1682 /// Plain `char` is a third type either way, distinct from both `signed char` and
1683 /// `unsigned char` in every place a type is compared, and this says which of the two it has
1684 /// the range of. Changing it changes the ABI, so it is a decision about the whole program
1685 /// rather than about one file, and `__CHAR_UNSIGNED__` is defined when the answer is unsigned
1686 /// so that a header can see what was decided.
1687 pub char_signed: Option<bool>,
1688 /// Whether an enumeration nothing wrote an underlying type for is represented in the smallest
1689 /// integer type that holds its enumerators, from `-fshort-enums`.
1690 ///
1691 /// The default is `int` or wider, which is what C says and what every psABI in the table
1692 /// expects. This makes it `char` or wider instead, so `enum { A }` is one byte, and that
1693 /// changes the size and the alignment of anything holding one. It is here because a great deal
1694 /// of embedded C and every ARM EABI object is built with it, and mixing the two answers in one
1695 /// program is a silent disagreement about layout rather than a link error.
1696 pub short_enums: bool,
1697 /// Whether an access names the type it goes through, from `-fstrict-aliasing` and
1698 /// `-fno-strict-aliasing`.
1699 ///
1700 /// On, which is gcc's answer at every level above `-O0` and is what C 6.5 paragraph 7 already
1701 /// says. Clearing it makes the front end leave the type off every load and every store, and an
1702 /// access with no type on it is one the alias analysis has no type based reason to separate
1703 /// from any other, which is what the flag asks for.
1704 pub strict_aliasing: bool,
1705 /// How far a multiply and an addition may be fused into one rounding, from `-ffp-contract=`.
1706 ///
1707 /// See [`Contract`]. This is the only one of the floating point flags with anywhere to be kept,
1708 /// because it is the only one this compiler could act on: the rest of that group withdraw
1709 /// licences that nothing here takes in the first place.
1710 pub fp_contract: Contract,
1711 /// What a path is rewritten by before it is written into the output, from the
1712 /// `-f*-prefix-map=` family.
1713 ///
1714 /// See [`PrefixMaps`]. This is what makes a build reproducible from a different directory, and
1715 /// it is three lists rather than one because gcc has three flags and a build uses them apart.
1716 pub prefix_map: PrefixMaps,
1717 /// Whether warnings are errors.
1718 pub warnings_are_errors: bool,
1719 /// Whether a warning is raised at all, which is `-w` turned around.
1720 ///
1721 /// A build that passes this has decided it does not want to hear about anything that is not
1722 /// fatal, and the flag is dropped at the one place every diagnostic goes through rather than
1723 /// tested at each site that raises one. `-w` beats `-Werror` where both are given, because a
1724 /// warning that was never raised cannot be promoted.
1725 pub warnings: bool,
1726 /// How many diagnostics to print before giving up. Past a certain point the output is
1727 /// noise from a single earlier mistake, and GCC's default of no limit is not a kindness.
1728 pub error_limit: u32,
1729 /// The dialect, from `-std=`.
1730 pub std: Std,
1731 /// Whether the GNU extensions are on, which is `-std=gnu23` rather than `-std=c23`.
1732 pub gnu_extensions: bool,
1733 /// Whether `-pedantic` was given, which is what turns a use of an extension from silence
1734 /// into a diagnostic. It is not the same knob as the dialect: `-std=c17 -pedantic` warns
1735 /// about a construct that `-std=c17` alone accepts without a word.
1736 pub pedantic: bool,
1737 /// Whether `-fpermissive` was given, which turns the rules gcc 14 promoted from errors back
1738 /// into warnings.
1739 ///
1740 /// Six of them, all about code written before the language settled: a declaration with no
1741 /// type in it, a call to a function nothing declared, a parameter in an old style definition
1742 /// with no type, a pointer made from an integer, a pointer assigned from a pointer to
1743 /// something else, and a `return` whose value disagrees with what was promised. The flag says
1744 /// nothing about any other diagnostic, and it does not say to compile something different: a
1745 /// program it accepts is compiled the way the rule it broke says it means.
1746 pub permissive: bool,
1747 /// Whether the whole unit is under GNU's reading of `inline` rather than C's, which is
1748 /// `-fgnu89-inline`.
1749 ///
1750 /// Under C's reading a definition every file-scope declaration wrote `inline` for and none
1751 /// wrote `extern` for emits nothing, and under GNU's it is the definition alone that decides
1752 /// and `extern inline` is the one that emits nothing. The C89 dialects are under GNU's
1753 /// whatever this says, since that is where the older reading came from, so this is the flag a
1754 /// program written against it reaches for when it is being compiled under a later dialect.
1755 pub gnu89_inline: bool,
1756 /// What a name that nothing in the source said anything about reaches, from `-fvisibility=`.
1757 pub visibility: Visibility,
1758 /// Whether the object may end up in a shared library, from `-fPIC` and `-fPIE`.
1759 pub pic: Pic,
1760 /// Whether a definition in this unit may be replaced at load time by one in another object,
1761 /// from `-fsemantic-interposition` and `-fno-semantic-interposition`.
1762 ///
1763 /// True is the honest answer and is gcc's default, because that is what an exported name in a
1764 /// shared library means: the dynamic linker takes the first definition it finds in load order,
1765 /// so a function this unit defines and calls may not be the one that runs. Everything the
1766 /// optimizer reads off a body has to stop at a name like that.
1767 ///
1768 /// False is a promise the build makes, and every distribution makes it, because otherwise a
1769 /// library cannot inline its own functions into each other. It is a promise rather than a
1770 /// deduction: nothing checks it, and a program that then interposes one of those names gets a
1771 /// mixture of the two definitions. It says nothing about `-fPIE`, where no name is replaceable
1772 /// to begin with, and it says nothing about how an address is reached, which is the separate
1773 /// question `-fPIC` decides.
1774 pub interposition: bool,
1775 /// Whether a function is described to an unwinder at every instruction, from
1776 /// `-fasynchronous-unwind-tables` and `-fno-asynchronous-unwind-tables`.
1777 ///
1778 /// True is the default, which is gcc's wherever anything reads the table, and the reason is
1779 /// that the programs that read it are not the ones being compiled. C++ exceptions,
1780 /// `backtrace`, a profiler sampling a stack and a crash handler printing one all walk frames
1781 /// belonging to code that knew nothing about them, so a unit that opts out stops a walk that
1782 /// started somewhere else.
1783 ///
1784 /// What `asynchronous` asks for on top of a table is that the answer is right at every
1785 /// instruction and not only where a call is, because a signal can arrive anywhere, including
1786 /// the middle of a prologue. Rows come off the prologue as it is built here, so that is the
1787 /// only kind of table there is to write and the weaker request below is answered with it.
1788 ///
1789 /// False is for a build that knows nothing will ever walk it, which in practice is a kernel or
1790 /// a freestanding image, and what it saves is the section rather than any instruction.
1791 pub async_unwind_tables: bool,
1792 /// Whether a function is described to an unwinder at all, from `-funwind-tables` and
1793 /// `-fno-unwind-tables`.
1794 ///
1795 /// The weaker of the two requests and off by default, because the one above is on and implies
1796 /// it. A table is written when either of them is standing, which is what [`Self::unwinds`]
1797 /// answers and is how gcc resolves a line that asks for a table and against an asynchronous
1798 /// one.
1799 ///
1800 /// Neither of them is about anything but ELF. Mach-O and COFF have their own arrangements and
1801 /// neither is written yet, so on those targets nothing reads these.
1802 pub unwind_tables: bool,
1803 /// Whether each function gets a section of its own, from `-ffunction-sections`.
1804 ///
1805 /// A linker can leave out a section nothing reaches and cannot leave out half of one, so this
1806 /// is what makes `--gc-sections` able to drop a function this file defines and nothing calls.
1807 /// A kernel and an embedded image are both linked that way and are both a good deal larger
1808 /// without it, and the cost is one section header per function.
1809 pub function_sections: bool,
1810 /// Whether each variable gets a section of its own, from `-fdata-sections`.
1811 ///
1812 /// The same bargain for the data, and a separate flag because gcc has two of them: a build
1813 /// that wants one and not the other is a build that measured something. Splitting the data can
1814 /// cost more than it saves, since two variables a loop reads together are no longer certain to
1815 /// land in the same page.
1816 pub data_sections: bool,
1817 /// The GCC release claimed, from `-fgnuc-version=`.
1818 pub gnuc: GnucVersion,
1819 /// Whether there is a standard library, which is `-ffreestanding` turned around.
1820 pub hosted: bool,
1821 /// Whether a call to a C library function written under its own plain name may be taken to
1822 /// mean that function, which is `-fno-builtin` turned around.
1823 ///
1824 /// The names are reserved, so `llabs` is the library's `llabs` and the compiler is allowed to
1825 /// know what it does. A program that means something else by one of them is the reason the
1826 /// flag exists, and `-ffreestanding` turns it off as well, because a freestanding program has
1827 /// no C library for the name to be the name of. The `__builtin_` spellings are not affected by
1828 /// either, since the prefix is the program saying which function it means.
1829 pub builtins: bool,
1830 /// The names `-fno-builtin-<name>` took away one at a time, without the prefix.
1831 ///
1832 /// A build that means its own `memcpy` and the library's everything else writes this rather
1833 /// than the whole flag, which is what the kernel does for a handful of names.
1834 pub no_builtin: Vec<String>,
1835 /// The glibc release the headers on the search path are, as the minor number alone.
1836 ///
1837 /// `Some` means two things together: this is a glibc target, and step 3 of
1838 /// `spec/cross-compile/08-sysroots.md` section 8.5 resolved to the tree we bundle. Then the
1839 /// compiler defines `__GLIBC_MINOR__`, because one tree serves every version and the version is
1840 /// the part of it the target supplies. `__GLIBC__` is not ours to define either way, since it is
1841 /// in the tree and a real `features.h` defines it too.
1842 ///
1843 /// `None` is every other case, and the cases matter more than the value. A host glibc's
1844 /// `features.h` defines the macro itself, and a tree the user named has a `features.h` of its
1845 /// own, so defining it as well would be two definitions with different values, which is a
1846 /// warning on every compilation of every file. A musl or mingw target has no such macro at all.
1847 pub glibc_minor: Option<u32>,
1848 /// `-D` in command line order. `FOO` means `FOO=1`, as GCC has it.
1849 pub defines: Vec<String>,
1850 /// `-U` in command line order, applied after the defines because `-U` wins.
1851 pub undefines: Vec<String>,
1852 /// Where a header is looked for.
1853 pub search: SearchPath,
1854 /// What `-imacros` and `-include` named, in command line order.
1855 pub preincludes: Vec<Preinclude>,
1856 /// Whether `-E` writes line markers, which `-P` turns off.
1857 pub line_markers: bool,
1858 /// What the `-d` family asks for.
1859 pub dumps: Dumps,
1860 /// What the `-M` family asks for.
1861 pub deps: Deps,
1862 /// Whether the intermediate files are kept, from `-save-temps`.
1863 pub save_temps: SaveTemps,
1864 /// Whether each step says how long it took, from `-time`.
1865 pub time: bool,
1866 /// What `-f<pass>` and `-fno-<pass>` said about an optimizer pass, in the order the command
1867 /// line said it, so that the last mention of a pass is the one that decides.
1868 ///
1869 /// The pipeline the level chose is the starting point and this is what is added to and taken
1870 /// away from it. The names are checked against the pass list while the arguments are parsed,
1871 /// so anything in here is a pass the compiler has.
1872 pub passes: Vec<(String, bool)>,
1873 /// What `-fpass-fuel=<pass>=<n>` limited a pass to, by pass name.
1874 ///
1875 /// A pass with an entry here performs exactly that many transformations and then stops
1876 /// transforming, which is what bisects a miscompilation to one rewrite. See section 9.10 of
1877 /// `spec/09-optimizer.md`.
1878 pub pass_fuel: Vec<(String, u32)>,
1879 /// What `-fpass-fuel-global=<n>` limited the whole pipeline to, across every pass.
1880 ///
1881 /// The outer of the two searches in section 4.5 of `spec/optimizer/04-pass-manager.md`.
1882 /// Halving this says which pass holds the bad rewrite, and halving `-fpass-fuel` for that
1883 /// pass says which rewrite it is. Where both are given, a pass is stopped by whichever of
1884 /// the two is tighter.
1885 pub pass_fuel_global: Option<u32>,
1886 /// What `-fdisable-<pass>[=<range>]` and `-fenable-<pass>[=<range>]` said, in the order the
1887 /// command line said it, with `true` for the enabling half.
1888 ///
1889 /// A rule covers the functions it names and nothing else, and the last rule that covers a
1890 /// function is the one that decides for it, so the order has to survive. This is the second
1891 /// half of the bisection interface in section 41.6 of `spec/optimizer/41-correctness.md`:
1892 /// `-fpass-fuel` finds the rewrite and this finds the function. The pass names are checked
1893 /// against the pass list while the arguments are parsed.
1894 pub pass_gates: Vec<(bool, String)>,
1895 /// What `-fdump-ir=` asked to see, as it was written, which is `all`, `before-<pass>` or
1896 /// `after-<pass>`.
1897 pub dump_ir: Vec<String>,
1898 /// What `-fopt-info` asked to hear about, as the keywords were written, with the leading
1899 /// hyphen taken off, so a bare `-fopt-info` is the empty string in here.
1900 ///
1901 /// The keywords are `optimized`, `missed`, `note` and `all`, and two flags add up rather than
1902 /// the second replacing the first. Checked while the arguments are parsed, so anything in
1903 /// here is a spelling the optimizer understands. See section 42.2 of
1904 /// `spec/optimizer/42-measurement.md` for why `missed` is the one that earns the feature.
1905 pub opt_info: Vec<String>,
1906 /// Where `-fopt-info=<file>` sends the remarks, or `None` for standard error.
1907 ///
1908 /// One file for the whole run rather than one per input, the way GCC does it, and the last
1909 /// one on the command line is the one that decides. A harness that wants the remarks kept
1910 /// away from the diagnostics gives a file, which is what the corpus in `tamnd/rucc-corpus`
1911 /// does with GCC so that a rejection can still be matched against the diagnostic stream.
1912 pub opt_info_file: Option<String>,
1913 /// Whether the IR verifier runs after every pass that changed anything.
1914 ///
1915 /// On in a debug build without being asked, since that is where a broken pass should be
1916 /// caught. `-Zverify-each` turns it on in a release build, which is what CI wants.
1917 pub verify_each: bool,
1918 /// Where `-Zrule-coverage=FILE` writes which lowering rules fired, if it was given.
1919 ///
1920 /// A measurement rather than a thing a build asks for, which is why it is spelled with a `-Z`
1921 /// the way an unstable option is everywhere else: it is here for the harness in
1922 /// `tamnd/rucc-compat` to union over a corpus and report, and nothing about the code that comes
1923 /// out changes when it is on. One file per run of the compiler, holding the whole rule set with
1924 /// the rules this run reached marked, whatever the run compiled and however many files it was.
1925 pub rule_coverage: Option<String>,
1926 /// Where `-Zregister-pressure=FILE` writes what the allocator had to put on the stack.
1927 ///
1928 /// A measurement and spelled with a `-Z` for the same reason as the one above: nothing about
1929 /// the code that comes out changes when it is on. One file per run of the compiler, one line
1930 /// per function, holding how many values went to the stack and how many stores and reloads
1931 /// that cost. What reads it is `cargo xtask pressure`, which compiles the benchmarks in
1932 /// `bench/safety` with the monitor off and on and reports the difference, since
1933 /// `spec/safe-memory/13-performance.md` section 13.1 asks for that number and section 5.2.1
1934 /// says why: a capability in flight is four words, and if materializing one spills something
1935 /// else in a hot loop then check elimination cannot save it.
1936 pub register_pressure: Option<String>,
1937}
1938
1939impl Options {
1940 /// Default options for `target`.
1941 pub fn new(target: Triple) -> Self {
1942 Self {
1943 target,
1944 opt_level: OptLevel::default(),
1945 safety: Safety::default(),
1946 padding: Padding::default(),
1947 subobject: Subobject::default(),
1948 promise: Promise::default(),
1949 emit: EmitKind::default(),
1950 debug_info: false,
1951 compress: Compress::None,
1952 lto: Lto::default(),
1953 profile_data: Profile::default(),
1954 frame_pointer: false,
1955 red_zone: true,
1956 protector: Protector::default(),
1957 stack_clash: false,
1958 control: Control::default(),
1959 profile: false,
1960 hook: Hook::default(),
1961 patchable: Patchable::default(),
1962 wrapping: Wrapping::NONE,
1963 char_signed: None,
1964 short_enums: false,
1965 strict_aliasing: true,
1966 fp_contract: Contract::Off,
1967 prefix_map: PrefixMaps::default(),
1968 warnings_are_errors: false,
1969 warnings: true,
1970 error_limit: 20,
1971 std: Std::default(),
1972 gnu_extensions: true,
1973 pedantic: false,
1974 permissive: false,
1975 gnu89_inline: false,
1976 visibility: Visibility::default(),
1977 pic: Pic::default(),
1978 interposition: true,
1979 async_unwind_tables: true,
1980 unwind_tables: false,
1981 function_sections: false,
1982 data_sections: false,
1983 gnuc: GnucVersion::default(),
1984 hosted: true,
1985 builtins: true,
1986 no_builtin: Vec::new(),
1987 glibc_minor: None,
1988 defines: Vec::new(),
1989 undefines: Vec::new(),
1990 search: SearchPath::new(),
1991 preincludes: Vec::new(),
1992 line_markers: true,
1993 dumps: Dumps::default(),
1994 deps: Deps::default(),
1995 save_temps: SaveTemps::default(),
1996 time: false,
1997 passes: Vec::new(),
1998 pass_fuel: Vec::new(),
1999 pass_fuel_global: None,
2000 pass_gates: Vec::new(),
2001 dump_ir: Vec::new(),
2002 opt_info: Vec::new(),
2003 opt_info_file: None,
2004 verify_each: cfg!(debug_assertions),
2005 rule_coverage: None,
2006 register_pressure: None,
2007 }
2008 }
2009
2010 /// Whether a function in this unit is described to an unwinder.
2011 ///
2012 /// Either request is answered with the same table, so what decides is whether either of them
2013 /// is standing. Asked here rather than worked out at the two places that write a table, since
2014 /// those two writing different answers for one function is what `spec/11-asm-objects-debug.md`
2015 /// section 11.1 says must not be possible.
2016 #[must_use]
2017 pub const fn unwinds(&self) -> bool {
2018 self.async_unwind_tables || self.unwind_tables
2019 }
2020}
2021
2022/// One compilation.
2023///
2024/// Holds the options, the string interner and the diagnostics raised so far. Passing a
2025/// `&mut Session` is how a stage reports a problem, and the return value of a stage says
2026/// what it produced, never whether it succeeded: that question is answered by
2027/// [`Session::has_errors`].
2028#[derive(Debug)]
2029pub struct Session {
2030 /// What this compilation was asked to do.
2031 pub opts: Options,
2032 /// Everything known about the target.
2033 pub target: TargetInfo,
2034 /// The one interner for the compilation.
2035 pub interner: Interner,
2036 /// Every file read during the compilation, and the flat coordinate space their spans
2037 /// live in.
2038 ///
2039 /// This is on the session rather than passed around separately because a span is only
2040 /// meaningful against the map that issued it, and one map per compilation is the rule
2041 /// that makes that true by construction.
2042 pub sources: SourceMap,
2043 diagnostics: Vec<Diagnostic>,
2044 error_count: u32,
2045 warning_count: u32,
2046}
2047
2048impl Session {
2049 /// A session for `opts`.
2050 ///
2051 /// The command line's answer about plain `char` is put into the target here rather than
2052 /// carried beside it, because every place that asks what a `char` is asks the target, and two
2053 /// answers to one question is how a front end ends up disagreeing with its own back end.
2054 pub fn new(opts: Options) -> Self {
2055 let mut target = TargetInfo::new(opts.target);
2056 if let Some(signed) = opts.char_signed {
2057 target.char_is_signed = signed;
2058 }
2059 Self {
2060 opts,
2061 target,
2062 interner: Interner::with_capacity(1024),
2063 sources: SourceMap::new(),
2064 diagnostics: Vec::new(),
2065 error_count: 0,
2066 warning_count: 0,
2067 }
2068 }
2069
2070 /// Records a diagnostic.
2071 ///
2072 /// Under `-Werror` a warning is promoted here, once, rather than at every site that
2073 /// raises one, and under `-w` it is dropped here for the same reason. A warning that `-w`
2074 /// dropped is not counted, so `-w -Werror` compiles rather than failing on a warning
2075 /// nobody was going to see.
2076 pub fn emit(&mut self, mut diag: Diagnostic) {
2077 if !self.opts.warnings && diag.severity == Severity::Warning {
2078 return;
2079 }
2080 if self.opts.warnings_are_errors && diag.severity == Severity::Warning {
2081 diag.severity = Severity::Error;
2082 }
2083 match diag.severity {
2084 Severity::Error | Severity::Ice => self.error_count += 1,
2085 Severity::Warning => self.warning_count += 1,
2086 Severity::Note | Severity::Help => {}
2087 }
2088 self.diagnostics.push(diag);
2089 }
2090
2091 /// Everything raised so far, in the order it was raised.
2092 pub fn diagnostics(&self) -> &[Diagnostic] {
2093 &self.diagnostics
2094 }
2095
2096 /// Whether anything fatal has been raised.
2097 pub fn has_errors(&self) -> bool {
2098 self.error_count > 0
2099 }
2100
2101 /// How many errors have been raised.
2102 pub fn error_count(&self) -> u32 {
2103 self.error_count
2104 }
2105
2106 /// How many warnings have been raised.
2107 pub fn warning_count(&self) -> u32 {
2108 self.warning_count
2109 }
2110
2111 /// Whether the error limit has been reached and the caller should stop.
2112 pub fn error_limit_reached(&self) -> bool {
2113 self.opts.error_limit != 0 && self.error_count >= self.opts.error_limit
2114 }
2115}
2116
2117#[cfg(test)]
2118mod tests {
2119 use super::*;
2120
2121 fn session() -> Session {
2122 Session::new(Options::new("x86_64-unknown-linux-gnu".parse().unwrap()))
2123 }
2124
2125 #[test]
2126 fn a_version_claim_reads_the_way_gcc_prints_one() {
2127 // `gcc -dumpfullversion` gives all three, `gcc -dumpversion` gives one, and both are
2128 // things a script pastes straight into a flag.
2129 let all = |v: &str| v.parse::<GnucVersion>().unwrap();
2130 assert_eq!(all("15.1.0"), GnucVersion { major: 15, minor: 1, patch: 0 });
2131 assert_eq!(all("15"), GnucVersion { major: 15, minor: 0, patch: 0 });
2132 assert_eq!(all("4.2"), GnucVersion { major: 4, minor: 2, patch: 0 });
2133 assert!("".parse::<GnucVersion>().is_err());
2134 assert!("15.".parse::<GnucVersion>().is_err(), "a trailing dot is a typo, not a zero");
2135 assert!("1.2.3.4".parse::<GnucVersion>().is_err());
2136 }
2137
2138 #[test]
2139 fn a_prefix_map_rewrites_the_front_of_a_path_and_nothing_else() {
2140 let map = |pairs: &[(&str, &str)]| {
2141 let mut map = PrefixMap::new();
2142 for &(old, new) in pairs {
2143 map.push(old, new);
2144 }
2145 map
2146 };
2147 assert!(PrefixMap::new().is_empty());
2148 assert_eq!(PrefixMap::new().apply("sub/h.h"), "sub/h.h");
2149
2150 let one = map(&[("sub", "SUB")]);
2151 assert_eq!(one.apply("sub/h.h"), "SUB/h.h");
2152 assert_eq!(one.apply("a.c"), "a.c", "a path the mapping does not start");
2153 assert_eq!(one.apply("x/sub/h.h"), "x/sub/h.h", "the middle of a path is not the front");
2154
2155 // Characters rather than directories, which is what gcc compares and is worth a test of
2156 // its own because it is the part that looks like it ought to be otherwise.
2157 assert_eq!(map(&[("s", "B")]).apply("sub/h.h"), "Bub/h.h");
2158 assert_eq!(map(&[("sub/", "SUB/")]).apply("sub/h.h"), "SUB/h.h");
2159 assert_eq!(map(&[("sub", "")]).apply("sub/h.h"), "/h.h", "mapping to nothing");
2160 assert_eq!(map(&[("", "PRE")]).apply("a.c"), "PREa.c", "an empty old is in front of all");
2161
2162 // The last one that matches wins, whether or not the two ask about the same prefix, which
2163 // is what a project wide mapping plus a narrower one for a directory relies on.
2164 assert_eq!(map(&[("sub", "ONE"), ("sub", "TWO")]).apply("sub/h.h"), "TWO/h.h");
2165 assert_eq!(map(&[("sub", "A"), ("s", "B")]).apply("sub/h.h"), "Bub/h.h");
2166 assert_eq!(map(&[("s", "B"), ("sub", "A")]).apply("sub/h.h"), "A/h.h");
2167 assert_eq!(map(&[("nope", "X"), ("sub", "A")]).apply("sub/h.h"), "A/h.h");
2168 }
2169
2170 #[test]
2171 fn the_argument_is_split_at_the_last_equals_sign() {
2172 assert_eq!(PrefixMap::split("old=new"), Some(("old", "new")));
2173 assert_eq!(PrefixMap::split("=new"), Some(("", "new")), "an empty old is allowed");
2174 assert_eq!(PrefixMap::split("old="), Some(("old", "")), "and so is an empty new");
2175 // The last rather than the first, so a directory whose name has an `=` in it can be
2176 // mapped and a replacement whose name has one cannot. That is gcc's choice of which of
2177 // the two to make possible, and it is the right way round.
2178 assert_eq!(PrefixMap::split("/home/a=b=/src"), Some(("/home/a=b", "/src")));
2179 assert_eq!(PrefixMap::split("nope"), None);
2180 }
2181
2182 #[test]
2183 fn optimisation_levels_parse_the_way_gcc_spells_them() {
2184 assert_eq!("".parse::<OptLevel>().unwrap(), OptLevel::O1);
2185 assert_eq!("0".parse::<OptLevel>().unwrap(), OptLevel::O0);
2186 assert_eq!("2".parse::<OptLevel>().unwrap(), OptLevel::O2);
2187 assert_eq!("9".parse::<OptLevel>().unwrap(), OptLevel::O3);
2188 assert_eq!("s".parse::<OptLevel>().unwrap(), OptLevel::Os);
2189 assert!("q".parse::<OptLevel>().is_err());
2190 }
2191
2192 #[test]
2193 fn only_o0_skips_the_optimizer() {
2194 assert!(!OptLevel::O0.runs_optimizer());
2195 assert!(OptLevel::O1.runs_optimizer());
2196 assert!(OptLevel::Oz.runs_optimizer());
2197 }
2198
2199 #[test]
2200 fn the_safety_tiers_round_trip_and_nothing_else_is_one() {
2201 for tier in [Safety::Off, Safety::Detect, Safety::Enforce, Safety::Kernel] {
2202 assert_eq!(tier.as_str().parse::<Safety>().unwrap(), tier);
2203 }
2204 // `on` is the obvious thing to try and it is not a tier, because which tier somebody
2205 // means by it is the whole question document 02 answers.
2206 assert!("on".parse::<Safety>().is_err());
2207 assert!("".parse::<Safety>().is_err());
2208 }
2209
2210 #[test]
2211 fn room_for_a_patcher_is_written_the_way_it_was_asked_for() {
2212 for (written, total, before) in
2213 [("0", 0, 0), ("2", 2, 0), ("16", 16, 0), ("5,3", 5, 3), ("3,3", 3, 3)]
2214 {
2215 let room: Patchable = written.parse().unwrap();
2216 assert_eq!(room, Patchable { total, before });
2217 assert_eq!(room.to_string(), written);
2218 assert_eq!(room.after(), total - before);
2219 assert_eq!(room.any(), total > 0);
2220 }
2221 // A second number of zero is the same request as no second number, and it is written back
2222 // the shorter way, which is the way somebody reaching for the flag writes it.
2223 assert_eq!("2,0".parse::<Patchable>().unwrap().to_string(), "2");
2224 }
2225
2226 #[test]
2227 fn more_room_in_front_of_the_label_than_there_is_room_at_all_is_refused() {
2228 // Rather than clamped, because there is no reading of it a caller meant. gcc says the same
2229 // about each of these.
2230 assert!("1,2".parse::<Patchable>().is_err());
2231 assert!("1,2,3".parse::<Patchable>().is_err());
2232 assert!("a".parse::<Patchable>().is_err());
2233 assert!("".parse::<Patchable>().is_err());
2234 assert!("-1".parse::<Patchable>().is_err());
2235 }
2236
2237 #[test]
2238 fn the_two_places_the_intermediate_files_can_go_are_the_two_words_that_are_taken() {
2239 assert_eq!("obj".parse::<SaveTemps>().unwrap(), SaveTemps::Object);
2240 assert_eq!("cwd".parse::<SaveTemps>().unwrap(), SaveTemps::Cwd);
2241 // The names of the two flags that mean the same thing as `=obj` are not themselves
2242 // arguments of it, and neither is silence.
2243 assert!("obj,cwd".parse::<SaveTemps>().is_err());
2244 assert!("".parse::<SaveTemps>().is_err());
2245 // Nothing is kept unless something asked, and both of the words that ask do ask.
2246 assert_eq!(SaveTemps::default(), SaveTemps::No);
2247 assert!(!SaveTemps::No.wanted());
2248 assert!(SaveTemps::Object.wanted());
2249 assert!(SaveTemps::Cwd.wanted());
2250 }
2251
2252 #[test]
2253 fn a_build_that_did_not_ask_for_the_monitor_does_not_get_it() {
2254 assert_eq!(Safety::default(), Safety::Off);
2255 assert!(!Safety::Off.instruments());
2256 assert!(Safety::Detect.instruments());
2257 assert!(Safety::Enforce.instruments());
2258 assert!(Safety::Kernel.instruments());
2259 }
2260
2261 #[test]
2262 fn emit_kinds_round_trip_through_their_names() {
2263 for k in [
2264 EmitKind::Executable,
2265 EmitKind::Object,
2266 EmitKind::Asm,
2267 EmitKind::Preprocessed,
2268 EmitKind::Tast,
2269 EmitKind::Ir,
2270 EmitKind::MirFinal,
2271 ] {
2272 assert_eq!(k.as_str().parse::<EmitKind>().unwrap(), k);
2273 }
2274 }
2275
2276 #[test]
2277 fn errors_are_counted_and_warnings_are_not() {
2278 let mut s = session();
2279 s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
2280 s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
2281 assert_eq!(s.error_count(), 1);
2282 assert_eq!(s.warning_count(), 1);
2283 assert!(s.has_errors());
2284 assert_eq!(s.diagnostics().len(), 2);
2285 }
2286
2287 #[test]
2288 fn werror_promotes_once_at_the_sink() {
2289 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
2290 opts.warnings_are_errors = true;
2291 let mut s = Session::new(opts);
2292 s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
2293 assert_eq!(s.error_count(), 1);
2294 assert_eq!(s.warning_count(), 0);
2295 assert_eq!(s.diagnostics()[0].severity, Severity::Error);
2296 }
2297
2298 #[test]
2299 fn the_error_limit_can_be_switched_off() {
2300 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
2301 opts.error_limit = 0;
2302 let mut s = Session::new(opts);
2303 for _ in 0..100 {
2304 s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
2305 }
2306 assert!(!s.error_limit_reached());
2307 }
2308
2309 #[test]
2310 fn the_session_carries_the_source_map_spans_are_resolved_against() {
2311 let mut s = session();
2312 let file = s.sources.add("a.c", b"int x;\n".to_vec()).unwrap();
2313 let start = s.sources.file(file).start;
2314 assert_eq!(s.sources.render_position(start + 4), "a.c:1:5");
2315 }
2316
2317 #[test]
2318 fn the_session_carries_the_resolved_target() {
2319 let s = session();
2320 assert_eq!(s.target.pointer_width, 64);
2321 assert!(s.target.char_is_signed);
2322 }
2323}