1#![doc(html_root_url = "https://docs.rs/rucc-driver/0.16.1")]
30
31pub mod assemble;
32pub mod cache;
33pub mod compile;
34pub mod deps;
35pub mod dlltool;
36pub mod fetch;
37mod glibc;
38pub mod install;
39pub mod library;
40pub mod link;
41mod map;
42pub mod msvc;
43pub mod phase;
44pub mod preprocess;
45pub mod schedule;
46mod shapes;
47pub mod trace;
48mod warnings;
49
50use std::fmt::Write as _;
51use std::io::Write as _;
52use std::path::PathBuf;
53
54use rucc_codegen::coverage::{self, Fired};
55use rucc_codegen::lowering::Lowerings;
56use rucc_codegen::pressure::Pressure;
57use rucc_pp::Dependency;
58use rucc_session::{
59 Compress, Control, Dumps, EmitKind, Hook, Math, Options, Pic, PrefixMap, Preinclude, Protector,
60 SaveTemps, Session, Std, Wrapping, runtime,
61};
62use rucc_sysroot::{Manifest, Sysroot};
63use rucc_target::{ObjectFormat, Triple};
64use rucc_tuple::TargetTuple;
65
66use crate::link::LinkOptions;
67
68pub use crate::assemble::assemble;
69pub use crate::compile::{Artifact, Compiled, Temps, compile, compile_ir};
70pub use crate::phase::{ArchiveJob, Input, InputKind, Job, LinkJob, Output, Phase, Plan, Role};
71pub use crate::preprocess::{OsFileSystem, Preprocessed, preprocess};
72pub use crate::schedule::Jobs;
73
74pub const VERSION: &str = env!("CARGO_PKG_VERSION");
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum Action {
80 Help,
82 Version,
84 Print(String),
90 PrintConfig(Box<Options>),
92 PrintPipeline(Box<Options>),
94 PrintPlan {
96 opts: Box<Options>,
98 plan: Box<Plan>,
100 link: Box<LinkOptions>,
102 },
103 Fetch {
109 what: &'static rucc_sysroot::Pinned,
113 target: TargetTuple,
116 cache: PathBuf,
118 },
119 FetchMsvcSdk {
128 target: TargetTuple,
130 accepted: bool,
133 cache: PathBuf,
135 pinned: bool,
138 },
139 Compile {
141 opts: Box<Options>,
143 plan: Box<Plan>,
145 link: Box<LinkOptions>,
147 jobs: Jobs,
149 verbose: bool,
151 notes: Vec<String>,
159 },
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct CliError {
165 pub message: String,
168}
169
170impl std::fmt::Display for CliError {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.write_str(&self.message)
173 }
174}
175
176impl std::error::Error for CliError {}
177
178fn err(message: impl Into<String>) -> CliError {
179 CliError { message: message.into() }
180}
181
182fn rewrite<'a>(arg: &'a str, flag: &str) -> Result<(&'a str, &'a str), CliError> {
189 let rest = &arg[flag.len()..];
190 PrefixMap::split(rest).ok_or_else(|| {
191 let flag = flag.trim_end_matches('=');
192 err(format!(
193 "`{rest}` is not a rewrite for `{flag}`, which is an old prefix, an `=` and a new one"
194 ))
195 })
196}
197
198enum Query {
204 Machine,
206 Version,
208 FullVersion,
210 Multiarch,
212 SearchDirs,
214 Sysroot,
216 SysrootProvenance,
218 SysrootDigest,
220 FileName(String),
222 ProgName(String),
224 Libgcc,
226}
227
228pub const USAGE: &str = "\
233rucc, an optimizing C compiler
234
235usage: rucc [options] file...
236
237options:
238 -c compile and assemble, do not link
239 -S compile only, emit assembly
240 -E preprocess only
241 -o <file> write output to <file>, or to standard output for -
242 -D <name>[=<value>], -U <name> define a macro, or undefine one after every -D
243 -I <dir> add <dir> to the include search path
244 -iquote -isystem -idirafter <dir> the other chains, -nostdinc drops ours
245 -I-, -iprefix <p>, -iwithprefix[before] <dir> the older spellings of those
246 -include <file>, -imacros <file> read <file> first, the second for its macros only
247 --sysroot=<dir> look for the library's headers under <dir>, -isysroot too
248 -P, -dM with -E: leave out the markers, or dump the macros
249 -M -MM -MD -MMD write a make rule for the source, the last two compile as well
250 -MF <file> -MT <t> -MQ <t> -MP where the rule goes, what it builds, targets with no recipe
251 -std=<dialect> c89 through c2y, and the gnu spellings
252 -fgnuc-version=<v> -fms-compatibility-version=<v> the GCC (16.0.0) or MSVC (19.40) to claim
253 -x <lang> treat later inputs as <lang>, or none to stop
254 -O<level> optimize: 0, 1, 2, 3, s, z, fast
255 -fsafety=<tier> check memory safety: off, detect, enforce, kernel
256 -f[no-]sanitize=<what> the negative is taken, the positive is refused by name
257 -f[no-]safety-subobject a write has to stay inside the member it names
258 -f[no-]safety-restrict two restrict pointers of one block may not meet
259 -f<pass> -fno-<pass> -fdump-ir=<what> -fopt-info[-<kind>][=FILE]
260 -fpass-fuel=<pass>=<n>, -fpass-fuel-global=<n> stop a pass, or all of them, after n
261 -fdisable-<pass>[=<funcs>], -fenable-<pass>[=<funcs>] run a pass on some functions only
262 -g -g0 -gdwarf-5, -fno-omit-frame-pointer, -mno-red-zone debug info, frame pointer, red zone
263 -gz[=none|zlib|zlib-gnu|zstd] -gno-split-dwarf compress debug sections, one file not two
264 -flto[=auto|jobserver|<n>] -fno-lto -ffat-lto-objects read, and not done yet
265 -fprofile-use[=<path>] -fprofile-dir=<dir> read too, where -fprofile-generate is refused
266 -f[no-]stack-protector[-strong|-all], -f[no-]stack-clash-protection, -fcf-protection=<edges>
267 -ffunction-sections -fdata-sections a section per function or variable, for --gc-sections
268 -fvisibility=<what> default, hidden, internal or protected, when nothing in the source said
269 -l<name>, -L <dir>, -B <dir> link a library, where to look for one, where our own tools are
270 -fPIC -fpic -fPIE -fpie, -pipe what it does anyway, and -f[no-]common as the target's cc
271 -f[no-]strict-aliasing, -f[no-]delete-null-pointer-checks what it assumes anyway
272 -static -shared -pie -no-pie -nostdlib -nostartfiles -nodefaultlibs -rdynamic -s how to link
273 -Wl,<arg>, -Xlinker <arg>, -fuse-ld=<name> hand an argument to the linker, or pick one
274 -Werror -pedantic -pedantic-errors -w -W[no-]system-headers how much to say, and how fatal
275 -m64 -march= -mtune= -mcpu= -mabi= -mcmodel= what machine to generate for
276 -pg -p, -mfentry -mno-fentry call a profiler on the way in, and where that call goes
277 -fpatchable-function-entry=<n>[,<m>] room at the top of every function to patch later
278 -fwrapv, -fwrapv-pointer, -fno-strict-overflow, -ftrapv overflow wraps, or stops the program
279 -f[no-]exceptions, -f[no-]non-call-exceptions let an exception unwind through the code
280 -f[no-]signed-char, -f[no-]unsigned-char, -f[no-]short-enums change the ABI
281 -ffp-contract=<how> fuse a multiply and an addition: fast, on or off
282 -f[no-]fast-math and each of its members, -f[no-]rounding-math, -fexcess-precision=<how>
283 -ffile-prefix-map=<old>=<new> rewrite that front of every path we put in the output
284 -fmacro-prefix-map= -fdebug-prefix-map= -fprofile-prefix-map= the same, one output each
285 -pthread build for more than one thread, and link the library for it
286 -dumpmachine -dumpversion -print-multiarch -print-search-dirs what this compiler is
287 -print-file-name=<name> -print-prog-name=<name> where a file or a program is
288 -print-sysroot the root the headers and the libraries are read under
289 -print-sysroot-provenance every input under it, where it came from and its licence
290 -print-sysroot-digest the sha256 of that record, which names the whole sysroot in one line
291 --fetch <tuple> get the sysroot this release pins for <tuple> and install it in the cache
292 --fetch-msvc-sdk <tuple> the same for *-windows-msvc, from Microsoft's current build
293 --offline never download anything, which a compilation never does anyway
294 --dlltool <args> write an import library from a .def, as dlltool; --dlltool --help says how
295 -j[n] compile n translation units at once, default all
296 -v, -### print each phase as it runs, or without running any
297 -save-temps[=cwd|obj], -fstack-usage, -time keep the .i and .s, write a .su, time each step
298 --target=<triple> generate code for <triple>, which a name like <triple>-rucc also does
299 --emit=<kind> exe, obj, archive, asm, preprocessed, tast, ir, mir-final,
300 safety-summary, type-granules
301 --print-config, --print-pipeline print the configuration or the pipeline, and exit
302 --version print the version and exit
303 -h, --help print this message and exit
304
305See spec/04-driver-and-cli.md for the full flag reference.
306";
307
308fn joined_or_next(
312 arg: &str,
313 at: usize,
314 args: &[String],
315 i: &mut usize,
316) -> Result<String, CliError> {
317 if arg.len() > at {
318 return Ok(arg[at..].to_owned());
319 }
320 let next = args.get(*i).ok_or_else(|| err(format!("{arg} requires an argument")))?;
321 *i += 1;
322 Ok(next.clone())
323}
324
325const MIN_FUNC_ALIGN: u32 = 8;
331
332fn function_alignment(text: &str) -> Option<Option<u32>> {
342 let mut parts = text.split(':');
346 let first = parts.next()?;
347 if parts.any(|part| part.parse::<u64>().is_err()) {
348 return None;
349 }
350 let want: u64 = first.parse().ok()?;
351 if want <= 1 {
352 return Some(None);
353 }
354 let bytes = want.min(1 << 16).next_power_of_two();
355 Some(Some(u32::try_from(bytes).ok()?))
356}
357
358const SANITIZERS: [&str; 34] = [
371 "address",
372 "kernel-address",
373 "hwaddress",
374 "kernel-hwaddress",
375 "pointer-compare",
376 "pointer-subtract",
377 "thread",
378 "leak",
379 "undefined",
380 "shift",
381 "shift-base",
382 "shift-exponent",
383 "integer-divide-by-zero",
384 "unreachable",
385 "vla-bound",
386 "null",
387 "return",
388 "signed-integer-overflow",
389 "bounds",
390 "bounds-strict",
391 "alignment",
392 "object-size",
393 "float-divide-by-zero",
394 "float-cast-overflow",
395 "nonnull-attribute",
396 "returns-nonnull-attribute",
397 "bool",
398 "enum",
399 "vptr",
400 "pointer-overflow",
401 "builtin",
402 "alias",
403 "restrict",
404 "memory",
405];
406
407fn response_files(args: &[String]) -> Result<Vec<String>, CliError> {
424 const DEEPEST: usize = 64;
425 fn expand(args: &[String], depth: usize, out: &mut Vec<String>) -> Result<(), CliError> {
426 for arg in args {
427 let Some(name) = arg.strip_prefix('@') else {
428 out.push(arg.clone());
429 continue;
430 };
431 let Ok(text) = std::fs::read_to_string(name) else {
432 out.push(arg.clone());
433 continue;
434 };
435 if depth == DEEPEST {
436 return Err(err(format!("response file '{name}' is nested too deeply")));
437 }
438 expand(&response_words(&text), depth + 1, out)?;
439 }
440 Ok(())
441 }
442 if !args.iter().any(|arg| arg.starts_with('@')) {
443 return Ok(args.to_vec());
444 }
445 let mut out = Vec::with_capacity(args.len());
446 expand(args, 0, &mut out)?;
447 Ok(out)
448}
449
450fn response_words(text: &str) -> Vec<String> {
452 let mut words = Vec::new();
453 let mut word = String::new();
454 let mut begun = false;
457 let mut quote: Option<char> = None;
458 let mut chars = text.chars();
459 while let Some(c) = chars.next() {
460 match c {
461 '\\' => {
462 if let Some(next) = chars.next() {
463 word.push(next);
464 }
465 begun = true;
466 }
467 _ if quote == Some(c) => quote = None,
468 _ if quote.is_some() => word.push(c),
469 '\'' | '"' => {
470 quote = Some(c);
471 begun = true;
472 }
473 _ if c.is_whitespace() => {
474 if begun {
475 words.push(std::mem::take(&mut word));
476 begun = false;
477 }
478 }
479 _ => {
480 word.push(c);
481 begun = true;
482 }
483 }
484 }
485 if begun {
486 words.push(word);
487 }
488 words
489}
490
491fn preprocessor_args(args: &[String]) -> Vec<String> {
501 let mut out = Vec::with_capacity(args.len());
502 for arg in args {
503 let Some(list) = arg.strip_prefix("-Wp,") else {
504 out.push(arg.clone());
505 continue;
506 };
507 let words: Vec<&str> = list.split(',').collect();
508 let mut spelled = Vec::new();
509 let mut i = 0;
510 let understood = loop {
511 let Some(&word) = words.get(i) else {
512 break true;
513 };
514 i += 1;
515 match word {
516 "-MD" | "-MMD" | "-MF" | "-MT" | "-MQ" => {
517 let Some(&value) = words.get(i) else {
518 break false;
519 };
520 i += 1;
521 if word == "-MD" || word == "-MMD" {
522 spelled.extend([word.to_owned(), "-MF".to_owned()]);
523 } else {
524 spelled.push(word.to_owned());
525 }
526 spelled.push(value.to_owned());
527 }
528 "-MP" => spelled.push(word.to_owned()),
529 _ if word.len() > 2
530 && (word.starts_with("-D")
531 || word.starts_with("-U")
532 || word.starts_with("-I")) =>
533 {
534 spelled.push(word.to_owned());
535 }
536 _ => break false,
537 }
538 };
539 if understood {
540 out.extend(spelled);
541 } else {
542 out.push(arg.clone());
543 }
544 }
545 out
546}
547
548fn isa_name(arg: &str) -> Option<(&str, rucc_target::Feature, bool)> {
553 let rest = arg.strip_prefix("-m")?;
554 let (name, on) = match rest.strip_prefix("no-") {
555 Some(name) => (name, false),
556 None => (rest, true),
557 };
558 let known =
559 if on { rucc_target::Feature::named(name) } else { rucc_target::Feature::named_off(name) };
560 known.map(|feature| (name, feature, on))
561}
562
563fn native_isa() -> rucc_target::Isa {
572 let base = rucc_target::Isa::baseline();
573 #[cfg(target_arch = "x86_64")]
574 {
575 let mut isa = rucc_target::Choices::new();
576 macro_rules! asked {
577 ($($detected:tt => $name:literal),* $(,)?) => {
578 $(if std::arch::is_x86_feature_detected!($detected) {
579 isa.read($name).expect("a name gcc knows");
580 })*
581 };
582 }
583 asked! {
584 "sse3" => "sse3",
585 "ssse3" => "ssse3",
586 "sse4.1" => "sse4.1",
587 "sse4.2" => "sse4.2",
588 "sse4a" => "sse4a",
589 "popcnt" => "popcnt",
590 "avx" => "avx",
591 "avx2" => "avx2",
592 "fma" => "fma",
593 "f16c" => "f16c",
594 "bmi1" => "bmi",
595 "bmi2" => "bmi2",
596 "lzcnt" => "lzcnt",
597 "xsave" => "xsave",
598 "aes" => "aes",
599 "pclmulqdq" => "pclmul",
600 "sha" => "sha",
601 "cmpxchg16b" => "cx16",
602 "adx" => "adx",
603 "rdrand" => "rdrnd",
604 "rdseed" => "rdseed",
605 }
606 isa.over(base)
607 }
608 #[cfg(not(target_arch = "x86_64"))]
609 base
610}
611
612fn native_aarch64() -> rucc_target::Isa {
619 #[cfg(target_arch = "aarch64")]
620 {
621 if std::arch::is_aarch64_feature_detected!("crc") {
622 return rucc_target::Isa::aarch64_march("armv8-a+crc");
623 }
624 }
625 rucc_target::Isa::NONE
626}
627
628pub fn parse_args(args: &[String]) -> Result<Action, CliError> {
635 let expanded = preprocessor_args(&response_files(args)?);
636 let args = expanded.as_slice();
637 let host = Triple::host()
638 .ok_or_else(|| err("this host is not a supported target and no --target was given"))?;
639 let mut opts = Options::new(host);
640 opts.working_dir = std::env::current_dir().ok().map(|dir| dir.to_string_lossy().into_owned());
645 let mut inputs: Vec<Input> = Vec::new();
646 let mut print_config = false;
647 let mut print_pipeline = false;
648 let mut print_plan = false;
649 let mut verbose = false;
650 let mut jobs = Jobs::default();
651 let mut nostdinc = false;
652 let mut sysroot: Option<PathBuf> = None;
653 let mut notes: Vec<String> = Vec::new();
657 let mut pinned: Option<TargetTuple> = None;
661 let mut min_version: Option<rucc_tuple::Version> = None;
662 let mut output = None;
663 let mut link = LinkOptions::default();
664 let mut query: Option<Query> = None;
665 let mut fetch: Option<String> = None;
668 let mut fetch_msvc: Option<String> = None;
671 let mut accepted = false;
672 let mut offline = false;
673 let mut threads = false;
674 let mut sanitizers: Vec<&str> = Vec::new();
679 let mut math_flags: Vec<&str> = Vec::new();
683 let mut ofast = false;
684 let mut daz_ftz: Option<bool> = None;
687 let mut isa = rucc_target::Choices::new();
692 let mut isa_flag: Option<&str> = None;
693 let mut march: Option<&str> = None;
694 let mut exceptions: Option<bool> = None;
699 let mut forced: Option<InputKind> = None;
702 let mut iprefix = String::new();
709
710 let mut i = 0;
711 while i < args.len() {
712 let arg = args[i].as_str();
713 i += 1;
714 match arg {
715 "-h" | "--help" => return Ok(Action::Help),
716 "--version" => return Ok(Action::Version),
717 "--fetch" => {
722 let value = args
723 .get(i)
724 .ok_or_else(|| err("--fetch requires the target to get a sysroot for"))?;
725 i += 1;
726 fetch = Some(value.clone());
727 }
728 _ if arg.starts_with("--fetch=") => {
729 fetch = Some(arg["--fetch=".len()..].to_owned());
730 }
731 "--fetch-msvc-sdk" => {
735 let value = args.get(i).ok_or_else(|| {
736 err("--fetch-msvc-sdk requires the target to get the SDK for")
737 })?;
738 i += 1;
739 fetch_msvc = Some(value.clone());
740 }
741 _ if arg.starts_with("--fetch-msvc-sdk=") => {
742 fetch_msvc = Some(arg["--fetch-msvc-sdk=".len()..].to_owned());
743 }
744 "--accept-licence" | "--accept-license" => accepted = true,
748 "--offline" => offline = true,
754 "--dlltool" => {
757 return Err(err(
758 "--dlltool has to be the first argument, since everything after it is a \
759 dlltool command line rather than a compiler one",
760 ));
761 }
762 "--print-config" => print_config = true,
763 "--print-pipeline" => print_pipeline = true,
764 "-###" => print_plan = true,
765 "-v" => verbose = true,
766 "-save-temps" => opts.save_temps = SaveTemps::Object,
770 _ if arg.starts_with("-save-temps=") => {
771 opts.save_temps = arg["-save-temps=".len()..].parse().map_err(err)?;
772 }
773 "-fstack-usage" => opts.stack_usage = true,
776 "-fno-stack-usage" => opts.stack_usage = false,
777 "-time" => opts.time = true,
780 "-c" => opts.emit = EmitKind::Object,
781 "-S" => opts.emit = EmitKind::Asm,
782 "-E" => opts.emit = EmitKind::Preprocessed,
783 "-fsyntax-only" => opts.emit = EmitKind::SyntaxOnly,
784 "-g" => opts.debug_info = true,
785 "-g0" => opts.debug_info = false,
790 "-g1" | "-g2" | "-g3" | "-ggdb" | "-ggdb1" | "-ggdb2" | "-ggdb3" => {
791 opts.debug_info = true;
792 }
793 "-gdwarf" | "-gdwarf-5" => opts.debug_info = true,
796 _ if arg.starts_with("-gdwarf-") => {
797 return Err(err(format!(
798 "{arg}: this compiler writes DWARF 5 and no other version, see \
799 spec/11-debug-info.md"
800 )));
801 }
802 "-gno-split-dwarf" => {}
810 "-gsplit-dwarf" => {
811 return Err(err(format!(
812 "{arg}: this compiler writes no separate `.dwo` file, and a build that \
813 expects one beside each object would wait for a file that never arrives, \
814 see spec/11-debug-info.md"
815 )));
816 }
817 "-gz" => opts.compress = Compress::Zlib,
823 _ if arg.starts_with("-gz=") => {
824 let how = &arg["-gz=".len()..];
825 opts.compress = how.parse().map_err(|()| {
826 err(format!(
827 "`{how}` is not a way to compress debug sections, which is none, zlib, \
828 zlib-gnu or zstd"
829 ))
830 })?;
831 }
832 "-Werror" => opts.warnings_are_errors = true,
833 "-w" => opts.warnings = false,
836 "-Wsystem-headers" => opts.system_header_warnings = true,
841 "-Wno-system-headers" => opts.system_header_warnings = false,
842 "-pedantic-errors" => {
843 opts.pedantic = true;
844 opts.warnings_are_errors = true;
845 }
846 "-P" => opts.line_markers = false,
847 "-M" => {
854 opts.deps.emit = true;
855 opts.deps.instead_of_compiling = true;
856 }
857 "-MM" => {
858 opts.deps.emit = true;
859 opts.deps.instead_of_compiling = true;
860 opts.deps.system_headers = false;
861 }
862 "-MD" => opts.deps.emit = true,
863 "-MMD" => {
864 opts.deps.emit = true;
865 opts.deps.system_headers = false;
866 }
867 "-MP" => opts.deps.phony = true,
868 "-MF" | "-MT" | "-MQ" => {
871 let value =
872 args.get(i).ok_or_else(|| err(format!("{arg} requires an argument")))?;
873 i += 1;
874 match arg {
875 "-MF" => opts.deps.file = Some(value.clone()),
876 "-MT" => opts.deps.targets.push(value.clone()),
880 _ => opts.deps.targets.push(deps::escaped(value)),
881 }
882 }
883 "-dumpmachine" => query = Some(Query::Machine),
887 "-dumpversion" => query = Some(Query::Version),
892 "-dumpfullversion" => query = Some(Query::FullVersion),
893 "-print-multiarch" => query = Some(Query::Multiarch),
894 "-print-search-dirs" => query = Some(Query::SearchDirs),
895 "-print-sysroot" => query = Some(Query::Sysroot),
896 "-print-sysroot-provenance" | "--print-sysroot-provenance" => {
902 query = Some(Query::SysrootProvenance);
903 }
904 "-print-sysroot-digest" | "--print-sysroot-digest" => {
905 query = Some(Query::SysrootDigest);
906 }
907 "-print-libgcc-file-name" => query = Some(Query::Libgcc),
908 _ if arg.starts_with("-print-file-name=") => {
909 query = Some(Query::FileName(arg["-print-file-name=".len()..].to_owned()));
910 }
911 _ if arg.starts_with("-print-prog-name=") => {
912 query = Some(Query::ProgName(arg["-print-prog-name=".len()..].to_owned()));
913 }
914 "-pthread" | "-pthreads" => {
919 opts.defines.push("_REENTRANT".to_owned());
920 threads = true;
921 }
922 "-ansi" => {
923 opts.std = Std::C89;
924 opts.gnu_extensions = false;
925 }
926 "-pedantic" | "-Wpedantic" => opts.pedantic = true,
929 "-fpermissive" => opts.permissive = true,
932 "-fno-permissive" => opts.permissive = false,
933 "-ffreestanding" => opts.hosted = false,
934 "-fhosted" => opts.hosted = true,
935 "-fno-builtin" => opts.builtins = false,
936 "-fbuiltin" => opts.builtins = true,
937 "-fgnu89-inline" => opts.gnu89_inline = true,
941 "-fno-gnu89-inline" => opts.gnu89_inline = false,
942 "-fno-omit-frame-pointer" => opts.frame_pointer = Some(true),
945 "-fomit-frame-pointer" => opts.frame_pointer = Some(false),
946 "-freorder-blocks" => opts.reorder_blocks = Some(true),
949 "-fno-reorder-blocks" => opts.reorder_blocks = Some(false),
950 "-fschedule-insns2" => opts.schedule_insns = Some(true),
955 "-fno-schedule-insns2" => opts.schedule_insns = Some(false),
956 "-foptimize-sibling-calls" => opts.sibling_calls = Some(true),
958 "-fno-optimize-sibling-calls" => opts.sibling_calls = Some(false),
959 "-mno-red-zone" => opts.red_zone = false,
960 "-mred-zone" => opts.red_zone = true,
961 "-fno-stack-protector" | "-fno-stack-protector-all" | "-fno-stack-protector-strong" => {
966 opts.protector = Protector::None;
967 }
968 "-fstack-protector" => opts.protector = Protector::Buffers,
969 "-fstack-protector-strong" => opts.protector = Protector::Strong,
970 "-fstack-protector-all" => opts.protector = Protector::All,
971 "-fstack-clash-protection" => opts.stack_clash = true,
974 "-fno-stack-clash-protection" => opts.stack_clash = false,
975 "-fcf-protection" => opts.control = Control::Full,
979 "-fno-cf-protection" => opts.control = Control::None,
980 "-pg" | "-p" => {
984 opts.profile = true;
985 link.profile = true;
986 }
987 "-mfentry" => opts.hook = Hook::Early,
992 "-mno-fentry" => opts.hook = Hook::Late,
993 "-nostdinc" => nostdinc = true,
997 "-o" => {
998 output = Some(args.get(i).ok_or_else(|| err("-o requires an argument"))?.clone());
999 i += 1;
1000 }
1001 "-dumpbase" | "-dumpbase-ext" | "-dumpdir" => {
1006 let value =
1007 args.get(i).ok_or_else(|| err(format!("{arg} requires an argument")))?.clone();
1008 i += 1;
1009 match arg {
1010 "-dumpbase" => opts.dump_base = Some(value),
1011 "-dumpbase-ext" => opts.dump_base_ext = Some(value),
1012 _ => opts.dump_dir = Some(value),
1013 }
1014 }
1015 "-isysroot" => {
1022 let dir = args.get(i).ok_or_else(|| err("-isysroot requires an argument"))?;
1023 i += 1;
1024 sysroot = Some(PathBuf::from(dir));
1025 }
1026 "-iquote" | "-isystem" | "-idirafter" => {
1027 let dir = args.get(i).ok_or_else(|| err(format!("{arg} requires an argument")))?;
1028 i += 1;
1029 match arg {
1030 "-iquote" => opts.search.push_quote(dir.clone()),
1031 "-isystem" => opts.search.push_system(dir.clone()),
1032 _ => opts.search.push_after(dir.clone()),
1033 }
1034 }
1035 "-iprefix" => {
1036 iprefix = args.get(i).ok_or_else(|| err("-iprefix requires an argument"))?.clone();
1037 i += 1;
1038 }
1039 "-iwithprefix" | "-iwithprefixbefore" => {
1045 let dir = args.get(i).ok_or_else(|| err(format!("{arg} requires an argument")))?;
1046 i += 1;
1047 let dir = format!("{iprefix}{dir}");
1048 if arg == "-iwithprefix" {
1049 opts.search.push_system(dir);
1050 } else {
1051 opts.search.push_bracket(dir);
1052 }
1053 }
1054 "-include" | "-imacros" => {
1055 let name = args.get(i).ok_or_else(|| err(format!("{arg} requires an argument")))?;
1056 i += 1;
1057 opts.preincludes
1058 .push(Preinclude { name: name.clone(), macros_only: arg == "-imacros" });
1059 }
1060 "-I-" => opts.search.split_quote_chain(),
1065 _ if arg.starts_with("-x") => {
1068 let lang = joined_or_next(arg, 2, args, &mut i)?;
1069 forced = if lang == "none" {
1070 None
1071 } else {
1072 Some(InputKind::from_x_arg(&lang).map_err(|e| err(format!("{e}")))?)
1073 };
1074 }
1075 _ if arg.starts_with("-D") => {
1083 let value = joined_or_next(arg, 2, args, &mut i)?;
1084 opts.defines.push(value);
1085 }
1086 _ if arg.starts_with("-U") => {
1087 let value = joined_or_next(arg, 2, args, &mut i)?;
1088 opts.undefines.push(value);
1089 }
1090 _ if arg.starts_with("-I") => {
1091 let dir = joined_or_next(arg, 2, args, &mut i)?;
1092 opts.search.push_bracket(dir);
1093 }
1094 _ if arg.starts_with("-std=") => {
1095 let name = &arg["-std=".len()..];
1096 let (std, gnu) = Std::from_flag(name)
1097 .ok_or_else(|| err(format!("unknown dialect `{name}`, see --help")))?;
1098 opts.std = std;
1099 opts.gnu_extensions = gnu;
1100 }
1101 _ if Dumps::is_family(arg) => {
1110 opts.dumps.add(&arg[2..]);
1111 }
1112 _ if arg.starts_with("-fno-builtin-") => {
1117 opts.no_builtin.push(arg["-fno-builtin-".len()..].to_owned());
1118 }
1119 _ if arg.starts_with("-fgnuc-version=") => {
1120 let v = &arg["-fgnuc-version=".len()..];
1121 opts.gnuc = v.parse().map_err(err)?;
1122 opts.gnuc_given = true;
1123 }
1124 _ if arg.starts_with("-fms-compatibility-version=") => {
1126 let v = &arg["-fms-compatibility-version=".len()..];
1127 opts.msc = v.parse().map_err(err)?;
1128 }
1129 _ if arg.starts_with("-fms-runtime-lib=") => {
1136 let dll = match &arg["-fms-runtime-lib=".len()..] {
1137 "static" => false,
1138 "dll" => true,
1139 debug @ ("static_dbg" | "dll_dbg") => {
1140 return Err(err(format!(
1141 "-fms-runtime-lib={debug} asks for Microsoft's debug C runtime, which \
1142 this compiler does not link against yet. static and dll are the two \
1143 it has"
1144 )));
1145 }
1146 other => {
1147 return Err(err(format!(
1148 "-fms-runtime-lib= takes static or dll, and `{other}` is neither"
1149 )));
1150 }
1151 };
1152 opts.ms_dll_runtime = dll;
1153 link.crt = if dll { rucc_sysroot::Crt::Dll } else { rucc_sysroot::Crt::Static };
1154 }
1155 "-fnested-functions" => {
1160 return Err(err(
1161 "nested functions are not supported: a call to one goes through a trampoline \
1162 written on the stack, which no target that enforces an unexecutable stack \
1163 allows",
1164 ));
1165 }
1166 "-fno-nested-functions" => {}
1167 "-fPIC" | "-fpic" => opts.pic = Pic::Library,
1178 "-fPIE" | "-fpie" => opts.pic = Pic::Executable,
1182 "-fsemantic-interposition" => opts.interposition = true,
1189 "-fno-semantic-interposition" => opts.interposition = false,
1190 "-fasynchronous-unwind-tables" => opts.async_unwind_tables = true,
1195 "-fno-asynchronous-unwind-tables" => opts.async_unwind_tables = false,
1196 "-funwind-tables" => opts.unwind_tables = true,
1197 "-fno-unwind-tables" => opts.unwind_tables = false,
1198 "-fno-pic" | "-fno-pie" => {
1205 return Err(err(
1206 "position dependent code is not supported: an address that may be in another \
1207 object is loaded out of the global offset table, and nothing here emits the \
1208 absolute form this asks for. Use -no-pie if what you meant was how to link",
1209 ));
1210 }
1211 "-ffunction-sections" => opts.function_sections = true,
1217 "-fno-function-sections" => opts.function_sections = false,
1218 "-fdata-sections" => opts.data_sections = true,
1219 "-fno-data-sections" => opts.data_sections = false,
1220 "-fcommon" => opts.common = Some(true),
1224 "-fno-common" => opts.common = Some(false),
1225 "-fwrapv" => {
1240 opts.wrapping.signed = true;
1241 opts.wrapping.trap = false;
1242 }
1243 "-fno-wrapv" => opts.wrapping.signed = false,
1244 "-fwrapv-pointer" => opts.wrapping.pointer = true,
1245 "-fno-wrapv-pointer" => opts.wrapping.pointer = false,
1246 "-fno-strict-overflow" => opts.wrapping = Wrapping::ALL,
1247 "-fstrict-overflow" => {
1251 opts.wrapping.signed = false;
1252 opts.wrapping.pointer = false;
1253 }
1254 "-ftrapv" => {
1255 opts.wrapping.trap = true;
1256 opts.wrapping.signed = false;
1257 }
1258 "-fno-trapv" => opts.wrapping.trap = false,
1259 "-fsigned-char" | "-fno-unsigned-char" => opts.char_signed = Some(true),
1266 "-funsigned-char" | "-fno-signed-char" => opts.char_signed = Some(false),
1267 "-fshort-enums" => opts.short_enums = true,
1270 "-fno-short-enums" => opts.short_enums = false,
1271 "-fms-extensions" => opts.ms_extensions = Some(true),
1276 "-fno-ms-extensions" => opts.ms_extensions = Some(false),
1277 "-fstrict-aliasing" => opts.strict_aliasing = true,
1292 "-fno-strict-aliasing" => opts.strict_aliasing = false,
1293 "-fdelete-null-pointer-checks" | "-fno-delete-null-pointer-checks" => {}
1308 "-frounding-math" | "-fno-rounding-math" => {}
1326 "-ftrapping-math"
1344 | "-fno-trapping-math"
1345 | "-ffast-math"
1346 | "-fno-fast-math"
1347 | "-funsafe-math-optimizations"
1348 | "-fno-unsafe-math-optimizations"
1349 | "-fmath-errno"
1350 | "-fno-math-errno"
1351 | "-ffinite-math-only"
1352 | "-fno-finite-math-only"
1353 | "-fsigned-zeros"
1354 | "-fno-signed-zeros"
1355 | "-freciprocal-math"
1356 | "-fno-reciprocal-math"
1357 | "-fassociative-math"
1358 | "-fno-associative-math" => math_flags.push(arg),
1359 "-mdaz-ftz" => daz_ftz = Some(true),
1362 "-mno-daz-ftz" => daz_ftz = Some(false),
1363 "-pipe" => {}
1366 "-cpp" => {}
1369 "-fdiagnostics-color" | "-fno-diagnostics-color" => {}
1376 _ if arg.starts_with("-fdiagnostics-color=") => {}
1377 "-static" => link.is_static = true,
1381 "-shared" => link.shared = true,
1382 "-r" => link.relocatable = true,
1383 "-pie" => link.pie = Some(true),
1384 "-no-pie" | "-nopie" => link.pie = Some(false),
1385 "-nostdlib" => link.no_stdlib = true,
1386 "-nostartfiles" => link.no_startfiles = true,
1387 "-nodefaultlibs" => link.no_defaultlibs = true,
1388 "-fno-builtins-lib" => link.no_builtins_lib = true,
1389 "-fbuiltins-lib" => link.no_builtins_lib = false,
1390 "-rdynamic" | "-export-dynamic" => link.export_dynamic = true,
1391 "-s" => link.strip = true,
1392 "-mwindows" => link.gui = true,
1398 "-mconsole" => link.gui = false,
1399 "-municode" => {
1400 link.unicode = true;
1401 opts.defines.push("UNICODE".to_owned());
1402 }
1403 "-Xlinker" => {
1407 let next = args.get(i).ok_or_else(|| err("-Xlinker requires an argument"))?;
1408 i += 1;
1409 inputs.push(Input::linker(next));
1410 }
1411 _ if arg.starts_with("-Wl,") => {
1412 inputs.extend(arg["-Wl,".len()..].split(',').map(Input::linker));
1415 }
1416 _ if arg.starts_with("-fuse-ld=") => {
1417 link.use_ld = Some(arg["-fuse-ld=".len()..].to_owned());
1418 }
1419 _ if arg.starts_with("-l") && arg.len() > 2 => {
1420 inputs.push(Input::library(&arg[2..]));
1421 }
1422 "-l" => {
1423 let next = args.get(i).ok_or_else(|| err("-l requires an argument"))?;
1424 i += 1;
1425 inputs.push(Input::library(next));
1426 }
1427 _ if arg.starts_with("-L") => {
1428 link.search.push(PathBuf::from(joined_or_next(arg, 2, args, &mut i)?));
1429 }
1430 _ if arg.starts_with("-B") => {
1431 link.prefixes.push(PathBuf::from(joined_or_next(arg, 2, args, &mut i)?));
1432 }
1433 _ if arg.starts_with("-j") => {
1434 jobs = Jobs::parse(&arg[2..]).map_err(err)?;
1435 }
1436 _ if arg.starts_with("--sysroot=") => {
1437 sysroot = Some(PathBuf::from(&arg["--sysroot=".len()..]));
1438 }
1439 _ if arg.starts_with("--target=") => {
1440 let t = &arg["--target=".len()..];
1441 pinned = t.parse().ok();
1446 opts.target = match t.parse() {
1449 Ok(triple) => triple,
1450 Err(e) => {
1451 pinned.and_then(Triple::from_tuple).ok_or_else(|| err(format!("{e}")))?
1452 }
1453 };
1454 }
1455 _ if arg.starts_with("--emit=") => {
1456 let k = &arg["--emit=".len()..];
1457 opts.emit = k
1458 .parse()
1459 .map_err(|()| err(format!("unknown --emit kind `{k}`, see --help")))?;
1460 }
1461 "-O" | "-Og" => {
1467 opts.opt_level = rucc_session::OptLevel::O1;
1468 ofast = false;
1469 }
1470 "-Ofast" => {
1474 opts.opt_level = rucc_session::OptLevel::O3;
1475 ofast = true;
1476 }
1477 _ if arg.starts_with("-O") => {
1478 ofast = false;
1479 opts.opt_level = arg[2..]
1480 .parse()
1481 .map_err(|()| err(format!("unknown optimization level `{arg}`")))?;
1482 }
1483 _ if arg.starts_with("-ffp-contract=") => {
1490 let how = &arg["-ffp-contract=".len()..];
1491 opts.fp_contract = how.parse().map_err(|()| {
1492 err(format!("`{how}` is not a contraction, which is fast, on or off"))
1493 })?;
1494 }
1495 _ if arg.starts_with("-fstack-reuse=") => {
1511 let how = &arg["-fstack-reuse=".len()..];
1512 opts.stack_reuse = match how {
1513 "all" | "named_vars" => Some(true),
1514 "none" => Some(false),
1515 _ => {
1516 return Err(err(format!(
1517 "`{how}` is not a stack reuse, which is all, named_vars or none"
1518 )));
1519 }
1520 };
1521 }
1522 _ if arg.starts_with("-fexcess-precision=") => {
1523 let how = &arg["-fexcess-precision=".len()..];
1524 if !matches!(how, "16" | "fast" | "standard") {
1525 return Err(err(format!(
1526 "`{how}` is not an excess precision, which is 16, fast or standard"
1527 )));
1528 }
1529 }
1530 _ if arg.starts_with("-fmacro-prefix-map=") => {
1539 let (old, new) = rewrite(arg, "-fmacro-prefix-map=")?;
1540 opts.prefix_map.macros.push(old, new);
1541 }
1542 _ if arg.starts_with("-fdebug-prefix-map=") => {
1543 let (old, new) = rewrite(arg, "-fdebug-prefix-map=")?;
1544 opts.prefix_map.debug.push(old, new);
1545 }
1546 _ if arg.starts_with("-fprofile-prefix-map=") => {
1547 let (old, new) = rewrite(arg, "-fprofile-prefix-map=")?;
1548 opts.prefix_map.profile.push(old, new);
1549 }
1550 _ if arg.starts_with("-ffile-prefix-map=") => {
1551 let (old, new) = rewrite(arg, "-ffile-prefix-map=")?;
1552 opts.prefix_map.macros.push(old, new);
1553 opts.prefix_map.debug.push(old, new);
1554 opts.prefix_map.profile.push(old, new);
1555 }
1556 "-flto" => opts.lto.requested = true,
1567 "-fno-lto" => opts.lto.requested = false,
1568 _ if arg.starts_with("-flto=") => {
1569 let how = &arg["-flto=".len()..];
1570 opts.lto.jobs = how.parse().map_err(|()| {
1571 err(format!(
1572 "`{how}` is not a number of link time jobs, which is auto, jobserver or a \
1573 count above zero"
1574 ))
1575 })?;
1576 opts.lto.requested = true;
1577 }
1578 _ if arg.starts_with("-flto-partition=") => {
1579 let how = &arg["-flto-partition=".len()..];
1580 opts.lto.partition = how.parse().map_err(|()| {
1581 err(format!(
1582 "`{how}` is not a partitioning model, which is balanced, 1to1, one, max \
1583 or none"
1584 ))
1585 })?;
1586 }
1587 _ if arg.starts_with("-flto-compression-level=") => {
1588 let how = &arg["-flto-compression-level=".len()..];
1589 let level =
1590 how.parse::<u8>().ok().filter(|level| *level <= 19).ok_or_else(|| {
1591 err(format!("`{how}` is not a compression level, 0 to 19"))
1592 })?;
1593 opts.lto.compression = Some(level);
1594 }
1595 "-ffat-lto-objects" | "-fno-fat-lto-objects" => {}
1600 "-fuse-linker-plugin" | "-fno-use-linker-plugin" => {}
1604 "-fprofile-use" => opts.profile_data.requested = true,
1613 "-fno-profile-use" => opts.profile_data.requested = false,
1614 _ if arg.starts_with("-fprofile-use=") => {
1615 opts.profile_data.path = Some(arg["-fprofile-use=".len()..].to_string());
1616 opts.profile_data.requested = true;
1617 }
1618 _ if arg.starts_with("-fprofile-dir=") => {
1619 opts.profile_data.dir = Some(arg["-fprofile-dir=".len()..].to_string());
1620 }
1621 "-fprofile-abs-path" => opts.profile_data.absolute = true,
1622 "-fno-profile-abs-path" => opts.profile_data.absolute = false,
1623 "-fprofile-correction" => opts.profile_data.correction = true,
1624 "-fno-profile-correction" => opts.profile_data.correction = false,
1625 "-fprofile-partial-training" => opts.profile_data.partial_training = true,
1626 "-fno-profile-partial-training" => opts.profile_data.partial_training = false,
1627 "-fprofile-arcs"
1637 | "--coverage"
1638 | "-fcondition-coverage"
1639 | "-fpath-coverage"
1640 | "-fprofile-generate" => {
1641 return Err(err(format!(
1642 "{arg}: this compiler does not instrument for profiling, and a build that \
1643 expects the counts a run of the instrumented program writes would optimize \
1644 against nothing on its second pass, see spec/04-driver-and-cli.md"
1645 )));
1646 }
1647 _ if arg.starts_with("-fprofile-generate=") => {
1648 return Err(err(format!(
1649 "{arg}: this compiler does not instrument for profiling, and a build that \
1650 expects the counts a run of the instrumented program writes would optimize \
1651 against nothing on its second pass, see spec/04-driver-and-cli.md"
1652 )));
1653 }
1654 "-ftest-coverage" => {
1655 return Err(err(format!(
1656 "{arg}: this compiler writes no `.gcno` file beside the object, and a build \
1657 that expects one would wait for a file that never arrives, see \
1658 spec/04-driver-and-cli.md"
1659 )));
1660 }
1661 _ if arg.starts_with("-fprofile-update=") => {
1667 let how = &arg["-fprofile-update=".len()..];
1668 if !matches!(how, "single" | "atomic" | "prefer-atomic") {
1669 return Err(err(format!(
1670 "`{how}` is not a profile update method, which is single, atomic or \
1671 prefer-atomic"
1672 )));
1673 }
1674 }
1675 _ if arg.starts_with("-fprofile-reproducible=") => {
1676 let how = &arg["-fprofile-reproducible=".len()..];
1677 if !matches!(how, "serial" | "parallel-runs" | "multithreaded") {
1678 return Err(err(format!(
1679 "`{how}` is not a profile reproducibility method, which is serial, \
1680 parallel-runs or multithreaded"
1681 )));
1682 }
1683 }
1684 "-fprofile-values" | "-fno-profile-values" | "-fprofile-info-section" => {}
1685 "-fno-test-coverage" | "-fno-profile-arcs" | "-fno-profile-generate" => {}
1686 _ if arg.starts_with("-fprofile-filter-files=")
1687 || arg.starts_with("-fprofile-exclude-files=")
1688 || arg.starts_with("-fprofile-note=") => {}
1689 _ if arg.starts_with("-fvisibility=") => {
1693 let seen = &arg["-fvisibility=".len()..];
1694 opts.visibility = seen.parse().map_err(|()| {
1695 err(format!(
1696 "`{seen}` is not a visibility, which is default, hidden, internal or \
1697 protected"
1698 ))
1699 })?;
1700 }
1701 _ if arg.starts_with("-fcf-protection=") => {
1705 let edges = &arg["-fcf-protection=".len()..];
1706 opts.control = edges.parse().map_err(|()| {
1707 err(format!(
1708 "`{edges}` is not a control flow protection, which is full, branch, \
1709 return, none or check"
1710 ))
1711 })?;
1712 }
1713 _ if arg.starts_with("-fpatchable-function-entry=") => {
1716 let room = &arg["-fpatchable-function-entry=".len()..];
1717 opts.patchable = room.parse().map_err(|()| {
1718 err(format!(
1719 "`{room}` is not an amount of room to reserve, which is a number of bytes and then, after a comma, how many of them go in front of the function's own label"
1720 ))
1721 })?;
1722 }
1723 _ if arg.starts_with("-fsafety=") => {
1728 let tier = &arg["-fsafety=".len()..];
1729 opts.safety = tier.parse().map_err(|()| {
1730 err(format!(
1731 "`{tier}` is not a safety tier, which is off, detect, enforce or kernel"
1732 ))
1733 })?;
1734 }
1735 _ if arg.starts_with("-fsafety-init=") => {
1740 let mode = &arg["-fsafety-init=".len()..];
1741 opts.padding = mode.parse().map_err(|()| {
1742 err(format!("`{mode}` is not a padding mode, which is padding or nopadding"))
1743 })?;
1744 }
1745 "-fsafety-subobject" => opts.subobject = rucc_session::Subobject::Members,
1750 "-fno-safety-subobject" => opts.subobject = rucc_session::Subobject::Off,
1751 _ if arg.starts_with("-fsafety-subobject=") => {
1752 let form = &arg["-fsafety-subobject=".len()..];
1753 return Err(err(format!(
1754 "`{form}` is not a form of -fsafety-subobject. The flag takes no value, and \
1755 the strict form of section 9.4 is tamnd/rucc#967"
1756 )));
1757 }
1758 "-fsafety-restrict" => opts.promise = rucc_session::Promise::Blocks,
1763 "-fno-safety-restrict" => opts.promise = rucc_session::Promise::Off,
1764 _ if arg.starts_with("-fsafety-restrict=") => {
1765 let form = &arg["-fsafety-restrict=".len()..];
1766 return Err(err(format!(
1767 "`{form}` is not a form of -fsafety-restrict. The flag takes no value."
1768 )));
1769 }
1770 _ if arg.starts_with("-fsafety-races=") => {
1775 let mode = &arg["-fsafety-races=".len()..];
1776 opts.races = mode.parse().map_err(|()| {
1777 err(format!("`{mode}` is not a race mode, which is off, metadata or pointer"))
1778 })?;
1779 }
1780 "-fno-safety-races" => opts.races = rucc_session::Races::Off,
1781 _ if arg.starts_with("-fsanitize=") => {
1788 for one in arg["-fsanitize=".len()..].split(',') {
1789 if one == "all" {
1790 return Err(err(
1793 "`-fsanitize=all` is not a gcc option, only `-fno-sanitize=all` is",
1794 ));
1795 }
1796 if !SANITIZERS.contains(&one) {
1797 return Err(err(format!(
1798 "`{one}` is not a sanitizer, see spec/04-driver-and-cli.md section 4.7"
1799 )));
1800 }
1801 if !sanitizers.contains(&one) {
1802 sanitizers.push(one);
1803 }
1804 }
1805 }
1806 _ if arg.starts_with("-fno-sanitize=") => {
1807 for one in arg["-fno-sanitize=".len()..].split(',') {
1808 if one == "all" {
1809 sanitizers.clear();
1810 continue;
1811 }
1812 if !SANITIZERS.contains(&one) {
1813 return Err(err(format!(
1814 "`{one}` is not a sanitizer, see spec/04-driver-and-cli.md section 4.7"
1815 )));
1816 }
1817 sanitizers.retain(|asked| *asked != one);
1818 }
1819 }
1820 _ if arg.starts_with("-fsanitize-recover=")
1825 || arg.starts_with("-fno-sanitize-recover=")
1826 || arg.starts_with("-fsanitize-trap=")
1827 || arg.starts_with("-fno-sanitize-trap=") =>
1828 {
1829 let how = arg.split_once('=').map_or("", |(_, rest)| rest);
1832 for one in how.split(',') {
1833 if one != "all" && !SANITIZERS.contains(&one) {
1834 return Err(err(format!(
1835 "`{one}` is not a sanitizer, see spec/04-driver-and-cli.md section 4.7"
1836 )));
1837 }
1838 }
1839 }
1840 "-fsanitize-undefined-trap-on-error"
1841 | "-fsanitize-address-use-after-scope"
1842 | "-fno-sanitize-address-use-after-scope" => {}
1843 _ if arg.starts_with("-fsanitize-sections=") => {}
1844 _ if arg.starts_with("-fsanitize-coverage=") => {
1849 let how = &arg["-fsanitize-coverage=".len()..];
1850 for one in how.split(',') {
1851 if !matches!(one, "trace-pc" | "trace-cmp") {
1852 return Err(err(format!(
1853 "`{one}` is not a coverage instrumentation, which is trace-pc or \
1854 trace-cmp"
1855 )));
1856 }
1857 }
1858 return Err(err(format!(
1859 "{arg}: this compiler generates no coverage callbacks, and a fuzzer built \
1860 with it would run without any feedback at all, see \
1861 spec/04-driver-and-cli.md section 4.7"
1862 )));
1863 }
1864 _ if arg.starts_with("-fpass-fuel=") => {
1868 let (name, count) = arg["-fpass-fuel=".len()..]
1869 .split_once('=')
1870 .ok_or_else(|| err("-fpass-fuel= is spelled <pass>=<count>"))?;
1871 if rucc_opt::pass::find(name).is_none() {
1872 return Err(err(format!(
1873 "`{name}` is not a pass this compiler has, see --print-pipeline"
1874 )));
1875 }
1876 let count: u32 = count
1877 .parse()
1878 .map_err(|_| err(format!("`{count}` is not a number of transformations")))?;
1879 opts.pass_fuel.push((name.to_owned(), count));
1880 }
1881 _ if arg.starts_with("-fpass-fuel-global=") => {
1882 let count = &arg["-fpass-fuel-global=".len()..];
1883 let count: u32 = count
1884 .parse()
1885 .map_err(|_| err(format!("`{count}` is not a number of transformations")))?;
1886 opts.pass_fuel_global = Some(count);
1887 }
1888 _ if arg.starts_with("-frucc-trace=") => {
1889 let path = &arg["-frucc-trace=".len()..];
1890 if path.is_empty() {
1891 return Err(err("-frucc-trace= needs a file to write to"));
1892 }
1893 opts.trace = Some(path.to_owned());
1894 }
1895 _ if arg == "-fopt-info"
1900 || arg.starts_with("-fopt-info=")
1901 || arg.starts_with("-fopt-info-") =>
1902 {
1903 let rest = &arg["-fopt-info".len()..];
1904 let (kinds, file) = match rest.split_once('=') {
1905 Some((kinds, file)) => (kinds, Some(file)),
1906 None => (rest, None),
1907 };
1908 let kinds = kinds.strip_prefix('-').unwrap_or(kinds);
1909 rucc_opt::Wants::none().add(kinds).map_err(err)?;
1910 opts.opt_info.push(kinds.to_owned());
1911 if let Some(file) = file {
1912 if file.is_empty() {
1913 return Err(err("-fopt-info= was given no file to write to"));
1914 }
1915 opts.opt_info_file = Some(file.to_owned());
1916 }
1917 }
1918 _ if arg.starts_with("-fdump-ir=") => {
1919 let spec = &arg["-fdump-ir=".len()..];
1922 rucc_opt::Dumps::default().add(spec).map_err(err)?;
1923 opts.dump_ir.push(spec.to_owned());
1924 }
1925 _ if arg.starts_with("-fdisable-") || arg.starts_with("-fenable-") => {
1931 let on = arg.starts_with("-fenable-");
1932 let spec = &arg[if on { "-fenable-".len() } else { "-fdisable-".len() }..];
1933 rucc_opt::Gates::default().add(on, spec).map_err(err)?;
1934 opts.pass_gates.push((on, spec.to_owned()));
1935 }
1936 "-funroll-loops" => opts.passes.push(("unroll".to_owned(), true)),
1943 "-fno-unroll-loops" => opts.passes.push(("unroll".to_owned(), false)),
1944 "-fipa-cp" => opts.passes.push((rucc_opt::ipcp::NAME.to_owned(), true)),
1949 "-fno-ipa-cp" => opts.passes.push((rucc_opt::ipcp::NAME.to_owned(), false)),
1950 "-fipa-sra" => opts.passes.push((rucc_opt::ipasra::NAME.to_owned(), true)),
1954 "-fno-ipa-sra" => opts.passes.push((rucc_opt::ipasra::NAME.to_owned(), false)),
1955 "-flibcall" => opts.passes.push((rucc_opt::libcall::NAME.to_owned(), true)),
1961 "-fno-libcall" => opts.passes.push((rucc_opt::libcall::NAME.to_owned(), false)),
1962 _ if arg.strip_prefix("-fno-").is_some_and(|n| rucc_opt::pass::find(n).is_some()) => {
1963 opts.passes.push((arg["-fno-".len()..].to_owned(), false));
1964 }
1965 _ if arg.strip_prefix("-f").is_some_and(|n| rucc_opt::pass::find(n).is_some()) => {
1966 opts.passes.push((arg["-f".len()..].to_owned(), true));
1967 }
1968 _ if arg.starts_with("-ftree-") || arg.starts_with("-fno-tree-") => {}
1989 _ if arg.starts_with("-fipa-") || arg.starts_with("-fno-ipa-") => {}
1990 "-fexpensive-optimizations" | "-fno-expensive-optimizations" => {}
1991 "-fmodulo-sched" | "-fno-modulo-sched" => {}
1992 "-fvect-cost-model" | "-fno-vect-cost-model" => {}
1993 _ if arg.starts_with("-fvect-cost-model=") || arg.starts_with("-fsimd-cost-model=") => {
1994 }
1995 "-fearly-inlining" | "-fno-early-inlining" => {}
1996 "-finline" => opts.passes.push((rucc_opt::inline::NAME.to_owned(), true)),
2000 "-fno-inline" => opts.passes.push((rucc_opt::inline::NAME.to_owned(), false)),
2001 "-finline-functions-called-once" => {
2004 opts.passes.push((rucc_opt::inline::ONCE.to_owned(), true));
2005 }
2006 "-fno-inline-functions-called-once" => {
2007 opts.passes.push((rucc_opt::inline::ONCE.to_owned(), false));
2008 }
2009 "-finline-functions"
2010 | "-fno-inline-functions"
2011 | "-finline-small-functions"
2012 | "-fno-inline-small-functions" => {}
2013 "-foptimize-strlen" | "-fno-optimize-strlen" => {}
2014 "-fira-share-spill-slots" | "-fno-ira-share-spill-slots" => {}
2015 "-falign-functions" => opts.align_functions = None,
2023 "-fno-align-functions" => opts.align_functions = Some(MIN_FUNC_ALIGN),
2024 _ if arg.starts_with("-falign-functions=") => {
2025 opts.align_functions = function_alignment(&arg["-falign-functions=".len()..])
2026 .ok_or_else(|| {
2027 err(format!("{arg}: the alignment has to be a number of bytes"))
2028 })?;
2029 }
2030 "-falign-loops" => opts.align_loops = Some(true),
2036 "-fno-align-loops" => opts.align_loops = Some(false),
2037 _ if arg.starts_with("-falign-labels")
2043 || arg.starts_with("-falign-loops=")
2044 || arg.starts_with("-falign-jumps")
2045 || arg.starts_with("-fno-align-labels")
2046 || arg.starts_with("-fno-align-jumps") => {}
2047 _ if arg.starts_with("-finput-charset=") => {
2054 let name = &arg["-finput-charset=".len()..];
2055 if !name.eq_ignore_ascii_case("utf-8") && !name.eq_ignore_ascii_case("utf8") {
2056 return Err(err(format!(
2057 "-finput-charset={name}: the preprocessor reads UTF-8 and has no \
2058 converter, so a file in another encoding would be read as though it were \
2059 UTF-8 rather than converted",
2060 )));
2061 }
2062 }
2063 "-fexceptions" => exceptions = Some(true),
2067 "-fno-exceptions" => exceptions = Some(false),
2068 "-fnon-call-exceptions" => opts.non_call_exceptions = true,
2069 "-fno-non-call-exceptions" => opts.non_call_exceptions = false,
2070 "-fdelete-dead-exceptions" | "-fno-delete-dead-exceptions" => {}
2074 "-finstrument-functions" => opts.instrument_functions = true,
2075 "-fno-instrument-functions" => opts.instrument_functions = false,
2076 "-Zverify-each" => opts.verify_each = true,
2082 _ if arg.starts_with("-Zrule-coverage=") => {
2083 let file = &arg["-Zrule-coverage=".len()..];
2084 if file.is_empty() {
2085 return Err(err("-Zrule-coverage= needs a file to write to"));
2086 }
2087 opts.rule_coverage = Some(file.to_owned());
2088 }
2089 _ if arg.starts_with("-Zcycle-accurate-model=") => {
2090 let value = &arg["-Zcycle-accurate-model=".len()..];
2091 opts.cycle_accurate_model = match value {
2092 "yes" | "1" => Some(true),
2093 "no" | "0" => Some(false),
2094 _ => {
2095 return Err(err("-Zcycle-accurate-model= takes yes or no"));
2096 }
2097 };
2098 }
2099 _ if arg.starts_with("-Zregalloc=") => {
2100 opts.backtracking = match &arg["-Zregalloc=".len()..] {
2101 "backtracking" => Some(true),
2102 "single" => Some(false),
2103 _ => return Err(err("-Zregalloc= takes backtracking or single")),
2104 };
2105 }
2106 _ if arg.starts_with("-Zswitch=") => {
2107 let shape = &arg["-Zswitch=".len()..];
2108 if rucc_codegen::switch::Force::named(shape).is_none() {
2109 return Err(err("-Zswitch= takes table, tree or walk"));
2110 }
2111 opts.switch_shape = Some(shape.to_owned());
2112 }
2113 _ if arg.starts_with("-Zlowering=") => {
2114 let file = &arg["-Zlowering=".len()..];
2115 if file.is_empty() {
2116 return Err(err("-Zlowering= needs a file to write to"));
2117 }
2118 opts.lowering_dump = Some(file.to_owned());
2119 }
2120 _ if arg.starts_with("-Zregister-pressure=") => {
2121 let file = &arg["-Zregister-pressure=".len()..];
2122 if file.is_empty() {
2123 return Err(err("-Zregister-pressure= needs a file to write to"));
2124 }
2125 opts.register_pressure = Some(file.to_owned());
2126 }
2127 _ if arg.starts_with("-Z") => {
2128 return Err(err(format!(
2129 "`{arg}` is not an unstable option this compiler has, see \
2130 spec/04-driver-and-cli.md section 4.11 for the ones it does"
2131 )));
2132 }
2133 "-m64" | "-m32" | "-mx32" => {
2138 let want: u32 = match arg {
2139 "-m64" => 64,
2140 _ => 32,
2141 };
2142 let have = rucc_target::TargetInfo::new(opts.target).pointer_width;
2143 if have != want {
2144 return Err(err(format!(
2145 "{arg} asks for a {want} bit target and {} is {have} bit, use \
2146 --target= to name the one you mean",
2147 opts.target
2148 )));
2149 }
2150 }
2151 _ if isa_name(arg).is_some() => {
2159 let Some((_, feature, on)) = isa_name(arg) else { continue };
2160 if on && !feature.honoured() {
2161 return Err(err(format!(
2162 "{arg}: this compiler has no intrinsics for {} yet, so it cannot build a \
2163 whole unit for it",
2164 feature.name()
2165 )));
2166 }
2167 if !on && rucc_target::Isa::baseline().has(feature) {
2168 return Err(err(format!(
2169 "{arg}: {} is part of the x86-64 baseline and the psABI passes values in \
2170 it, so a unit built without it would call and be called differently",
2171 feature.name()
2172 )));
2173 }
2174 isa.read(&arg["-m".len()..]).map_err(|_| err(format!("unknown option `{arg}`")))?;
2175 isa_flag.get_or_insert(arg);
2176 }
2177 _ if arg.starts_with("-march=") => march = Some(&arg["-march=".len()..]),
2185 _ if arg.starts_with("-mtune=") || arg.starts_with("-mcpu=") => {}
2186 _ if arg.starts_with("-mabi=") => {
2189 let want = &arg["-mabi=".len()..];
2190 let have = match opts.target.arch {
2191 rucc_target::Arch::X86_64 => "sysv",
2192 rucc_target::Arch::Aarch64 => "lp64",
2193 rucc_target::Arch::Riscv64 => "lp64d",
2194 };
2195 if want != have {
2196 return Err(err(format!(
2197 "{arg}: {} uses the {have} convention and this compiler has no other",
2198 opts.target
2199 )));
2200 }
2201 }
2202 "-mcmodel=small" => {}
2206 _ if arg.starts_with("-mmacosx-version-min=")
2210 || arg.starts_with("-mmacos-version-min=") =>
2211 {
2212 let text = &arg[arg.find('=').map_or(arg.len(), |i| i + 1)..];
2213 let version = rucc_tuple::Version::parse(text)
2214 .ok_or_else(|| err(format!("`{text}` in `{arg}` is not a version")))?;
2215 min_version = Some(version);
2216 }
2217 _ if arg.starts_with("-mcmodel=") => {
2218 return Err(err(format!(
2219 "{arg}: this compiler emits the small code model and no other, see \
2220 spec/12-targets.md"
2221 )));
2222 }
2223 _ if arg.starts_with("-specs=") => {
2227 return Err(err(
2228 "-specs= is not supported: the parts of it builds rely on are -B, -L, \
2229 -nostdlib, -nostartfiles and -Wl,, see spec/04-driver-and-cli.md \
2230 section 4.4",
2231 ));
2232 }
2233 _ if arg.starts_with("-Wa,") || arg.starts_with("-Wp,") => {
2240 return Err(err(format!(
2241 "`{arg}` is an argument for a separate assembler or preprocessor, and both \
2242 are inside this compiler rather than programs it runs"
2243 )));
2244 }
2245 "-Xassembler" | "-Xpreprocessor" => {
2246 return Err(err(format!(
2247 "{arg} hands an argument to a separate assembler or preprocessor, and both \
2248 are inside this compiler rather than programs it runs"
2249 )));
2250 }
2251 _ if arg.starts_with("-W") => {
2259 let name = &arg["-W".len()..];
2260 let named = name.strip_prefix("error=").or_else(|| name.strip_prefix("no-error="));
2261 if let Some(named) = named {
2262 if !warnings::known(named) {
2263 return Err(err(format!("`{arg}`: no option `-W{named}`")));
2264 }
2265 } else if !name.is_empty() && !name.starts_with("no-") && !warnings::known(name) {
2266 return Err(err(format!("unknown option `{arg}`")));
2267 }
2268 }
2269 "-fno-ident"
2277 | "-fident"
2278 | "-funit-at-a-time"
2279 | "-fno-unit-at-a-time"
2280 | "-shared-libgcc"
2281 | "-static-libgcc"
2282 | "-mthreads"
2283 | "-fpch-deps"
2284 | "-fno-pch-deps" => {}
2285 _ if arg.starts_with('-') && arg.len() > 1 => {
2286 return Err(err(format!("unknown option `{arg}`")));
2291 }
2292 _ => inputs.push(Input { path: arg.to_owned(), forced, role: Role::File }),
2293 }
2294 }
2295
2296 if let Some(named) = fetch {
2301 if fetch_msvc.is_some() {
2302 return Err(err(
2303 "--fetch and --fetch-msvc-sdk are two different commands and this command line \
2304 asked for both. --fetch gets what this release pins by URL and by hash, which for \
2305 a *-windows-msvc target is one Visual Studio build, and --fetch-msvc-sdk gets the \
2306 build Microsoft's channel names today. Run whichever one you meant",
2307 ));
2308 }
2309 return fetch_action(&named, offline, accepted, &inputs);
2310 }
2311 if let Some(named) = fetch_msvc {
2312 return fetch_msvc_action(&named, offline, accepted, &inputs);
2313 }
2314 if accepted {
2315 return Err(err(
2316 "--accept-licence says that Microsoft's Visual Studio Build Tools licence is accepted, \
2317 and nothing on this command line asked for anything that licence covers. \
2318 --fetch <tuple> or --fetch-msvc-sdk <tuple> for a *-windows-msvc target is the \
2319 command it belongs to, and an ordinary compile downloads nothing with it or \
2320 without it",
2321 ));
2322 }
2323
2324 if let Some(first) = sanitizers.first() {
2340 return Err(err(format!(
2341 "-fsanitize={first}: this compiler has no sanitizer instrumentation, and a build that \
2342 asked for one and got none would run its tests unchecked, see \
2343 spec/04-driver-and-cli.md section 4.7. `-fsafety=detect` is the memory checking this \
2344 compiler does have"
2345 )));
2346 }
2347 let mut math = Math::default();
2352 let mut trapping = if ofast { math.set_fast(true) } else { true };
2353 for flag in &math_flags {
2354 match *flag {
2355 "-ftrapping-math" => trapping = true,
2356 "-fno-trapping-math" => trapping = false,
2357 "-ffast-math" => trapping = math.set_fast(true),
2358 "-fno-fast-math" => trapping = math.set_fast(false),
2359 "-funsafe-math-optimizations" => trapping = math.set_unsafe(true),
2360 "-fno-unsafe-math-optimizations" => trapping = math.set_unsafe(false),
2361 "-fmath-errno" => math.errno = true,
2362 "-fno-math-errno" => math.errno = false,
2363 "-ffinite-math-only" => math.finite_only = true,
2364 "-fno-finite-math-only" => math.finite_only = false,
2365 "-fsigned-zeros" => math.signed_zeros = true,
2366 "-fno-signed-zeros" => math.signed_zeros = false,
2367 "-freciprocal-math" => math.reciprocal = true,
2368 "-fno-reciprocal-math" => math.reciprocal = false,
2369 "-fassociative-math" => math.associative = true,
2370 "-fno-associative-math" => math.associative = false,
2371 _ => unreachable!("{flag} is not in the family"),
2372 }
2373 }
2374 opts.trapping_math = trapping;
2375 opts.math = math;
2376 let last = |on: &str, off: &str| {
2377 math_flags.iter().rev().find(|f| **f == on || **f == off).is_some_and(|f| *f == on)
2378 };
2379 link.fast_math = ofast
2380 || last("-ffast-math", "-fno-fast-math")
2381 || last("-funsafe-math-optimizations", "-fno-unsafe-math-optimizations");
2382 link.daz_ftz = daz_ftz;
2383 match opts.target.arch {
2389 rucc_target::Arch::X86_64 => {
2390 let base = match march {
2391 Some("native") => native_isa(),
2392 Some(name) => {
2393 rucc_target::Isa::level(name).unwrap_or_else(rucc_target::Isa::baseline)
2394 }
2395 None => rucc_target::Isa::baseline(),
2396 };
2397 opts.isa = isa.over(base);
2398 }
2399 rucc_target::Arch::Aarch64 | rucc_target::Arch::Riscv64 => {
2400 if let Some(flag) = isa_flag {
2401 return Err(err(format!("unknown option `{flag}`")));
2402 }
2403 opts.isa = match (opts.target.arch, march) {
2404 (rucc_target::Arch::Aarch64, Some(name)) => match name.strip_prefix("native") {
2405 Some(modifiers) => native_aarch64().aarch64_modifiers(modifiers),
2406 None => rucc_target::Isa::aarch64_march(name),
2407 },
2408 _ => rucc_target::Isa::NONE,
2409 };
2410 }
2411 }
2412 opts.exceptions = exceptions.unwrap_or(opts.non_call_exceptions);
2413 link.sysroot = sysroot.clone();
2414 link.cache = Some(cache::dir());
2419 link.usr = Some(PathBuf::from("/usr"));
2422 link.pinned = pinned;
2427 if opts.target.os == rucc_target::Os::Darwin {
2430 opts.os_version = min_version.or_else(|| pinned.and_then(TargetTuple::os_version));
2431 link.os_version = opts.os_version;
2432 }
2433 if threads {
2438 inputs.push(Input::library("pthread"));
2439 }
2440 if let Some(query) = query {
2441 return Ok(Action::Print(answer(&query, &opts, &link)?));
2442 }
2443 if opts.deps.instead_of_compiling {
2449 opts.emit = EmitKind::Preprocessed;
2450 }
2451 if !nostdinc {
2452 opts.search.push_system(runtime::DIR);
2453 let cross = link::cross_sysroot(opts.target, &link);
2462 let kernel = link::cross_kernel(opts.target, &link);
2463 let distro = link::distro_cross(opts.target, &link);
2464 if cross.is_some() {
2469 let target = pinned.unwrap_or_else(|| opts.target.tuple());
2470 opts.glibc_minor = rucc_sysroot::bundled_glibc_minor(target).map_err(|skew| {
2471 err(format!(
2472 "{skew}; pin a release the tree has, or name a tree that has that one \
2473 with --sysroot"
2474 ))
2475 })?;
2476 }
2477 let system = library::header_dirs(
2478 opts.target,
2479 sysroot.as_deref(),
2480 cross.as_ref(),
2481 kernel.as_ref(),
2482 distro.as_ref(),
2483 );
2484 if system.is_empty() && sysroot.is_none() {
2501 let tuple = pinned.unwrap_or_else(|| opts.target.tuple());
2502 if let Some(wall) = rucc_sysroot::Wall::of(tuple) {
2503 opts.search.explain_missing_system(wall.no_headers(&tuple.to_canonical_string()));
2504 }
2505 }
2506 if sysroot.is_some() {
2511 notes.extend(glibc::skew(opts.target, pinned, &system));
2512 }
2513 for dir in system {
2514 opts.search.push_system(dir);
2515 }
2516 }
2517 opts.search.remove_duplicates();
2521
2522 if print_config {
2525 return Ok(Action::PrintConfig(Box::new(opts)));
2526 }
2527 if print_pipeline {
2528 return Ok(Action::PrintPipeline(Box::new(opts)));
2529 }
2530 let plan = Plan::new(&opts, &inputs, output.as_deref()).map_err(|e| err(e.message))?;
2531 if print_plan {
2532 return Ok(Action::PrintPlan {
2533 opts: Box::new(opts),
2534 plan: Box::new(plan),
2535 link: Box::new(link),
2536 });
2537 }
2538 Ok(Action::Compile {
2539 opts: Box::new(opts),
2540 plan: Box::new(plan),
2541 link: Box::new(link),
2542 jobs,
2543 verbose,
2544 notes,
2545 })
2546}
2547
2548fn fetch_action(
2560 named: &str,
2561 offline: bool,
2562 accepted: bool,
2563 inputs: &[Input],
2564) -> Result<Action, CliError> {
2565 if offline {
2569 return Err(err(
2570 "--fetch asks for a download and --offline forbids every download, so this command \
2571 line asks for two opposite things. Drop one of them: --offline is how a build says it \
2572 will not reach the network, and --fetch is one of the two things in this compiler \
2573 that reaches it",
2574 ));
2575 }
2576 if let Some(first) = inputs.first() {
2577 return Err(err(format!(
2578 "--fetch gets a sysroot and compiles nothing, so `{}` on the same command line is an \
2579 input that nothing would read",
2580 first.path
2581 )));
2582 }
2583 let target: TargetTuple = named
2584 .parse()
2585 .map_err(|why| err(format!("--fetch {named}: {why}, so there is no sysroot to get")))?;
2586 let tuple = target.to_canonical_string();
2589 if rucc_sysroot::Wall::of(target) == Some(rucc_sysroot::Wall::Microsoft) {
2594 return Ok(Action::FetchMsvcSdk { target, accepted, cache: cache::dir(), pinned: true });
2595 }
2596 if let Some(wall) = rucc_sysroot::Wall::of(target) {
2600 return Err(err(format!("--fetch {tuple}: {}", wall.no_fetch(&tuple))));
2601 }
2602 let Some(what) = rucc_sysroot::pinned_for_target(target) else {
2603 return Err(err(unpinned(&tuple)));
2604 };
2605 Ok(Action::Fetch { what, target, cache: cache::dir() })
2606}
2607
2608fn fetch_msvc_action(
2621 named: &str,
2622 offline: bool,
2623 accepted: bool,
2624 inputs: &[Input],
2625) -> Result<Action, CliError> {
2626 if offline {
2627 return Err(err(
2628 "--fetch-msvc-sdk asks for a download and --offline forbids every download, so this \
2629 command line asks for two opposite things. Drop one of them: --offline is how a build \
2630 says it will not reach the network",
2631 ));
2632 }
2633 if let Some(first) = inputs.first() {
2634 return Err(err(format!(
2635 "--fetch-msvc-sdk gets an SDK and compiles nothing, so `{}` on the same command line \
2636 is an input that nothing would read",
2637 first.path
2638 )));
2639 }
2640 let target: TargetTuple = named.parse().map_err(|why| {
2641 err(format!("--fetch-msvc-sdk {named}: {why}, so there is no SDK to get"))
2642 })?;
2643 Ok(Action::FetchMsvcSdk { target, accepted, cache: cache::dir(), pinned: false })
2644}
2645
2646fn unpinned(tuple: &str) -> String {
2653 let pinned = rucc_sysroot::pinned_targets();
2654 if pinned.is_empty() {
2655 return format!(
2656 "this release pins no sysroot for {tuple}, and it pins none for any target yet. A \
2657 sysroot is built and published by the producer in tamnd/rucc-cross, per \
2658 spec/cross-compile/13-distribution.md section 13.8, and a release of this compiler \
2659 names one by URL and by hash afterwards. Until then, pass --sysroot=<dir> to compile \
2660 against a tree you have already"
2661 );
2662 }
2663 format!(
2664 "this release pins no sysroot for {tuple}. What it pins is {}. Pass --sysroot=<dir> to \
2665 compile against a tree you have already",
2666 pinned.join(", ")
2667 )
2668}
2669
2670fn fetch_sysroot(
2682 what: &rucc_sysroot::Pinned,
2683 kernel: Option<&rucc_sysroot::Pinned>,
2684 target: TargetTuple,
2685 cache: &std::path::Path,
2686) -> i32 {
2687 let tuple = target.to_canonical_string();
2688 let say = |line: &str| println!("rucc: {tuple}: {line}");
2689 if let Err(why) = bring(what, cache, &say) {
2690 return complain(why);
2691 }
2692 let archive = what.archive_in(cache);
2693 match install::install(&archive, what.sha256, target, cache) {
2694 Ok(done) => report(&done, "sysroot", &say),
2695 Err(why) => return complain(why),
2696 }
2697 let Some(kernel) = kernel else { return 0 };
2698 if let Err(why) = bring(kernel, cache, &say) {
2699 return complain(why);
2700 }
2701 match install::install_kernel(&kernel.archive_in(cache), kernel.sha256, cache) {
2702 Ok(done) => {
2703 report(&done, "kernel header tree", &say);
2704 0
2705 }
2706 Err(why) => complain(why),
2707 }
2708}
2709
2710fn bring(
2712 what: &rucc_sysroot::Pinned,
2713 cache: &std::path::Path,
2714 say: &impl Fn(&str),
2715) -> Result<(), CliError> {
2716 let archive = what.archive_in(cache);
2717 match fetch::fetch(what.url, what.sha256, &archive)? {
2718 fetch::Fetched::AlreadyThere => {
2719 say(&format!("{} is already here and matches the hash", archive.display()));
2720 }
2721 fetch::Fetched::Downloaded(by) => {
2722 say(&format!("downloaded {} with {}", what.url, by.program()));
2723 }
2724 }
2725 Ok(())
2726}
2727
2728fn report(done: &install::Installed, what: &str, say: &impl Fn(&str)) {
2730 match &done.before {
2731 install::Before::Nothing => {
2732 say(&format!("{} files installed at {}", done.files, done.root.display()));
2733 }
2734 install::Before::TheSame => {
2735 say(&format!(
2736 "the same {what} is already at {}, so nothing moved",
2737 done.root.display()
2738 ));
2739 }
2740 install::Before::Different(was) => {
2741 say(&format!(
2742 "{} files installed at {}, over a tree whose record digested to {was}",
2743 done.files,
2744 done.root.display()
2745 ));
2746 }
2747 }
2748 say(&format!("the {what}'s record digests to {}", done.digest));
2749}
2750
2751fn answer(query: &Query, opts: &Options, link: &LinkOptions) -> Result<String, CliError> {
2757 let found = |name: &str| {
2758 link::find_in_search(link, opts.target, name)
2759 .map_or_else(|| name.to_owned(), |path| path.display().to_string())
2760 };
2761 Ok(match query {
2762 Query::Machine => opts.target.to_string(),
2763 Query::Version => opts.gnuc.major.to_string(),
2764 Query::FullVersion => {
2765 format!("{}.{}.{}", opts.gnuc.major, opts.gnuc.minor, opts.gnuc.patch)
2766 }
2767 Query::Multiarch => link::multiarch(opts.target),
2768 Query::SearchDirs => {
2773 let here = std::env::current_exe()
2774 .ok()
2775 .and_then(|p| p.parent().map(std::path::Path::to_path_buf))
2776 .unwrap_or_default();
2777 let list = |dirs: &[PathBuf]| {
2778 dirs.iter().map(|d| d.display().to_string()).collect::<Vec<_>>().join(":")
2779 };
2780 let libraries = link::search_dirs(link, opts.target);
2781 format!(
2782 "install: {}\nprograms: ={}\nlibraries: ={}",
2783 here.display(),
2784 list(&link.prefixes),
2785 list(&libraries)
2786 )
2787 }
2788 Query::Sysroot => {
2794 sysroot_root(opts, link).map(|root| root.display().to_string()).unwrap_or_default()
2795 }
2796 Query::SysrootProvenance => match sysroot_manifest(opts, link)? {
2807 Some(manifest) => manifest.render().trim_end_matches('\n').to_string(),
2808 None => String::new(),
2809 },
2810 Query::SysrootDigest => match sysroot_manifest(opts, link)? {
2817 Some(manifest) => manifest.digest(),
2818 None => String::new(),
2819 },
2820 Query::FileName(name) => found(name),
2821 Query::Libgcc => found("libgcc.a"),
2825 Query::ProgName(name) => link
2829 .prefixes
2830 .iter()
2831 .map(|dir| dir.join(name))
2832 .find(|path| path.is_file())
2833 .map_or_else(|| name.clone(), |path| path.display().to_string()),
2834 })
2835}
2836
2837fn sysroot_root(opts: &Options, link: &LinkOptions) -> Option<PathBuf> {
2844 link.sysroot
2845 .clone()
2846 .or_else(|| link::cross_sysroot(opts.target, link).map(|at| at.root().to_path_buf()))
2847}
2848
2849fn sysroot_manifest(opts: &Options, link: &LinkOptions) -> Result<Option<Manifest>, CliError> {
2864 let Some(root) = sysroot_root(opts, link) else {
2865 return Ok(None);
2866 };
2867 let path = Sysroot::at(root, opts.target.tuple()).manifest_path();
2868 match std::fs::read_to_string(&path) {
2869 Ok(text) => Manifest::parse(&text)
2870 .map(Some)
2871 .map_err(|why| err(format!("{}: {why}", path.display()))),
2872 Err(why) if why.kind() == std::io::ErrorKind::NotFound => Ok(None),
2873 Err(why) => Err(err(format!("{}: {why}", path.display()))),
2874 }
2875}
2876
2877#[must_use]
2883pub fn print_pipeline(opts: &Options) -> String {
2884 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
2885 settings.toggles.clone_from(&opts.passes);
2886 settings.global_fuel = opts.pass_fuel_global;
2887 for (on, spec) in &opts.pass_gates {
2888 let _ = settings.gates.add(*on, spec);
2891 }
2892 rucc_opt::pipeline::print(&settings)
2893}
2894
2895#[must_use]
2900pub fn print_config(opts: &Options) -> String {
2901 let sess = Session::new(opts.clone());
2902 let t = &sess.target;
2903 let mut out = String::new();
2904 let _ = writeln!(out, "version: {VERSION}");
2905 let _ = writeln!(out, "target: {}", opts.target);
2909 let _ = writeln!(out, "arch: {}", opts.target.arch.as_str());
2910 let _ = writeln!(out, "os: {}", opts.target.os.as_str());
2911 let _ = writeln!(out, "env: {}", opts.target.env.as_str());
2912 let _ = writeln!(out, "object-format: {}", t.object_format.as_str());
2913 let _ = writeln!(out, "pointer-width: {}", t.pointer_width);
2914 let _ = writeln!(out, "long-width: {}", t.long_width);
2915 let _ = writeln!(out, "long-double-width: {}", t.long_double_width);
2916 let _ = writeln!(out, "endian: {}", if t.little_endian { "little" } else { "big" });
2917 let _ = writeln!(out, "char-signed: {}", t.char_is_signed);
2918 let _ = writeln!(out, "va-list: {}", t.va_list.map_or("none", |list| list.as_str()));
2919 let regs: Vec<String> = t
2922 .regs
2923 .classes()
2924 .map(|(class, info)| format!("{} {}", info.name, t.regs.len(class)))
2925 .collect();
2926 let _ = writeln!(
2927 out,
2928 "registers: {}",
2929 if regs.is_empty() { "none".to_string() } else { regs.join(", ") }
2930 );
2931 let _ = writeln!(out, "timing-model: {}", t.timing.map_or("none", |timing| timing.model));
2934 let _ = writeln!(out, "opt-level: {}", sess.opts.opt_level);
2935 let _ = writeln!(out, "safety: {}", sess.opts.safety);
2936 let _ = writeln!(out, "emit: {}", sess.opts.emit.as_str());
2937 let _ = writeln!(out, "debug-info: {}", sess.opts.debug_info);
2938 let _ = writeln!(out, "frame-pointer: {}", sess.opts.keeps_frame_pointer());
2939 let _ = writeln!(out, "red-zone: {}", sess.opts.red_zone);
2940 let _ = writeln!(out, "stack-protector: {}", sess.opts.protector);
2941 let _ = writeln!(out, "stack-clash-protection: {}", sess.opts.stack_clash);
2942 let _ = writeln!(out, "cf-protection: {}", sess.opts.control);
2943 let _ = writeln!(out, "patchable-function-entry: {}", sess.opts.patchable);
2944 let _ = writeln!(out, "profile: {}", sess.opts.profile);
2945 let _ = writeln!(out, "profile-hook: {}", sess.opts.hook);
2946 for dir in sess.opts.search.dirs() {
2949 let system = if dir.is_system { " (system)" } else { "" };
2950 let _ = writeln!(out, "include: {}{system}", dir.path.display());
2951 }
2952 out
2953}
2954
2955fn deps_target_output<'a>(opts: &Options, plan: &'a Plan) -> Option<&'a str> {
2963 if opts.emit == EmitKind::Preprocessed { None } else { plan.output.as_deref() }
2964}
2965
2966fn write_named(path: &str, bytes: &[u8]) -> Result<(), String> {
2969 if path == "-" {
2970 return write_out(&Output::Stdout, bytes);
2971 }
2972 write_out(&Output::File(path.to_owned()), bytes)
2973}
2974
2975fn write_deps(
2981 opts: &Options,
2982 plan: &Plan,
2983 job: &Job,
2984 found: &[Dependency],
2985 stderr: &mut impl std::io::Write,
2986) -> bool {
2987 let targets = if opts.deps.targets.is_empty() {
2988 vec![deps::default_target(&job.input, deps_target_output(opts, plan))]
2989 } else {
2990 opts.deps.targets.clone()
2991 };
2992 let rule = deps::rule(&opts.deps, &targets, &job.input, found);
2993 let wrote = match deps::default_file(&opts.deps, &job.input, plan.output.as_deref()) {
2996 Some(path) => write_named(&path, rule.as_bytes()).and_then(|()| {
3000 if opts.deps.instead_of_compiling { write_out(&job.output, b"") } else { Ok(()) }
3001 }),
3002 None => write_out(&job.output, rule.as_bytes()),
3003 };
3004 if let Err(e) = wrote {
3005 let _ = writeln!(stderr, "rucc: error: {e}");
3006 return false;
3007 }
3008 true
3009}
3010
3011fn preprocess_all(opts: &Options, plan: &Plan) -> i32 {
3017 let fs = OsFileSystem::new();
3018 let mut stderr = std::io::stderr().lock();
3019 let mut failed = false;
3020 for job in &plan.jobs {
3021 if !job.phases.first().is_some_and(|p| *p == Phase::Preprocess) {
3022 continue;
3025 }
3026 let started = std::time::Instant::now();
3027 let result = preprocess(opts, &job.input, &fs);
3028 if opts.time {
3029 say_time(&job.input, started.elapsed(), &mut stderr);
3030 }
3031 for message in &result.messages {
3032 let _ = writeln!(stderr, "{message}");
3033 }
3034 if result.failed() {
3035 failed = true;
3036 continue;
3037 }
3038 if opts.deps.emit {
3039 failed |= !write_deps(opts, plan, job, &result.deps, &mut stderr);
3040 if opts.deps.instead_of_compiling {
3043 continue;
3044 }
3045 }
3046 if let Err(e) = write_out(&job.output, result.text.as_bytes()) {
3047 let _ = writeln!(stderr, "rucc: error: {e}");
3048 failed = true;
3049 }
3050 }
3051 i32::from(failed)
3052}
3053
3054fn needs_an_assembler(job: &Job) -> bool {
3062 job.phases.contains(&Phase::Assemble) && !job.phases.contains(&Phase::Compile)
3063}
3064
3065fn assembly_wants_cpp(job: &Job) -> bool {
3068 job.phases.contains(&Phase::Preprocess)
3069}
3070
3071fn compile_all(opts: &Options, plan: &Plan) -> i32 {
3077 let fs = OsFileSystem::new();
3078 let mut stderr = std::io::stderr().lock();
3079 let mut failed = false;
3080 let (mut remarks, ok) = Remarks::new(opts.opt_info_file.as_ref(), &mut stderr);
3081 failed |= !ok;
3082 let mut fired = Fired::new();
3083 let mut pressure = Pressure::new();
3084 let mut lowerings = Lowerings::new();
3085 for job in &plan.jobs {
3086 if !job.phases.contains(&Phase::Compile) && !needs_an_assembler(job) {
3087 continue;
3088 }
3089 let started = std::time::Instant::now();
3094 let result = if needs_an_assembler(job) {
3095 assemble(opts, &job.input, assembly_wants_cpp(job), &fs)
3096 } else if job.kind == InputKind::Ir {
3097 compile_ir(opts, &job.input, &fs)
3098 } else {
3099 compile(opts, &job.input, &fs)
3100 };
3101 if opts.time {
3102 say_time(&job.input, started.elapsed(), &mut stderr);
3103 }
3104 failed |= !write_trace(opts, job, started, &result, &mut stderr);
3105 fired.merge(&result.fired);
3106 pressure.merge(&result.pressure);
3107 lowerings.merge(&result.lowerings);
3108 failed |= !write_dumps(&job.input, &result.dumps, &mut stderr);
3109 failed |= !remarks.write(&result.remarks, &mut stderr);
3110 for message in &result.messages {
3111 let _ = writeln!(stderr, "{message}");
3112 }
3113 failed |= !write_temps(job, &result.temps, &mut stderr);
3116 failed |= !write_stack_usage(job, &result.stack_usage, &mut stderr);
3119 if result.failed() {
3120 failed = true;
3121 continue;
3122 }
3123 if opts.deps.emit {
3128 failed |= !write_deps(opts, plan, job, &result.deps, &mut stderr);
3129 }
3130 if let Err(e) = write_out(&job.output, result.artifact.bytes()) {
3131 let _ = writeln!(stderr, "rucc: error: {e}");
3132 failed = true;
3133 }
3134 }
3135 failed |= !write_coverage(opts, &fired, &mut stderr);
3136 failed |= !write_pressure(opts, &pressure, &mut stderr);
3137 failed |= !write_lowering(opts, &lowerings, &mut stderr);
3138 i32::from(failed)
3139}
3140
3141struct Scratch {
3148 dir: PathBuf,
3150}
3151
3152impl Scratch {
3153 fn new() -> Result<Scratch, String> {
3159 let dir = std::env::temp_dir().join(format!("rucc-{}", std::process::id()));
3160 std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
3161 Ok(Scratch { dir })
3162 }
3163}
3164
3165impl Drop for Scratch {
3166 fn drop(&mut self) {
3167 let _ = std::fs::remove_dir_all(&self.dir);
3168 }
3169}
3170
3171fn link_line(opts: &Options, link: &LinkOptions, job: &LinkJob) -> Result<String, link::Error> {
3178 let linker = link::find(opts.target, link)?;
3179 let args = link::line(opts.target, link, &job.inputs, &job.output)?;
3180 Ok(link::render(&linker, &args))
3181}
3182
3183fn link_all(opts: &Options, plan: &Plan, link: &LinkOptions, verbose: bool) -> i32 {
3190 let Some(job) = &plan.link else {
3191 let mut stderr = std::io::stderr().lock();
3194 let _ = writeln!(stderr, "rucc: error: there is nothing to link");
3195 return 1;
3196 };
3197 if let Err(why) = link::preflight(opts.target, link) {
3203 return complain(why);
3204 }
3205 let linker = match link::find(opts.target, link) {
3206 Ok(linker) => linker,
3207 Err(why) => return complain(why),
3208 };
3209 if let Err(why) = link::write_stubs(opts.target, link) {
3214 return complain(why);
3215 }
3216
3217 let scratch = match Scratch::new() {
3218 Ok(scratch) => scratch,
3219 Err(why) => return complain(format!("could not make a place for the object files: {why}")),
3220 };
3221
3222 let fs = OsFileSystem::new();
3223 let mut failed = false;
3224 let mut produced: Vec<String> = Vec::with_capacity(plan.jobs.len());
3227 let mut fired = Fired::new();
3228 let mut pressure = Pressure::new();
3229 let mut lowerings = Lowerings::new();
3230 {
3231 let mut stderr = std::io::stderr().lock();
3232 let (mut remarks, ok) = Remarks::new(opts.opt_info_file.as_ref(), &mut stderr);
3233 failed |= !ok;
3234 for (at, job) in plan.jobs.iter().enumerate() {
3235 let out = match &job.output {
3236 Output::Temporary(hint) => {
3237 scratch.dir.join(format!("{at}-{hint}")).display().to_string()
3240 }
3241 Output::File(path) => path.clone(),
3242 Output::Stdout => continue,
3245 };
3246 produced.push(out.clone());
3247 if !job.phases.contains(&Phase::Compile) && !needs_an_assembler(job) {
3248 continue;
3249 }
3250 let started = std::time::Instant::now();
3251 let result = if needs_an_assembler(job) {
3252 assemble(opts, &job.input, assembly_wants_cpp(job), &fs)
3253 } else if job.kind == InputKind::Ir {
3254 compile_ir(opts, &job.input, &fs)
3255 } else {
3256 compile(opts, &job.input, &fs)
3257 };
3258 if opts.time {
3259 say_time(&job.input, started.elapsed(), &mut stderr);
3260 }
3261 failed |= !write_trace(opts, job, started, &result, &mut stderr);
3262 fired.merge(&result.fired);
3263 pressure.merge(&result.pressure);
3264 lowerings.merge(&result.lowerings);
3265 failed |= !write_dumps(&job.input, &result.dumps, &mut stderr);
3266 failed |= !remarks.write(&result.remarks, &mut stderr);
3267 for message in &result.messages {
3268 let _ = writeln!(stderr, "{message}");
3269 }
3270 failed |= !write_temps(job, &result.temps, &mut stderr);
3271 failed |= !write_stack_usage(job, &result.stack_usage, &mut stderr);
3272 if result.failed() {
3273 failed = true;
3274 continue;
3275 }
3276 if opts.deps.emit {
3281 failed |= !write_deps(opts, plan, job, &result.deps, &mut stderr);
3282 }
3283 if !matches!(result.artifact, Artifact::Object { .. }) {
3284 let _ = writeln!(
3289 stderr,
3290 "rucc: internal error: {}: no object file was produced for the link",
3291 job.input
3292 );
3293 failed = true;
3294 continue;
3295 }
3296 if let Err(e) = std::fs::write(&out, result.artifact.bytes()) {
3297 let _ = writeln!(stderr, "rucc: error: {out}: {e}");
3298 failed = true;
3299 }
3300 }
3301 failed |= !write_coverage(opts, &fired, &mut stderr);
3302 failed |= !write_pressure(opts, &pressure, &mut stderr);
3303 failed |= !write_lowering(opts, &lowerings, &mut stderr);
3304 failed |= !write_lowering(opts, &lowerings, &mut stderr);
3305 }
3306 if failed {
3307 return 1;
3311 }
3312
3313 let mut outputs = produced.into_iter();
3317 let mut items = Vec::with_capacity(job.inputs.len());
3318 for item in &job.inputs {
3319 match item {
3320 link::Item::Library(name) => items.push(link::Item::Library(name.clone())),
3321 link::Item::Linker(arg) => items.push(link::Item::Linker(arg.clone())),
3322 link::Item::File(_) => match outputs.next() {
3323 Some(path) => items.push(link::Item::File(path)),
3324 None => return complain("the plan asks the linker for a file nothing produced"),
3325 },
3326 }
3327 }
3328
3329 let args = match link::line(opts.target, link, &items, &job.output) {
3330 Ok(args) => args,
3331 Err(why) => return complain(why),
3332 };
3333 if verbose {
3334 let mut stderr = std::io::stderr().lock();
3335 let _ = writeln!(stderr, "{}", link::render(&linker, &args));
3336 }
3337 let started = std::time::Instant::now();
3338 let ran = link::run(&linker, &args);
3339 if opts.time {
3340 let mut stderr = std::io::stderr().lock();
3343 say_time(&linker.name, started.elapsed(), &mut stderr);
3344 }
3345 match ran {
3346 Ok(()) => 0,
3347 Err(link::Error::Refused { .. }) => 1,
3350 Err(why) => complain(why),
3351 }
3352}
3353
3354fn archive_all(opts: &Options, plan: &Plan) -> i32 {
3365 let Some(job) = &plan.archive else {
3366 return complain("there is nothing to put in an archive");
3369 };
3370 let flavour = match opts.target.os.object_format() {
3373 ObjectFormat::Elf => rucc_archive::Flavour::Gnu,
3374 ObjectFormat::Coff => rucc_archive::Flavour::Coff,
3375 ObjectFormat::MachO => rucc_archive::Flavour::Bsd,
3376 format @ ObjectFormat::Wasm => {
3378 return complain(format!(
3379 "there is no archive format for {} objects in this compiler yet",
3380 format.as_str()
3381 ));
3382 }
3383 };
3384
3385 let fs = OsFileSystem::new();
3386 let mut failed = false;
3387 let mut members: Vec<rucc_archive::Member> = Vec::with_capacity(plan.jobs.len());
3388 let mut names = job.members.iter();
3389 let mut fired = Fired::new();
3390 let mut pressure = Pressure::new();
3391 let mut lowerings = Lowerings::new();
3392 {
3393 let mut stderr = std::io::stderr().lock();
3394 let (mut remarks, ok) = Remarks::new(opts.opt_info_file.as_ref(), &mut stderr);
3395 failed |= !ok;
3396 for plan_job in &plan.jobs {
3397 let Some(member) = names.next() else {
3401 return complain("the plan asks the archive for a member nothing produced");
3402 };
3403 if !plan_job.phases.contains(&Phase::Compile) && !needs_an_assembler(plan_job) {
3404 let _ = writeln!(
3407 &mut stderr,
3408 "rucc: error: {}: this compiler makes an archive out of what it compiles, and \
3409 there is nothing here for it to do",
3410 plan_job.input
3411 );
3412 failed = true;
3413 continue;
3414 }
3415 let started = std::time::Instant::now();
3416 let result = if needs_an_assembler(plan_job) {
3417 assemble(opts, &plan_job.input, assembly_wants_cpp(plan_job), &fs)
3418 } else if plan_job.kind == InputKind::Ir {
3419 compile_ir(opts, &plan_job.input, &fs)
3420 } else {
3421 compile(opts, &plan_job.input, &fs)
3422 };
3423 if opts.time {
3424 say_time(&plan_job.input, started.elapsed(), &mut stderr);
3425 }
3426 failed |= !write_trace(opts, plan_job, started, &result, &mut stderr);
3427 fired.merge(&result.fired);
3428 pressure.merge(&result.pressure);
3429 lowerings.merge(&result.lowerings);
3430 failed |= !write_dumps(&plan_job.input, &result.dumps, &mut stderr);
3431 failed |= !remarks.write(&result.remarks, &mut stderr);
3432 for message in &result.messages {
3433 let _ = writeln!(stderr, "{message}");
3434 }
3435 failed |= !write_temps(plan_job, &result.temps, &mut stderr);
3436 failed |= !write_stack_usage(plan_job, &result.stack_usage, &mut stderr);
3437 if result.failed() {
3438 failed = true;
3439 continue;
3440 }
3441 if opts.deps.emit {
3442 failed |= !write_deps(opts, plan, plan_job, &result.deps, &mut stderr);
3443 }
3444 let Artifact::Object { bytes, defines } = result.artifact else {
3445 let _ = writeln!(
3446 stderr,
3447 "rucc: internal error: {}: no object file was produced for the archive",
3448 plan_job.input
3449 );
3450 failed = true;
3451 continue;
3452 };
3453 if let Output::File(path) = &plan_job.output {
3456 if let Err(e) = std::fs::write(path, &bytes) {
3457 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3458 failed = true;
3459 }
3460 }
3461 members.push(rucc_archive::Member { name: member.clone(), body: bytes, defines });
3462 }
3463 failed |= !write_coverage(opts, &fired, &mut stderr);
3464 failed |= !write_pressure(opts, &pressure, &mut stderr);
3465 failed |= !write_lowering(opts, &lowerings, &mut stderr);
3466 failed |= !write_lowering(opts, &lowerings, &mut stderr);
3467 }
3468 if failed {
3469 return 1;
3473 }
3474
3475 let bytes = match rucc_archive::write(flavour, &members) {
3476 Ok(bytes) => bytes,
3477 Err(why) => return complain(format!("the archive could not be written: {why}")),
3480 };
3481 match std::fs::write(&job.output, &bytes) {
3482 Ok(()) => 0,
3483 Err(e) => complain(format!("{}: {e}", job.output)),
3484 }
3485}
3486
3487fn complain(why: impl std::fmt::Display) -> i32 {
3489 let mut stderr = std::io::stderr().lock();
3490 let _ = writeln!(stderr, "rucc: error: {why}");
3491 1
3492}
3493
3494fn write_coverage(opts: &Options, fired: &Fired, stderr: &mut impl std::io::Write) -> bool {
3503 let Some(path) = &opts.rule_coverage else { return true };
3504 let Some(table) = coverage::table(opts.target.arch) else {
3505 let _ = writeln!(
3506 stderr,
3507 "rucc: error: there are no lowering rules for {} yet, so there is no coverage of them \
3508 to report",
3509 opts.target
3510 );
3511 return false;
3512 };
3513 match std::fs::write(path, fired.listing(table)) {
3514 Ok(()) => true,
3515 Err(e) => {
3516 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3517 false
3518 }
3519 }
3520}
3521
3522fn write_pressure(opts: &Options, pressure: &Pressure, stderr: &mut impl std::io::Write) -> bool {
3530 let Some(path) = &opts.register_pressure else { return true };
3531 match std::fs::write(path, pressure.listing()) {
3532 Ok(()) => true,
3533 Err(e) => {
3534 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3535 false
3536 }
3537 }
3538}
3539
3540fn write_lowering(opts: &Options, lowerings: &Lowerings, stderr: &mut impl std::io::Write) -> bool {
3546 let Some(path) = &opts.lowering_dump else { return true };
3547 match std::fs::write(path, lowerings.listing()) {
3548 Ok(()) => true,
3549 Err(e) => {
3550 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3551 false
3552 }
3553 }
3554}
3555
3556struct Remarks {
3563 file: Option<String>,
3565 started: bool,
3568}
3569
3570impl Remarks {
3571 fn new(file: Option<&String>, stderr: &mut impl std::io::Write) -> (Self, bool) {
3577 let mut ok = true;
3578 if let Some(path) = file {
3579 if let Err(e) = std::fs::write(path, "") {
3580 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3581 ok = false;
3582 }
3583 }
3584 (Self { file: file.cloned(), started: false }, ok)
3585 }
3586
3587 fn write(&mut self, text: &str, stderr: &mut impl std::io::Write) -> bool {
3593 if text.is_empty() {
3594 return true;
3595 }
3596 let Some(path) = &self.file else {
3597 let _ = write!(stderr, "{text}");
3598 return true;
3599 };
3600 let opened = std::fs::OpenOptions::new()
3601 .write(true)
3602 .append(self.started)
3603 .truncate(!self.started)
3604 .create(true)
3605 .open(path);
3606 self.started = true;
3607 let result =
3608 opened.and_then(|mut file| std::io::Write::write_all(&mut file, text.as_bytes()));
3609 if let Err(e) = result {
3610 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3611 return false;
3612 }
3613 true
3614 }
3615}
3616
3617fn write_dumps(input: &str, dumps: &[rucc_opt::Dump], stderr: &mut impl std::io::Write) -> bool {
3628 let stem = std::path::Path::new(input)
3629 .file_name()
3630 .map_or_else(|| input.to_owned(), |name| name.to_string_lossy().into_owned());
3631 let mut ok = true;
3632 for dump in dumps {
3633 let path = format!("{stem}.{}.ir", dump.name);
3634 if let Err(e) = std::fs::write(&path, &dump.text) {
3635 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3636 ok = false;
3637 }
3638 }
3639 ok
3640}
3641
3642fn write_temps(job: &Job, temps: &Temps, stderr: &mut impl std::io::Write) -> bool {
3648 let mut ok = true;
3649 let kept = [(job.saved_text(), &temps.preprocessed), (job.saved_asm(), &temps.assembly)];
3650 for (path, text) in kept {
3651 let (Some(path), Some(text)) = (path, text) else { continue };
3654 if let Err(e) = std::fs::write(&path, text) {
3655 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3656 ok = false;
3657 }
3658 }
3659 ok
3660}
3661
3662fn write_stack_usage(job: &Job, text: &str, stderr: &mut impl std::io::Write) -> bool {
3668 let Some(path) = &job.stack_usage else { return true };
3669 if let Err(e) = std::fs::write(path, text) {
3670 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
3671 return false;
3672 }
3673 true
3674}
3675
3676fn write_trace(
3680 opts: &Options,
3681 job: &Job,
3682 started: std::time::Instant,
3683 result: &Compiled,
3684 stderr: &mut impl std::io::Write,
3685) -> bool {
3686 let Some(path) = &opts.trace else {
3687 return true;
3688 };
3689 let output = match &job.output {
3690 Output::Stdout => "-",
3691 Output::File(path) | Output::Temporary(path) => path,
3692 };
3693 let record = trace::Record {
3694 input: &job.input,
3695 output,
3696 ok: !result.failed(),
3697 total: started.elapsed(),
3698 timing: &result.timing,
3699 };
3700 match trace::append(path, &record) {
3701 Ok(()) => true,
3702 Err(e) => {
3703 let _ = writeln!(stderr, "rucc: error: {e}");
3704 false
3705 }
3706 }
3707}
3708
3709fn say_time(name: &str, took: std::time::Duration, stderr: &mut impl std::io::Write) {
3716 let _ = writeln!(stderr, "# {name} {:.2} {:.2}", took.as_secs_f64(), 0.0);
3717}
3718
3719fn write_out(output: &Output, bytes: &[u8]) -> Result<(), String> {
3726 match output {
3727 Output::Stdout => {
3728 let mut stdout = std::io::stdout().lock();
3729 stdout.write_all(bytes).map_err(|e| format!("writing to standard output: {e}"))
3730 }
3731 Output::File(path) | Output::Temporary(path) => {
3732 std::fs::write(path, bytes).map_err(|e| format!("{path}: {e}"))
3733 }
3734 }
3735}
3736
3737pub fn target_from_program(program: &str) -> Option<String> {
3744 let name = program.rsplit(['/', '\\']).next()?;
3745 let name = name.strip_suffix(".exe").or_else(|| name.strip_suffix(".EXE")).unwrap_or(name);
3746 let triple = name.strip_suffix("-rucc")?;
3747 triple.parse::<Triple>().ok()?;
3748 Some(triple.to_owned())
3749}
3750
3751pub fn run_as(program: &str, args: &[String]) -> i32 {
3759 if dlltool::is_dlltool(program) {
3760 return dlltool::run(program, args);
3761 }
3762 if args.first().is_some_and(|first| first == "--dlltool") {
3763 return dlltool::run(program, &args[1..]);
3764 }
3765 match target_from_program(program) {
3766 Some(triple) => {
3767 let mut all = Vec::with_capacity(args.len() + 1);
3768 all.push(format!("--target={triple}"));
3769 all.extend_from_slice(args);
3770 run(&all)
3771 }
3772 None => run(args),
3773 }
3774}
3775
3776fn banner() -> String {
3786 format!(
3787 "rucc {VERSION}\nA C compiler for the GNU C dialect of GCC 16 from the Free Software Foundation.\nThis is free software under the Apache License 2.0. There is NO warranty.\n"
3788 )
3789}
3790
3791pub fn run(args: &[String]) -> i32 {
3796 match parse_args(args) {
3797 Ok(Action::Help) => {
3798 print!("{USAGE}");
3799 0
3800 }
3801 Ok(Action::Version) => {
3802 print!("{}", banner());
3803 0
3804 }
3805 Ok(Action::Print(line)) => {
3806 println!("{line}");
3807 0
3808 }
3809 Ok(Action::PrintConfig(opts)) => {
3810 print!("{}", print_config(&opts));
3811 0
3812 }
3813 Ok(Action::PrintPipeline(opts)) => {
3814 print!("{}", print_pipeline(&opts));
3815 0
3816 }
3817 Ok(Action::PrintPlan { opts, plan, link }) => {
3818 print!("{}", plan.render());
3819 if let Some(job) = &plan.link {
3823 match link_line(&opts, &link, job) {
3824 Ok(line) => println!("{line}"),
3825 Err(why) => {
3826 let mut stderr = std::io::stderr().lock();
3827 let _ = writeln!(stderr, "rucc: error: {why}");
3828 return 1;
3829 }
3830 }
3831 }
3832 0
3833 }
3834 Ok(Action::Fetch { what, target, cache }) => {
3835 let kernel = rucc_sysroot::Kernel::for_target(&cache, target)
3836 .map(|_| &rucc_sysroot::KERNEL_HEADERS);
3837 fetch_sysroot(what, kernel, target, &cache)
3838 }
3839 Ok(Action::FetchMsvcSdk { target, accepted, cache, pinned }) => msvc::fetch_msvc_sdk(
3840 target,
3841 accepted,
3842 &cache,
3843 pinned.then_some(&rucc_sysroot::PINNED_BUILD),
3844 ),
3845 Ok(Action::Compile { opts, plan, link, jobs, verbose, notes }) => {
3846 {
3847 let mut stderr = std::io::stderr().lock();
3848 for note in ¬es {
3852 let _ = writeln!(stderr, "rucc: warning: {note}");
3853 }
3854 if verbose {
3855 let _ = write!(stderr, "{}", plan.render());
3856 let _ = writeln!(stderr, "workers: {}", jobs.count());
3857 let _ = write!(stderr, "{}", opts.search.render_gcc());
3860 }
3861 }
3862 if opts.emit == EmitKind::Preprocessed {
3863 return preprocess_all(&opts, &plan);
3864 }
3865 if opts.emit == EmitKind::Archive {
3866 return archive_all(&opts, &plan);
3867 }
3868 if opts.emit != EmitKind::Executable {
3869 return compile_all(&opts, &plan);
3870 }
3871 if let Some(why) = unlinkable(&opts) {
3872 let _ = writeln!(std::io::stderr().lock(), "rucc: error: {why}");
3873 return 1;
3874 }
3875 link_all(&opts, &plan, &link, verbose)
3876 }
3877 Err(e) => {
3878 let mut stderr = std::io::stderr().lock();
3879 let _ = writeln!(stderr, "rucc: error: {e}");
3880 let _ = writeln!(stderr, "rucc: note: run `rucc --help` for usage");
3881 1
3882 }
3883 }
3884}
3885
3886fn unlinkable(opts: &Options) -> Option<String> {
3895 (opts.safety.instruments() && opts.target.os == rucc_target::Os::Windows).then(|| {
3896 format!(
3897 "-fsafety={}: the checked modes are not available on a Windows target yet, because \
3898 the runtime they need has not been ported to Windows. -c still builds the object",
3899 opts.safety
3900 )
3901 })
3902}
3903
3904#[cfg(test)]
3905mod tests {
3906 use rucc_session::{
3907 Contract, GnucVersion, IncludeForm, LtoJobs, OptLevel, Partition, Patchable, Visibility,
3908 };
3909
3910 use super::*;
3911
3912 fn args(s: &[&str]) -> Vec<String> {
3913 s.iter().map(|x| (*x).to_owned()).collect()
3914 }
3915
3916 const LINUX: &str = "--target=x86_64-unknown-linux-gnu";
3927
3928 #[test]
3929 fn a_response_file_is_split_the_way_libiberty_splits_one() {
3930 let words = response_words("-Wl,--as-needed 'a b' \"c d\"\ne\\ f '' \"it's\" g\\\\h\n");
3931 assert_eq!(words, ["-Wl,--as-needed", "a b", "c d", "e f", "", "it's", "g\\h"]);
3932 assert!(response_words(" \n\t").is_empty());
3933 }
3934
3935 #[test]
3936 fn a_response_file_on_the_command_line_is_read_in_its_place() {
3937 let dir = std::env::temp_dir().join(format!("rucc-rsp-{}", std::process::id()));
3938 std::fs::create_dir_all(&dir).unwrap();
3939 let inner = dir.join("inner.rsp");
3940 std::fs::write(&inner, "-lm\n").unwrap();
3941 let outer = dir.join("outer.rsp");
3942 let named = inner.display().to_string().replace('\\', "\\\\");
3945 std::fs::write(&outer, format!("-o 'my prog' -Wl,--as-needed @{named}\n")).unwrap();
3946 let line = args(&["x.o", &format!("@{}", outer.display()), "@no-such-file"]);
3947 assert_eq!(
3948 response_files(&line).unwrap(),
3949 args(&["x.o", "-o", "my prog", "-Wl,--as-needed", "-lm", "@no-such-file"])
3950 );
3951 let itself = dir.join("itself.rsp");
3952 let named = itself.display().to_string().replace('\\', "\\\\");
3953 std::fs::write(&itself, format!("@{named}")).unwrap();
3954 let looped = response_files(&args(&[&format!("@{}", itself.display())]));
3955 assert!(looped.is_err(), "a file that names itself should be refused");
3956 std::fs::remove_dir_all(&dir).unwrap();
3957 }
3958
3959 #[test]
3960 fn dlltool_mode_is_asked_for_first_or_by_the_program_name() {
3961 let dir = std::env::temp_dir().join(format!("rucc-dlltool-mode-{}", std::process::id()));
3962 std::fs::create_dir_all(&dir).unwrap();
3963 let def = dir.join("w.def");
3964 std::fs::write(&def, "LIBRARY w.dll\nEXPORTS\nw\n").unwrap();
3965 let def = def.display().to_string();
3966 let first = dir.join("first.a").display().to_string();
3967 let named = dir.join("named.a").display().to_string();
3968 let line = args(&["--dlltool", "-m", "i386:x86-64", "-d", &def, "-l", &first]);
3969 assert_eq!(run_as("rucc", &line), 0);
3970 let line = args(&["-d", &def, "-l", &named]);
3972 assert_eq!(run_as("/opt/bin/x86_64-w64-mingw32-dlltool", &line), 0);
3973 assert_eq!(std::fs::read(&first).unwrap(), std::fs::read(&named).unwrap());
3974 std::fs::remove_dir_all(&dir).unwrap();
3975
3976 let later = parse_args(&args(&["x.c", "--dlltool", "-d", "x.def"])).unwrap_err();
3977 assert!(later.to_string().contains("first argument"), "{later}");
3978 }
3979
3980 #[test]
3981 fn help_and_version_win_over_everything_else() {
3982 assert_eq!(parse_args(&args(&["-c", "--help", "x.c"])).unwrap(), Action::Help);
3983 assert_eq!(parse_args(&args(&["--version"])).unwrap(), Action::Version);
3984 }
3985
3986 fn compile(s: &[&str]) -> (Box<Options>, Box<Plan>) {
3987 match parse_args(&args(s)).expect("expected a compilation") {
3988 Action::Compile { opts, plan, .. } => (opts, plan),
3989 other => panic!("expected a compilation, got {other:?}"),
3990 }
3991 }
3992
3993 fn linking(s: &[&str]) -> (Box<LinkOptions>, Box<Plan>) {
3994 match parse_args(&args(s)).expect("expected a compilation") {
3995 Action::Compile { link, plan, .. } => (link, plan),
3996 other => panic!("expected a compilation, got {other:?}"),
3997 }
3998 }
3999
4000 fn notes(s: &[&str]) -> Vec<String> {
4001 match parse_args(&args(s)).expect("expected a compilation") {
4002 Action::Compile { notes, .. } => notes,
4003 other => panic!("expected a compilation, got {other:?}"),
4004 }
4005 }
4006
4007 #[test]
4010 fn a_command_line_with_nothing_wrong_with_it_carries_no_notes() {
4011 assert_eq!(notes(&["-c", "a.c"]), Vec::<String>::new());
4012 }
4013
4014 #[test]
4016 fn debug_information_for_coff_is_kept() {
4017 assert_eq!(
4018 notes(&["--target=x86_64-windows-gnu", "-g", "-c", "a.c"]),
4019 Vec::<String>::new()
4020 );
4021 let (opts, _) = compile(&["--target=x86_64-windows-gnu", "-g", "-c", "a.c"]);
4022 assert!(opts.debug_info);
4023 }
4024
4025 #[test]
4030 fn a_named_tree_that_is_not_on_the_machine_is_not_a_release_mismatch() {
4031 let said =
4032 notes(&["--target=x86_64-linux-gnu.2.28", "--sysroot=/nowhere-at-all", "-c", "a.c"]);
4033 assert_eq!(said, Vec::<String>::new());
4034 }
4035
4036 #[test]
4037 fn collects_inputs_and_flags() {
4038 let (opts, plan) = compile(&["-c", "-O2", "-g", "a.c", "b.c"]);
4039 let paths: Vec<&str> = plan.jobs.iter().map(|j| j.input.as_str()).collect();
4040 assert_eq!(paths, vec!["a.c", "b.c"]);
4041 assert_eq!(opts.opt_level, OptLevel::O2);
4042 assert_eq!(opts.emit, EmitKind::Object);
4043 assert!(opts.debug_info);
4044 }
4045
4046 #[test]
4049 fn an_unstable_option_is_taken_and_one_that_does_not_exist_is_refused() {
4050 let (opts, _) = compile(&["-c", "-Zrule-coverage=/tmp/rules.cov", "a.c"]);
4051 assert_eq!(opts.rule_coverage.as_deref(), Some("/tmp/rules.cov"));
4052
4053 let (plain, _) = compile(&["-c", "a.c"]);
4054 assert_eq!(plain.rule_coverage, None, "nothing is measured unless it was asked for");
4055
4056 assert!(parse_args(&args(&["-Zrule-coverage=", "a.c"])).is_err(), "a file with no name");
4057 let unknown = parse_args(&args(&["-Zwhat", "a.c"])).expect_err("there is no such option");
4058 assert!(unknown.message.contains("4.11"), "{}", unknown.message);
4059 }
4060
4061 #[test]
4063 fn where_the_register_pressure_goes_is_asked_for_the_same_way() {
4064 let (opts, _) = compile(&["-c", "-O2", "-Zregister-pressure=/tmp/spills.txt", "a.c"]);
4065 assert_eq!(opts.register_pressure.as_deref(), Some("/tmp/spills.txt"));
4066
4067 let (plain, _) = compile(&["-c", "a.c"]);
4068 assert_eq!(plain.register_pressure, None, "nothing is measured unless it was asked for");
4069
4070 assert!(parse_args(&args(&["-Zregister-pressure=", "a.c"])).is_err(), "no file named");
4071 }
4072
4073 #[test]
4075 fn the_register_allocator_is_asked_for_by_name() {
4076 let (opts, _) = compile(&["-c", "-O2", "-Zregalloc=backtracking", "a.c"]);
4077 assert_eq!(opts.backtracking, Some(true));
4078 let (opts, _) = compile(&["-c", "-O2", "-Zregalloc=single", "a.c"]);
4079 assert_eq!(opts.backtracking, Some(false));
4080 let (plain, _) = compile(&["-c", "-O2", "a.c"]);
4081 assert_eq!(plain.backtracking, None, "the level decides unless it was asked for");
4082 assert!(parse_args(&args(&["-Zregalloc=graph", "a.c"])).is_err(), "not an allocator");
4083 }
4084
4085 #[test]
4087 fn a_switch_shape_is_forced_by_name_and_only_by_one_it_has() {
4088 let (opts, _) = compile(&["-c", "-O2", "-Zswitch=walk", "a.c"]);
4089 assert_eq!(opts.switch_shape.as_deref(), Some("walk"));
4090 let (plain, _) = compile(&["-c", "-O2", "a.c"]);
4091 assert_eq!(plain.switch_shape, None, "nothing is forced unless it was asked for");
4092 assert!(parse_args(&args(&["-Zswitch=bit-test", "a.c"])).is_err(), "not a shape it forces");
4093 }
4094
4095 #[test]
4096 fn where_the_lowering_dump_goes_is_asked_for_the_same_way() {
4097 let (opts, _) = compile(&["-c", "-O2", "-Zlowering=/tmp/lowering.txt", "a.c"]);
4098 assert_eq!(opts.lowering_dump.as_deref(), Some("/tmp/lowering.txt"));
4099
4100 let (plain, _) = compile(&["-c", "a.c"]);
4101 assert_eq!(plain.lowering_dump, None, "nothing is dumped unless it was asked for");
4102
4103 assert!(parse_args(&args(&["-Zlowering=", "a.c"])).is_err(), "no file named");
4104 }
4105
4106 #[test]
4111 fn scheduling_can_be_turned_on_and_off_and_left_to_the_optimization_level() {
4112 let (on, _) = compile(&["-c", "-O0", "-fschedule-insns2", "a.c"]);
4113 assert_eq!(on.schedule_insns, Some(true));
4114
4115 let (off, _) = compile(&["-c", "-O2", "-fno-schedule-insns2", "a.c"]);
4116 assert_eq!(off.schedule_insns, Some(false));
4117
4118 let (quiet, _) = compile(&["-c", "-O2", "a.c"]);
4119 assert_eq!(quiet.schedule_insns, None, "nothing said, so the level decides");
4120 assert!(quiet.opt_level.schedules(), "and at this level the level says yes");
4121
4122 let (none, _) = compile(&["-c", "a.c"]);
4123 assert!(!none.opt_level.schedules(), "at no optimization it says no");
4124 }
4125
4126 #[test]
4128 fn sibling_calls_can_be_turned_on_and_off_and_left_to_the_optimization_level() {
4129 let (on, _) = compile(&["-c", "-O1", "-foptimize-sibling-calls", "a.c"]);
4130 assert_eq!(on.sibling_calls, Some(true));
4131
4132 let (off, _) = compile(&["-c", "-O2", "-fno-optimize-sibling-calls", "a.c"]);
4133 assert_eq!(off.sibling_calls, Some(false));
4134
4135 let (quiet, _) = compile(&["-c", "-Os", "a.c"]);
4136 assert_eq!(quiet.sibling_calls, None, "nothing said, so the level decides");
4137 assert!(quiet.opt_level.sibling_calls(), "and at this level the level says yes");
4138
4139 let (one, _) = compile(&["-c", "-O1", "a.c"]);
4140 assert!(!one.opt_level.sibling_calls(), "gcc leaves them off at -O1");
4141 }
4142
4143 #[test]
4146 fn whether_the_timing_model_is_cycle_accurate_can_be_overridden() {
4147 let (yes, _) = compile(&["-c", "-O2", "-Zcycle-accurate-model=yes", "a.c"]);
4148 assert_eq!(yes.cycle_accurate_model, Some(true));
4149
4150 let (no, _) = compile(&["-c", "-O2", "-Zcycle-accurate-model=no", "a.c"]);
4151 assert_eq!(no.cycle_accurate_model, Some(false));
4152
4153 let (plain, _) = compile(&["-c", "-O2", "a.c"]);
4154 assert_eq!(plain.cycle_accurate_model, None, "the target's own answer stands");
4155
4156 let bad = parse_args(&args(&["-Zcycle-accurate-model=maybe", "a.c"]))
4157 .expect_err("it takes yes or no");
4158 assert!(bad.message.contains("yes or no"), "{}", bad.message);
4159 }
4160
4161 #[test]
4162 fn a_bare_dash_o_means_o1_the_way_gcc_reads_it() {
4163 let (opts, _) = compile(&["-O", "a.c"]);
4164 assert_eq!(opts.opt_level, OptLevel::O1);
4165 }
4166
4167 #[test]
4168 fn dash_x_applies_to_later_inputs_only_and_none_stops_it() {
4169 let (_, plan) = compile(&["a.o", "-x", "c", "b.txt", "-x", "none", "c.o"]);
4170 assert_eq!(plan.jobs[0].kind, InputKind::LinkerInput);
4171 assert_eq!(plan.jobs[1].kind, InputKind::C);
4172 assert_eq!(plan.jobs[2].kind, InputKind::LinkerInput);
4173 }
4174
4175 #[test]
4176 fn dash_x_can_be_joined_to_its_language() {
4177 let (_, plan) = compile(&["a.o", "-xc", "b.txt", "-xnone", "c.o"]);
4178 assert_eq!(plan.jobs[0].kind, InputKind::LinkerInput);
4179 assert_eq!(plan.jobs[1].kind, InputKind::C);
4180 assert_eq!(plan.jobs[2].kind, InputKind::LinkerInput);
4181 }
4182
4183 #[test]
4184 fn dash_j_reaches_the_scheduler_and_defaults_to_the_machine() {
4185 let (_, _, jobs) = match parse_args(&args(&["-j4", "a.c"])).unwrap() {
4186 Action::Compile { opts, plan, jobs, .. } => (opts, plan, jobs),
4187 other => panic!("expected a compilation, got {other:?}"),
4188 };
4189 assert_eq!(jobs.count(), 4);
4190
4191 let default = match parse_args(&args(&["a.c"])).unwrap() {
4192 Action::Compile { jobs, .. } => jobs,
4193 other => panic!("expected a compilation, got {other:?}"),
4194 };
4195 assert_eq!(default, Jobs::available());
4196 assert!(parse_args(&args(&["-j0", "a.c"])).is_err());
4197 }
4198
4199 #[test]
4200 fn triple_hash_prints_the_plan_and_runs_nothing() {
4201 let a = parse_args(&args(&["-###", "-c", "a.c"])).unwrap();
4202 let Action::PrintPlan { plan, .. } = a else { panic!("expected a plan dump") };
4203 assert!(plan.render().contains("a.c: preprocess, compile, assemble -> a.o"));
4204 }
4205
4206 #[test]
4207 fn the_flag_that_keeps_the_intermediate_files_has_three_spellings_and_two_meanings() {
4208 assert_eq!(compile(&["-c", "-save-temps", "a.c"]).0.save_temps, SaveTemps::Object);
4212 assert_eq!(compile(&["-c", "-save-temps=obj", "a.c"]).0.save_temps, SaveTemps::Object);
4213 assert_eq!(compile(&["-c", "-save-temps=cwd", "a.c"]).0.save_temps, SaveTemps::Cwd);
4214 assert_eq!(compile(&["-c", "a.c"]).0.save_temps, SaveTemps::No);
4215 let (opts, _) = compile(&["-c", "-save-temps", "-save-temps=cwd", "a.c"]);
4219 assert_eq!(opts.save_temps, SaveTemps::Cwd);
4220 let e = parse_args(&args(&["-c", "-save-temps=nowhere", "a.c"])).unwrap_err();
4221 assert!(e.message.contains("accepted: cwd, obj"), "{}", e.message);
4222 }
4223
4224 #[test]
4225 fn the_flag_that_times_each_step_reaches_the_options_and_changes_nothing_else() {
4226 let (opts, plan) = compile(&["-c", "-time", "a.c"]);
4227 let (plain, without) = compile(&["-c", "a.c"]);
4228 assert!(opts.time);
4229 assert!(!plain.time);
4230 assert_eq!(plan.jobs[0].output, without.jobs[0].output);
4233 }
4234
4235 #[test]
4236 fn dash_x_names_what_it_accepts_when_it_does_not_know_a_language() {
4237 let e = parse_args(&args(&["-x", "fortran", "a.c"])).unwrap_err();
4238 assert!(e.message.contains("assembler-with-cpp"), "{}", e.message);
4239 }
4240
4241 #[test]
4244 fn a_fetch_of_a_target_nothing_is_pinned_for_says_so_rather_than_reaching_the_network() {
4245 let e = parse_args(&args(&["--fetch", "x86_64-linux-gnux32"])).unwrap_err();
4246 assert!(e.message.contains("pins no sysroot for x86_64-linux-gnux32"), "{}", e.message);
4247 assert!(e.message.contains("x86_64-windows-gnu"), "{}", e.message);
4250 let joined = parse_args(&args(&["--fetch=x86_64-linux-gnux32"])).unwrap_err();
4252 assert_eq!(joined, e);
4253 }
4254
4255 #[test]
4262 fn a_fetch_of_a_target_behind_a_licence_wall_says_so_rather_than_saying_not_yet() {
4263 let e = parse_args(&args(&["--fetch", "aarch64-macos"])).unwrap_err();
4264 assert!(e.message.contains("Xcode licence"), "{}", e.message);
4265 assert!(e.message.contains("there never will be"), "{}", e.message);
4266 assert!(!e.message.contains("tamnd/rucc-cross"), "{}", e.message);
4267
4268 let action = parse_args(&args(&["--fetch", "x86_64-windows-msvc"])).expect("pinned build");
4271 let Action::FetchMsvcSdk { target, accepted, pinned, .. } = action else {
4272 panic!("{action:?}")
4273 };
4274 assert_eq!(target.to_canonical_string(), "x86_64-windows-msvc");
4275 assert!(pinned);
4276 assert!(!accepted);
4277 let action = parse_args(&args(&["--fetch=aarch64-windows-msvc", "--accept-licence"]))
4278 .expect("pinned build");
4279 let Action::FetchMsvcSdk { accepted, pinned, .. } = action else { panic!("{action:?}") };
4280 assert!(accepted && pinned);
4281 let action = parse_args(&args(&["--fetch", "x86_64-windows-gnu"])).expect("it is pinned");
4284 let Action::Fetch { what, .. } = action else { panic!("{action:?}") };
4285 assert_eq!(what.tuple, "x86_64-windows-gnu");
4286 }
4287
4288 #[test]
4289 fn the_other_fetch_takes_a_target_behind_microsofts_wall_and_carries_the_acceptance() {
4290 for line in [
4292 vec!["--fetch-msvc-sdk", "x86_64-windows-msvc"],
4293 vec!["--fetch-msvc-sdk=x86_64-windows-msvc"],
4294 ] {
4295 let action = parse_args(&args(&line)).expect("that is a target behind the wall");
4296 let Action::FetchMsvcSdk { target, accepted, pinned, .. } = action else {
4297 panic!("{action:?}")
4298 };
4299 assert_eq!(target.to_canonical_string(), "x86_64-windows-msvc");
4300 assert!(!accepted);
4302 assert!(!pinned);
4304 }
4305
4306 for word in ["--accept-licence", "--accept-license"] {
4309 let action = parse_args(&args(&["--fetch-msvc-sdk", "aarch64-windows-msvc", word]))
4310 .expect("that is a target behind the wall");
4311 let Action::FetchMsvcSdk { target, accepted, .. } = action else {
4312 panic!("{action:?}")
4313 };
4314 assert_eq!(target.to_canonical_string(), "aarch64-windows-msvc");
4315 assert!(accepted, "{word} should have been read");
4316 }
4317 }
4318
4319 #[test]
4320 fn the_other_fetch_refuses_the_command_lines_that_do_not_mean_anything() {
4321 let e = parse_args(&args(&["--fetch-msvc-sdk"])).unwrap_err();
4323 assert!(e.message.contains("requires the target"), "{}", e.message);
4324 let e = parse_args(&args(&["--fetch-msvc-sdk", "not-a-target"])).unwrap_err();
4325 assert!(e.message.contains("there is no SDK to get"), "{}", e.message);
4326
4327 for line in [
4330 vec!["--offline", "--fetch-msvc-sdk", "x86_64-windows-msvc"],
4331 vec!["--fetch-msvc-sdk", "x86_64-windows-msvc", "--offline"],
4332 ] {
4333 let e = parse_args(&args(&line)).unwrap_err();
4334 assert!(e.message.contains("two opposite things"), "{}", e.message);
4335 }
4336
4337 let e = parse_args(&args(&["--fetch-msvc-sdk", "x86_64-windows-msvc", "a.c"])).unwrap_err();
4339 assert!(e.message.contains("compiles nothing"), "{}", e.message);
4340
4341 let e = parse_args(&args(&[
4343 "--fetch",
4344 "x86_64-windows-gnu",
4345 "--fetch-msvc-sdk",
4346 "x86_64-windows-msvc",
4347 ]))
4348 .unwrap_err();
4349 assert!(e.message.contains("two different commands"), "{}", e.message);
4350
4351 let e = parse_args(&args(&["--accept-licence", "-c", "a.c"])).unwrap_err();
4354 assert!(e.message.contains("--fetch-msvc-sdk <tuple> for a"), "{}", e.message);
4355 }
4356
4357 #[test]
4364 fn an_apple_target_with_no_sdk_anywhere_carries_the_licence_rather_than_a_missing_directory() {
4365 if cfg!(target_os = "macos") || std::env::var_os("SDKROOT").is_some() {
4366 return;
4367 }
4368 let (opts, _) = compile(&["--target=aarch64-macos", "-c", "a.c"]);
4369 let why = opts.search.missing_system().expect("the wall is the reason there are none");
4370 assert!(why.contains("aarch64-macos needs a macOS SDK"), "{why}");
4371 assert!(why.contains("Xcode licence"), "{why}");
4372 assert!(why.contains("-isysroot"), "{why}");
4373
4374 let (opts, _) = compile(&["--target=aarch64-macos", "-nostdinc", "-c", "a.c"]);
4377 assert_eq!(opts.search.missing_system(), None);
4378 let (opts, _) = compile(&["--target=aarch64-macos", "-isysroot", "/opt/sdk", "-c", "a.c"]);
4381 assert_eq!(opts.search.missing_system(), None);
4382 }
4383
4384 #[test]
4393 fn an_msvc_target_with_no_sdk_named_says_which_environment_needs_nothing_installed() {
4394 if cfg!(target_os = "windows") || std::env::var_os("INCLUDE").is_some() {
4395 return;
4396 }
4397 let (opts, _) = compile(&["--target=x86_64-windows-msvc", "-c", "a.c"]);
4398 let why = opts.search.missing_system().expect("the wall is the reason there are none");
4399 assert!(why.contains("the Windows SDK and its universal CRT"), "{why}");
4400 assert!(why.contains("mingw-w64"), "{why}");
4401 let (opts, _) = compile(&["--target=x86_64-windows-gnu", "-c", "a.c"]);
4403 assert_eq!(opts.search.missing_system(), None);
4404 }
4405
4406 #[test]
4407 fn a_fetch_with_no_target_and_a_fetch_of_a_tuple_that_is_not_one_both_say_which() {
4408 let e = parse_args(&args(&["--fetch"])).unwrap_err();
4409 assert!(e.message.contains("--fetch requires"), "{}", e.message);
4410 let e = parse_args(&args(&["--fetch", "sparc64-solaris-gnu"])).unwrap_err();
4411 assert!(e.message.contains("--fetch sparc64-solaris-gnu"), "{}", e.message);
4412 assert!(e.message.contains("no sysroot to get"), "{}", e.message);
4413 }
4414
4415 #[test]
4417 fn a_fetch_and_offline_together_is_a_refusal_whichever_way_round_they_are_written() {
4418 for line in [
4419 vec!["--offline", "--fetch", "x86_64-linux-musl"],
4420 vec!["--fetch", "x86_64-linux-musl", "--offline"],
4421 ] {
4422 let e = parse_args(&args(&line)).unwrap_err();
4423 assert!(e.message.contains("two opposite things"), "{}", e.message);
4424 }
4425 }
4426
4427 #[test]
4428 fn a_fetch_does_not_compile_anything_and_says_so_when_it_is_handed_a_file() {
4429 let e = parse_args(&args(&["--fetch", "x86_64-linux-musl", "a.c"])).unwrap_err();
4430 assert!(e.message.contains("compiles nothing"), "{}", e.message);
4431 assert!(e.message.contains("a.c"), "{}", e.message);
4432 }
4433
4434 #[test]
4438 fn offline_on_a_compilation_is_the_same_compilation() {
4439 let (opts, plan) = compile(&["-c", "--offline", "a.c"]);
4440 let (plain, without) = compile(&["-c", "a.c"]);
4441 assert_eq!(opts.target, plain.target);
4442 assert_eq!(plan.jobs.len(), without.jobs.len());
4443 assert_eq!(plan.jobs[0].output, without.jobs[0].output);
4444 }
4445
4446 #[test]
4447 fn a_deployment_target_comes_from_the_tuple_or_from_the_flag() {
4448 let version = |v: &str| rucc_tuple::Version::parse(v);
4449 let (opts, _) = compile(&["--target=aarch64-macos.13", "-c", "a.c"]);
4450 assert_eq!(opts.target, "aarch64-apple-darwin".parse().unwrap());
4451 assert_eq!(opts.os_version, version("13"));
4452 let (opts, _) =
4454 compile(&["--target=aarch64-macos.13", "-mmacosx-version-min=14.2", "-c", "a.c"]);
4455 assert_eq!(opts.os_version, version("14.2"));
4456 let (opts, _) = compile(&["--target=x86_64-macos", "-mmacos-version-min=12", "-c", "a.c"]);
4457 assert_eq!(opts.os_version, version("12"));
4458 let (opts, _) = compile(&["--target=aarch64-macos", "-c", "a.c"]);
4460 assert_eq!(opts.os_version, None);
4461 let (opts, _) =
4463 compile(&["--target=aarch64-linux-gnu", "-mmacosx-version-min=13", "-c", "a.c"]);
4464 assert_eq!(opts.os_version, None);
4465 let e = parse_args(&args(&["-mmacosx-version-min=thirteen", "a.c"])).unwrap_err();
4466 assert!(e.message.contains("is not a version"), "{}", e.message);
4467 }
4468
4469 #[test]
4470 fn an_unknown_flag_is_an_error_rather_than_a_shrug() {
4471 let e = parse_args(&args(&["-fno-such-thing", "a.c"])).unwrap_err();
4472 assert!(e.message.contains("unknown option"), "{}", e.message);
4473 }
4474
4475 #[test]
4478 fn permissive_reads_in_both_directions_and_the_last_one_wins() {
4479 let (opts, _) = compile(&["-c", "a.c"]);
4480 assert!(!opts.permissive, "off unless it is asked for");
4481
4482 let (opts, _) = compile(&["-c", "-fpermissive", "a.c"]);
4483 assert!(opts.permissive);
4484
4485 let (opts, _) = compile(&["-c", "-fpermissive", "-fno-permissive", "a.c"]);
4486 assert!(!opts.permissive);
4487 }
4488
4489 #[test]
4490 fn asking_for_nested_functions_is_told_why_it_is_not_coming() {
4491 let e = parse_args(&args(&["-fnested-functions", "a.c"])).unwrap_err();
4492 assert!(e.message.contains("trampoline"), "{}", e.message);
4493 assert!(parse_args(&args(&["-fno-nested-functions", "a.c"])).is_ok());
4494 }
4495
4496 #[test]
4497 fn the_flag_every_configure_script_writes_is_taken() {
4498 for flag in ["-fPIC", "-fpic", "-fPIE", "-fpie"] {
4501 let (opts, _) = compile(&["-c", flag, "a.c"]);
4502 assert_eq!(opts.emit, EmitKind::Object, "{flag}");
4503 }
4504 }
4505
4506 #[test]
4507 fn a_table_is_written_unless_the_build_says_nothing_will_walk_it() {
4508 let (opts, _) = compile(&["-c", "a.c"]);
4509 assert!(opts.unwinds(), "the default is off");
4510 let (opts, _) = compile(&["-c", "-fno-asynchronous-unwind-tables", "a.c"]);
4511 assert!(!opts.unwinds(), "the build was not taken at its word");
4512 let (opts, _) = compile(&[
4513 "-c",
4514 "-fno-asynchronous-unwind-tables",
4515 "-fasynchronous-unwind-tables",
4516 "a.c",
4517 ]);
4518 assert!(opts.unwinds(), "the last flag did not win");
4519 let (opts, _) =
4523 compile(&["-c", "-fno-asynchronous-unwind-tables", "-funwind-tables", "a.c"]);
4524 assert!(opts.unwinds(), "the weaker request was dropped");
4525 let (opts, _) = compile(&["-c", "-fno-unwind-tables", "a.c"]);
4526 assert!(opts.unwinds(), "the weaker negative turned off the stronger request");
4527 let (opts, _) =
4528 compile(&["-c", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", "a.c"]);
4529 assert!(!opts.unwinds(), "both were turned off and one stayed on");
4530 }
4531
4532 #[test]
4533 fn the_flags_that_describe_what_this_compiler_already_does_are_taken() {
4534 for flag in [
4538 "-fstrict-aliasing",
4539 "-fno-strict-aliasing",
4540 "-fdelete-null-pointer-checks",
4541 "-fno-delete-null-pointer-checks",
4542 "-frounding-math",
4543 "-fno-rounding-math",
4544 "-fexcess-precision=standard",
4545 "-fexcess-precision=fast",
4546 "-fexcess-precision=16",
4547 "-pipe",
4548 "-cpp",
4549 "-fdiagnostics-color",
4550 "-fno-diagnostics-color",
4551 "-fdiagnostics-color=always",
4552 "-fdiagnostics-color=never",
4553 "-fdiagnostics-color=auto",
4554 ] {
4555 let (opts, _) = compile(&["-c", flag, "a.c"]);
4556 assert_eq!(opts.emit, EmitKind::Object, "{flag}");
4557 }
4558 }
4559
4560 #[test]
4561 fn whether_an_exception_is_looked_at_is_kept_and_defaults_to_gccs_answer() {
4562 let (opts, _) = compile(&["-c", "a.c"]);
4563 assert!(opts.trapping_math, "the default was not gcc's");
4564 let (opts, _) = compile(&["-c", "-fno-trapping-math", "a.c"]);
4565 assert!(!opts.trapping_math);
4566 let (opts, _) = compile(&["-c", "-ftrapping-math", "a.c"]);
4567 assert!(opts.trapping_math, "spelling out the default turned it off");
4568 let (opts, _) = compile(&["-c", "-fno-trapping-math", "-ftrapping-math", "a.c"]);
4571 assert!(opts.trapping_math);
4572 }
4573
4574 #[test]
4579 fn no_inline_turns_off_the_inlining_of_a_function_declared_inline() {
4580 let (opts, _) = compile(&["-c", "-O2", "-fno-inline", "a.c"]);
4581 assert_eq!(opts.passes, [(rucc_opt::inline::NAME.to_owned(), false)]);
4582 }
4583
4584 #[test]
4585 fn inlining_a_function_called_once_is_turned_off_and_on_by_its_own_flag() {
4586 for level in ["-O0", "-O1", "-O2", "-O3", "-Os", "-Oz", "-Og"] {
4587 let (opts, _) = compile(&["-c", level, "-fno-inline-functions-called-once", "a.c"]);
4588 assert_eq!(opts.passes, [(rucc_opt::inline::ONCE.to_owned(), false)], "{level}");
4589 let (opts, _) = compile(&["-c", level, "-finline-functions-called-once", "a.c"]);
4590 assert_eq!(opts.passes, [(rucc_opt::inline::ONCE.to_owned(), true)], "{level}");
4591 }
4592 }
4593
4594 #[test]
4595 fn the_flags_that_name_a_pass_of_gccs_own_are_taken_and_dropped() {
4596 for flag in [
4597 "-fno-tree-ccp",
4598 "-fno-tree-dominator-opts",
4599 "-fno-tree-vrp",
4600 "-fno-tree-bit-ccp",
4601 "-fno-tree-coalesce-vars",
4602 "-ftree-vectorize",
4603 "-ftree-loop-distribution",
4604 "-fipa-pta",
4605 "-fmodulo-sched",
4606 "-fno-vect-cost-model",
4607 "-fvect-cost-model=unlimited",
4608 "-fsimd-cost-model=cheap",
4609 "-fexpensive-optimizations",
4610 "-fno-early-inlining",
4611 "-finline-functions",
4612 "-foptimize-strlen",
4613 "-fno-ira-share-spill-slots",
4614 ] {
4615 let (opts, _) = compile(&["-c", flag, "a.c"]);
4616 assert_eq!(opts.emit, EmitKind::Object, "{flag}");
4617 assert!(opts.passes.is_empty(), "{flag} named a pass of gcc's and not one of ours");
4618 }
4619 }
4620
4621 #[test]
4624 fn a_pass_name_in_either_family_is_taken_whether_or_not_it_is_one_gcc_has() {
4625 for flag in ["-ftree-no-such-pass", "-fno-ipa-no-such-pass"] {
4626 let (opts, _) = compile(&["-c", flag, "a.c"]);
4627 assert_eq!(opts.emit, EmitKind::Object, "{flag}");
4628 }
4629 }
4630
4631 #[test]
4635 fn a_pass_name_this_compiler_has_is_still_read_as_a_pass() {
4636 let (opts, _) = compile(&["-c", "-fno-dce", "a.c"]);
4637 assert_eq!(opts.passes, vec![("dce".to_owned(), false)]);
4638 }
4639
4640 #[test]
4644 fn the_gcc_spelling_of_the_unroller_turns_the_unroller_on_and_off() {
4645 let (opts, _) = compile(&["-c", "-funroll-loops", "a.c"]);
4646 assert_eq!(opts.passes, vec![("unroll".to_owned(), true)]);
4647 let (opts, _) = compile(&["-c", "-fno-unroll-loops", "a.c"]);
4648 assert_eq!(opts.passes, vec![("unroll".to_owned(), false)]);
4649 }
4650
4651 #[test]
4657 fn the_transformations_that_are_not_passes_are_still_named_by_a_flag() {
4658 let (opts, _) = compile(&["-c", "-fno-ipa-cp", "-fipa-sra", "-fno-libcall", "a.c"]);
4659 assert_eq!(
4660 opts.passes,
4661 vec![
4662 (rucc_opt::ipcp::NAME.to_owned(), false),
4663 (rucc_opt::ipasra::NAME.to_owned(), true),
4664 (rucc_opt::libcall::NAME.to_owned(), false),
4665 ]
4666 );
4667 let (opts, _) = compile(&["-c", "-flibcall", "a.c"]);
4668 assert_eq!(opts.passes, vec![(rucc_opt::libcall::NAME.to_owned(), true)]);
4669 }
4670
4671 #[test]
4677 fn the_alignment_of_a_function_is_a_request_this_compiler_can_answer() {
4678 let (opts, _) = compile(&["-c", "-falign-functions", "a.c"]);
4679 assert_eq!(opts.align_functions, None, "the bare form asks for the default");
4680
4681 let (opts, _) = compile(&["-c", "-falign-functions=32", "a.c"]);
4682 assert_eq!(opts.align_functions, Some(32));
4683
4684 let (opts, _) = compile(&["-c", "-falign-functions=3", "a.c"]);
4685 assert_eq!(opts.align_functions, Some(4), "rounded up rather than refused");
4686
4687 let (opts, _) = compile(&["-c", "-falign-functions=32:8", "a.c"]);
4688 assert_eq!(opts.align_functions, Some(32), "the boundary is the answerable half");
4689
4690 for flag in ["-falign-functions=0", "-falign-functions=1"] {
4691 let (opts, _) = compile(&["-c", flag, "a.c"]);
4692 assert_eq!(opts.align_functions, None, "{flag} means the default");
4693 }
4694
4695 let (opts, _) = compile(&["-c", "-fno-align-functions", "a.c"]);
4696 assert_eq!(opts.align_functions, Some(8), "the smallest boundary the target has");
4697
4698 let (opts, _) = compile(&["-c", "-falign-functions=32", "-falign-functions", "a.c"]);
4700 assert_eq!(opts.align_functions, None);
4701
4702 let e = parse_args(&args(&["-c", "-falign-functions=big", "a.c"])).unwrap_err();
4703 assert!(e.message.contains("number of bytes"), "{}", e.message);
4704 }
4705
4706 #[test]
4710 fn the_alignment_flags_about_the_inside_of_a_body_are_taken_and_say_nothing() {
4711 for flag in [
4712 "-falign-labels",
4713 "-falign-loops",
4714 "-falign-jumps",
4715 "-falign-loops=16",
4716 "-falign-labels=32",
4717 "-fno-align-loops",
4718 "-fno-align-labels",
4719 "-fno-align-jumps",
4720 ] {
4721 let (opts, _) = compile(&["-c", flag, "a.c"]);
4722 assert_eq!(opts.emit, EmitKind::Object, "{flag}");
4723 assert_eq!(opts.align_functions, None, "{flag} is not about where a function starts");
4724 }
4725 }
4726
4727 #[test]
4730 fn the_loop_alignment_flag_is_answered_both_ways() {
4731 assert_eq!(compile(&["-c", "-O2", "a.c"]).0.align_loops, None);
4732 assert_eq!(compile(&["-c", "-O0", "-falign-loops", "a.c"]).0.align_loops, Some(true));
4733 assert_eq!(compile(&["-c", "-O2", "-fno-align-loops", "a.c"]).0.align_loops, Some(false));
4734 assert_eq!(compile(&["-c", "-falign-loops=32", "a.c"]).0.align_loops, None, "a number");
4735 }
4736
4737 #[test]
4740 fn the_input_charset_is_taken_when_it_names_the_one_that_is_read() {
4741 for flag in ["-finput-charset=utf-8", "-finput-charset=UTF-8", "-finput-charset=utf8"] {
4742 let (opts, _) = compile(&["-c", flag, "a.c"]);
4743 assert_eq!(opts.emit, EmitKind::Object, "{flag}");
4744 }
4745
4746 let e = parse_args(&args(&["-c", "-finput-charset=latin1", "a.c"])).unwrap_err();
4747 assert!(e.message.contains("latin1"), "{}", e.message);
4748 assert!(e.message.contains("UTF-8"), "what is read is worth saying: {}", e.message);
4749 }
4750
4751 #[test]
4754 fn exceptions_are_on_when_asked_for_and_non_call_ones_ask_unless_told_not_to() {
4755 let (opts, _) = compile(&["-c", "a.c"]);
4756 assert!(!opts.exceptions && !opts.non_call_exceptions, "gcc's default for C is off");
4757 let (opts, _) = compile(&["-c", "-fexceptions", "a.c"]);
4758 assert!(opts.exceptions && !opts.non_call_exceptions);
4759 let (opts, _) = compile(&["-c", "-fexceptions", "-fno-exceptions", "a.c"]);
4760 assert!(!opts.exceptions);
4761 let (opts, _) = compile(&["-c", "-fnon-call-exceptions", "a.c"]);
4762 assert!(opts.exceptions && opts.non_call_exceptions);
4763 for line in [
4764 ["-fno-exceptions", "-fnon-call-exceptions"],
4765 ["-fnon-call-exceptions", "-fno-exceptions"],
4766 ] {
4767 let (opts, _) = compile(&["-c", line[0], line[1], "a.c"]);
4768 assert!(!opts.exceptions && opts.non_call_exceptions, "{line:?}");
4769 }
4770 let (opts, _) =
4771 compile(&["-c", "-fnon-call-exceptions", "-fno-non-call-exceptions", "a.c"]);
4772 assert!(!opts.exceptions && !opts.non_call_exceptions);
4773 let (opts, _) = compile(&["-c", "-fno-delete-dead-exceptions", "a.c"]);
4774 assert_eq!(opts.emit, EmitKind::Object);
4775 }
4776
4777 #[test]
4780 fn fast_math_is_the_family_it_names_and_the_last_word_on_each_member_wins() {
4781 let both = |line: &[&str]| {
4782 let (opts, _) = compile(&[&["-c"], line, &["a.c"]].concat());
4783 let (link, _) = linking(&[line, &["a.c"]].concat());
4784 (opts, link)
4785 };
4786 let (opts, link) = both(&[]);
4787 assert_eq!(opts.math, Math::default());
4788 assert!(opts.trapping_math);
4789 assert!(!link.fast_math);
4790
4791 let (opts, link) = both(&["-ffast-math"]);
4792 assert!(opts.math.fast(opts.trapping_math), "{:?}", opts.math);
4793 assert!(!opts.trapping_math, "fast math turns trapping off");
4794 assert!(link.fast_math, "and it links the startup file");
4795
4796 let (opts, link) = both(&["-ffast-math", "-fno-finite-math-only"]);
4798 assert!(!opts.math.finite_only);
4799 assert!(!opts.math.errno && !opts.math.signed_zeros && opts.math.reciprocal);
4800 assert!(!opts.math.fast(opts.trapping_math));
4801 assert!(link.fast_math, "gcc's spec reads the flag and not the fields");
4802
4803 let (opts, _) = both(&["-ffast-math", "-ftrapping-math"]);
4804 assert!(opts.trapping_math);
4805 assert!(!opts.math.fast(opts.trapping_math));
4806 assert!(!opts.math.associative(opts.trapping_math));
4807
4808 let (opts, link) = both(&["-ffast-math", "-fno-fast-math"]);
4809 assert_eq!(opts.math, Math::default());
4810 assert!(opts.trapping_math);
4811 assert!(!link.fast_math);
4812
4813 let (opts, link) = both(&["-fno-math-errno"]);
4815 assert_eq!(opts.math, Math { errno: false, ..Math::default() });
4816 assert!(opts.math.iec_559(opts.trapping_math), "errno is not an IEC 60559 question");
4817 assert!(!link.fast_math);
4818
4819 let (opts, link) = both(&["-funsafe-math-optimizations"]);
4820 assert!(opts.math.unsafe_math && opts.math.associative(opts.trapping_math));
4821 assert!(opts.math.errno && !opts.math.finite_only);
4822 assert!(link.fast_math);
4823 }
4824
4825 #[test]
4828 fn ofast_is_o3_with_fast_math_as_a_default_a_flag_can_take_back() {
4829 let both = |line: &[&str]| {
4830 let (opts, _) = compile(&[&["-c"], line, &["a.c"]].concat());
4831 let (link, _) = linking(&[line, &["a.c"]].concat());
4832 (opts, link)
4833 };
4834 let (opts, link) = both(&["-Ofast"]);
4835 assert_eq!(opts.opt_level, OptLevel::O3);
4836 assert!(opts.math.fast(opts.trapping_math));
4837 assert!(link.fast_math);
4838
4839 for line in [&["-Ofast", "-O2"][..], &["-fno-fast-math", "-Ofast"]] {
4840 let (opts, _) = both(line);
4841 assert!(!opts.math.fast(opts.trapping_math), "{line:?}");
4842 }
4843
4844 let (_, link) = both(&["-Ofast", "-mno-daz-ftz"]);
4845 assert_eq!(link.daz_ftz, Some(false));
4846 }
4847
4848 #[test]
4851 fn instrument_functions_is_taken_and_the_last_of_the_pair_wins() {
4852 let (opts, _) = compile(&["-c", "-finstrument-functions", "a.c"]);
4853 assert!(opts.instrument_functions);
4854 let (opts, _) =
4855 compile(&["-c", "-finstrument-functions", "-fno-instrument-functions", "a.c"]);
4856 assert!(!opts.instrument_functions);
4857 }
4858
4859 #[test]
4860 fn a_tentative_definition_is_common_on_darwin_unless_told_otherwise() {
4861 let (opts, _) = compile(&[LINUX, "-c", "a.c"]);
4864 assert!(!Session::new(*opts).common());
4865
4866 let (opts, _) = compile(&["-c", "--target=aarch64-apple-darwin", "a.c"]);
4867 assert!(Session::new(*opts).common());
4868
4869 let (opts, _) = compile(&[LINUX, "-c", "-fcommon", "a.c"]);
4870 assert!(Session::new(*opts).common());
4871
4872 let (opts, _) =
4873 compile(&["-c", "--target=aarch64-apple-darwin", "-fcommon", "-fno-common", "a.c"]);
4874 assert!(!Session::new(*opts).common());
4875 }
4876
4877 #[test]
4878 fn asking_for_position_dependent_code_is_told_why_it_is_not_coming() {
4879 for flag in ["-fno-pic", "-fno-pie"] {
4880 let e = parse_args(&args(&[flag, "a.c"])).unwrap_err();
4881 assert!(e.message.contains("global offset table"), "{flag}: {}", e.message);
4882 assert!(e.message.contains("-no-pie"), "{flag}: {}", e.message);
4885 }
4886 }
4887
4888 #[test]
4889 fn a_program_name_with_a_known_target_in_front_of_rucc_picks_that_target() {
4890 let t = |p: &str| target_from_program(p);
4891 assert_eq!(t("aarch64-linux-gnu-rucc").as_deref(), Some("aarch64-linux-gnu"));
4892 assert_eq!(t("/usr/bin/riscv64-linux-musl-rucc").as_deref(), Some("riscv64-linux-musl"));
4893 assert_eq!(t(r"C:\bin\x86_64-windows-gnu-rucc.exe").as_deref(), Some("x86_64-windows-gnu"));
4894 assert_eq!(t("rucc"), None);
4895 assert_eq!(t("/usr/local/bin/rucc"), None);
4896 assert_eq!(t("my-rucc"), None);
4897 assert_eq!(t("sparc64-linux-gnu-rucc"), None);
4898 assert_eq!(t("aarch64-linux-gnu-gcc"), None);
4899 }
4900
4901 #[test]
4902 fn an_unsupported_target_names_itself() {
4903 let e = parse_args(&args(&["--target=sparc64-linux-gnu", "a.c"])).unwrap_err();
4904 assert!(e.message.contains("sparc64"), "{}", e.message);
4905 }
4906
4907 #[test]
4908 fn no_inputs_is_an_error_but_print_config_needs_none() {
4909 assert!(parse_args(&args(&[])).is_err());
4910 assert!(matches!(parse_args(&args(&["--print-config"])), Ok(Action::PrintConfig(_))));
4911 }
4912
4913 #[test]
4914 fn print_config_reports_the_target_it_was_given_not_the_host() {
4915 let a = parse_args(&args(&["--print-config", "--target=riscv64-linux-musl"])).unwrap();
4916 let Action::PrintConfig(opts) = a else { panic!("expected a configuration dump") };
4917 let text = print_config(&opts);
4918 assert!(text.contains("target: riscv64-unknown-linux-musl"), "{text}");
4919 assert!(text.contains("char-signed: false"), "{text}");
4920 assert!(text.contains("object-format: elf"), "{text}");
4921 assert!(text.contains("va-list: void-pointer"), "{text}");
4922 assert!(text.contains("registers: none"), "{text}");
4925 assert!(text.contains("timing-model: none"), "{text}");
4926 }
4927
4928 #[test]
4931 fn print_config_names_the_model_the_schedule_was_chosen_with() {
4932 let opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
4933 let text = print_config(&opts);
4934 let line = text.lines().find(|l| l.starts_with("timing-model:")).expect("the model");
4935 assert!(line.contains("Skylake"), "{line}");
4936 assert!(line.contains("published"), "a sentence saying where it came from: {line}");
4937 }
4938
4939 #[test]
4940 fn print_config_has_one_key_per_line_and_a_fixed_order() {
4941 let opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
4942 let text = print_config(&opts);
4943 let keys: Vec<&str> =
4944 text.lines().map(|l| l.split(':').next().unwrap_or_default()).collect();
4945 assert_eq!(keys[0], "version");
4946 assert_eq!(keys[1], "target");
4947 assert_eq!(keys.len(), 26);
4948 assert!(text.ends_with('\n'));
4949 }
4950
4951 #[test]
4952 fn the_safety_tier_is_read_off_the_command_line_and_a_wrong_one_is_refused() {
4953 let (opts, _) = compile(&["a.c"]);
4954 assert_eq!(opts.safety, rucc_session::Safety::Off);
4955
4956 for (flag, tier) in [
4957 ("-fsafety=detect", rucc_session::Safety::Detect),
4958 ("-fsafety=enforce", rucc_session::Safety::Enforce),
4959 ("-fsafety=kernel", rucc_session::Safety::Kernel),
4960 ("-fsafety=off", rucc_session::Safety::Off),
4961 ] {
4962 let (opts, _) = compile(&[flag, "a.c"]);
4963 assert_eq!(opts.safety, tier, "{flag}");
4964 }
4965
4966 let (opts, _) = compile(&["-fsafety=enforce", "-fsafety=off", "a.c"]);
4968 assert_eq!(opts.safety, rucc_session::Safety::Off);
4969
4970 let e = parse_args(&args(&["-fsafety=on", "a.c"])).unwrap_err();
4973 assert!(e.message.contains("is not a safety tier"), "{}", e.message);
4974 assert!(parse_args(&args(&["-fsafety", "a.c"])).is_err());
4975 }
4976
4977 #[test]
4981 fn a_checked_mode_on_windows_is_refused_at_the_link_and_nowhere_else() {
4982 let (opts, _) = compile(&["--target=x86_64-windows-gnu", "-fsafety=detect", "a.c"]);
4983 let why = unlinkable(&opts).expect("a refusal");
4984 assert!(why.contains("not available on a Windows target"), "{why}");
4985 let (opts, _) = compile(&["--target=x86_64-windows-gnu", "-fsafety=off", "a.c"]);
4986 assert_eq!(unlinkable(&opts), None);
4987 let (opts, _) = compile(&["--target=x86_64-linux-gnu", "-fsafety=detect", "a.c"]);
4988 assert_eq!(unlinkable(&opts), None);
4989 }
4990
4991 #[test]
4992 fn the_padding_mode_is_read_off_the_command_line_and_a_wrong_one_is_refused() {
4993 let (opts, _) = compile(&["a.c"]);
4996 assert_eq!(opts.padding, rucc_session::Padding::Ignored);
4997
4998 let (opts, _) = compile(&["-fsafety=detect", "-fsafety-init=padding", "a.c"]);
4999 assert_eq!(opts.padding, rucc_session::Padding::Tracked);
5000
5001 let (opts, _) = compile(&["-fsafety-init=padding", "-fsafety-init=nopadding", "a.c"]);
5002 assert_eq!(opts.padding, rucc_session::Padding::Ignored);
5003
5004 let (opts, _) = compile(&["-fsafety-init=padding", "a.c"]);
5007 assert_eq!(opts.safety, rucc_session::Safety::Off);
5008
5009 let e = parse_args(&args(&["-fsafety-init=some", "a.c"])).unwrap_err();
5010 assert!(e.message.contains("is not a padding mode"), "{}", e.message);
5011 }
5012
5013 #[test]
5014 fn whether_a_write_has_to_stay_inside_its_member_is_read_off_the_command_line() {
5015 let (opts, _) = compile(&["a.c"]);
5018 assert_eq!(opts.subobject, rucc_session::Subobject::Off);
5019
5020 let (opts, _) = compile(&["-fsafety=detect", "-fsafety-subobject", "a.c"]);
5021 assert_eq!(opts.subobject, rucc_session::Subobject::Members);
5022
5023 let (opts, _) = compile(&["-fsafety-subobject", "-fno-safety-subobject", "a.c"]);
5024 assert_eq!(opts.subobject, rucc_session::Subobject::Off);
5025
5026 let e = parse_args(&args(&["-fsafety-subobject=strict", "a.c"])).unwrap_err();
5029 assert!(e.message.contains("tamnd/rucc#967"), "{}", e.message);
5030 }
5031
5032 #[test]
5033 fn whether_two_restrict_pointers_may_meet_is_read_off_the_command_line() {
5034 let (opts, _) = compile(&["a.c"]);
5039 assert_eq!(opts.promise, rucc_session::Promise::Off);
5040
5041 let (opts, _) = compile(&["-fsafety=detect", "-fsafety-restrict", "a.c"]);
5042 assert_eq!(opts.promise, rucc_session::Promise::Blocks);
5043
5044 let (opts, _) = compile(&["-fsafety-restrict", "-fno-safety-restrict", "a.c"]);
5045 assert_eq!(opts.promise, rucc_session::Promise::Off);
5046
5047 let (opts, _) = compile(&["-fsafety-restrict", "a.c"]);
5050 assert_eq!(opts.safety, rucc_session::Safety::Off);
5051
5052 let e = parse_args(&args(&["-fsafety-restrict=blocks", "a.c"])).unwrap_err();
5053 assert!(e.message.contains("takes no value"), "{}", e.message);
5054 }
5055
5056 #[test]
5057 fn safety_races_takes_a_mode_and_defaults_to_watching_nothing() {
5058 let (opts, _) = compile(&["a.c"]);
5063 assert_eq!(opts.races, rucc_session::Races::Off);
5064
5065 let (opts, _) = compile(&["-fsafety-races=metadata", "a.c"]);
5066 assert_eq!(opts.races, rucc_session::Races::Metadata);
5067
5068 let (opts, _) = compile(&["-fsafety-races=pointer", "a.c"]);
5069 assert_eq!(opts.races, rucc_session::Races::Pointer);
5070
5071 let (opts, _) = compile(&["-fsafety-races=pointer", "-fno-safety-races", "a.c"]);
5073 assert_eq!(opts.races, rucc_session::Races::Off);
5074
5075 let e = parse_args(&args(&["-fsafety-races=all", "a.c"])).unwrap_err();
5076 assert!(e.message.contains("off, metadata or pointer"), "{}", e.message);
5077 }
5078
5079 #[test]
5080 fn print_pipeline_answers_with_the_passes_the_level_asked_for() {
5081 let a = parse_args(&args(&["--print-pipeline", "-O2"])).unwrap();
5082 let Action::PrintPipeline(opts) = a else { panic!("expected a pipeline dump") };
5083 let text = print_pipeline(&opts);
5084 assert!(text.starts_with("level: -O2\n"), "{text}");
5085 assert!(text.contains("fold"), "{text}");
5086
5087 let a = parse_args(&args(&["--print-pipeline"])).unwrap();
5088 let Action::PrintPipeline(opts) = a else { panic!("expected a pipeline dump") };
5089 assert!(print_pipeline(&opts).contains("1: expect,"), "{}", print_pipeline(&opts));
5094 assert!(print_pipeline(&opts).contains("2: simplify-cfg,"), "{}", print_pipeline(&opts));
5095
5096 let a = parse_args(&args(&["--print-pipeline", "-fno-simplify-cfg"])).unwrap();
5097 let Action::PrintPipeline(opts) = a else { panic!("expected a pipeline dump") };
5098 let text = print_pipeline(&opts);
5101 assert!(text.contains("1: expect,"), "{text}");
5102 assert!(!text.contains("simplify-cfg"), "{text}");
5103 }
5104
5105 #[test]
5106 fn print_pipeline_takes_the_toggles_into_account() {
5107 let a = parse_args(&args(&["--print-pipeline", "-O2", "-fno-fold"])).unwrap();
5108 let Action::PrintPipeline(opts) = a else { panic!("expected a pipeline dump") };
5109 let text = print_pipeline(&opts);
5110 assert!(!text.contains("fold"), "{text}");
5113 assert!(text.contains("dce"), "{text}");
5114
5115 let mut off = vec!["--print-pipeline".to_owned(), "-O2".to_owned()];
5121 off.extend(rucc_opt::PASSES.iter().map(|p| format!("-fno-{}", p.name())));
5122 let spelled: Vec<&str> = off.iter().map(String::as_str).collect();
5123 let a = parse_args(&args(&spelled)).unwrap();
5124 let Action::PrintPipeline(opts) = a else { panic!("expected a pipeline dump") };
5125 let text = print_pipeline(&opts);
5126 let left: Vec<&str> =
5127 rucc_opt::PASSES.iter().filter(|p| p.required()).map(|p| p.name()).collect();
5128 assert_eq!(left, vec!["expect", "constant-p"], "{text}");
5129 for (at, name) in left.iter().enumerate() {
5130 assert!(text.contains(&format!("{}: {name},", at + 1)), "{text}");
5131 }
5132 assert!(!text.contains("dce"), "{text}");
5133 }
5134
5135 #[test]
5136 fn print_pipeline_says_when_a_budget_will_stop_the_run_short() {
5137 let a = parse_args(&args(&["--print-pipeline", "-O2"])).unwrap();
5138 let Action::PrintPipeline(opts) = a else { panic!("expected a pipeline dump") };
5139 assert!(!print_pipeline(&opts).contains("global fuel"));
5140
5141 let a = parse_args(&args(&["--print-pipeline", "-O2", "-fpass-fuel-global=4"])).unwrap();
5142 let Action::PrintPipeline(opts) = a else { panic!("expected a pipeline dump") };
5143 let text = print_pipeline(&opts);
5144 assert!(text.contains("global fuel: 4"), "{text}");
5147 }
5148
5149 #[test]
5152 fn a_pass_is_named_by_dash_f_and_unnamed_by_dash_f_no() {
5153 let (opts, _) = compile(&["-c", "-O0", "-ffold", "-fno-fold", "-ffold", "a.c"]);
5154 assert_eq!(
5155 opts.passes,
5156 [("fold".to_owned(), true), ("fold".to_owned(), false), ("fold".to_owned(), true)]
5157 );
5158
5159 let e = parse_args(&args(&["-fno-such-pass", "a.c"])).unwrap_err();
5160 assert!(e.message.contains("unknown option"), "{}", e.message);
5161 }
5162
5163 #[test]
5164 fn pass_fuel_names_a_pass_and_a_count_and_refuses_anything_else() {
5165 let (opts, _) = compile(&["-c", "-O2", "-fpass-fuel=fold=3", "a.c"]);
5166 assert_eq!(opts.pass_fuel, [("fold".to_owned(), 3)]);
5167
5168 let e = parse_args(&args(&["-fpass-fuel=fold", "a.c"])).unwrap_err();
5169 assert!(e.message.contains("<pass>=<count>"), "{}", e.message);
5170 let e = parse_args(&args(&["-fpass-fuel=nosuch=3", "a.c"])).unwrap_err();
5171 assert!(e.message.contains("--print-pipeline"), "{}", e.message);
5172 let e = parse_args(&args(&["-fpass-fuel=fold=lots", "a.c"])).unwrap_err();
5173 assert!(e.message.contains("not a number"), "{}", e.message);
5174 }
5175
5176 #[test]
5177 fn global_pass_fuel_is_a_count_on_its_own_and_defaults_to_no_limit() {
5178 let (opts, _) = compile(&["-c", "-O2", "a.c"]);
5179 assert_eq!(opts.pass_fuel_global, None);
5180
5181 let (opts, _) = compile(&["-c", "-O2", "-fpass-fuel-global=12", "a.c"]);
5182 assert_eq!(opts.pass_fuel_global, Some(12));
5183 assert!(opts.pass_fuel.is_empty());
5186
5187 let e = parse_args(&args(&["-fpass-fuel-global=lots", "a.c"])).unwrap_err();
5188 assert!(e.message.contains("not a number"), "{}", e.message);
5189 }
5190
5191 #[test]
5192 fn the_trace_file_is_taken_from_the_flag_and_an_empty_one_is_refused() {
5193 let (opts, _) = compile(&["-c", "a.c"]);
5194 assert_eq!(opts.trace, None);
5195 let (opts, _) = compile(&["-c", "-frucc-trace=/tmp/compile.jsonl", "a.c"]);
5196 assert_eq!(opts.trace.as_deref(), Some("/tmp/compile.jsonl"));
5197 let e = parse_args(&args(&["-frucc-trace=", "a.c"])).unwrap_err();
5198 assert!(e.message.contains("needs a file"), "{}", e.message);
5199 }
5200
5201 #[test]
5202 fn a_gate_names_a_pass_and_optionally_the_functions_it_covers() {
5203 let (opts, _) = compile(&["-c", "-O2", "-fdisable-fold", "-fenable-fold=2-4,main", "a.c"]);
5204 assert_eq!(
5205 opts.pass_gates,
5206 [(false, "fold".to_owned()), (true, "fold=2-4,main".to_owned())],
5207 "the order is what decides, so it has to survive the parse"
5208 );
5209
5210 let e = parse_args(&args(&["-fdisable-nosuch", "a.c"])).unwrap_err();
5211 assert!(e.message.contains("--print-pipeline"), "{}", e.message);
5212 let e = parse_args(&args(&["-fenable-fold=9-2", "a.c"])).unwrap_err();
5213 assert!(e.message.contains("ends before it starts"), "{}", e.message);
5214 let e = parse_args(&args(&["-fdisable-fold=", "a.c"])).unwrap_err();
5215 assert!(e.message.contains("is empty"), "{}", e.message);
5216 }
5217
5218 #[test]
5219 fn the_pipeline_listing_says_which_passes_a_gate_touched() {
5220 let (opts, _) = compile(&["-c", "-O2", "-fdisable-fold=main", "a.c"]);
5221 let text = print_pipeline(&opts);
5222 assert!(text.contains("fold, "), "{text}");
5223 assert!(text.contains("[off for main]"), "{text}");
5224 }
5225
5226 #[test]
5230 fn a_dump_is_checked_when_it_is_asked_for_rather_than_when_it_is_taken() {
5231 let (opts, _) = compile(&["-c", "-O2", "-fdump-ir=all", "-fdump-ir=after-fold", "a.c"]);
5232 assert_eq!(opts.dump_ir, ["all", "after-fold"]);
5233
5234 let e = parse_args(&args(&["-fdump-ir=after-nosuch", "a.c"])).unwrap_err();
5235 assert!(e.message.contains("nosuch"), "{}", e.message);
5236 assert!(parse_args(&args(&["-fdump-ir=sideways-fold", "a.c"])).is_err());
5237 }
5238
5239 #[test]
5245 fn opt_info_takes_kinds_and_a_file_and_refuses_a_kind_it_does_not_have() {
5246 let (opts, _) = compile(&["-c", "-O2", "-fopt-info", "a.c"]);
5247 assert_eq!(opts.opt_info, [""], "a bare flag asks for the rewrites");
5248 assert_eq!(opts.opt_info_file, None, "and goes to standard error");
5249
5250 let (opts, _) = compile(&["-c", "-O2", "-fopt-info-missed-note", "a.c"]);
5251 assert_eq!(opts.opt_info, ["missed-note"]);
5252
5253 let (opts, _) =
5256 compile(&["-c", "-O2", "-fopt-info-missed=one.txt", "-fopt-info-all=two.txt", "a.c"]);
5257 assert_eq!(opts.opt_info, ["missed", "all"]);
5258 assert_eq!(opts.opt_info_file.as_deref(), Some("two.txt"));
5259
5260 let e = parse_args(&args(&["-fopt-info-vectorized", "a.c"])).unwrap_err();
5261 assert!(e.message.contains("vectorized"), "{}", e.message);
5262 assert!(e.message.contains("`missed`"), "{}", e.message);
5263 let e = parse_args(&args(&["-fopt-info-missed=", "a.c"])).unwrap_err();
5264 assert!(e.message.contains("no file"), "{}", e.message);
5265 }
5266
5267 #[test]
5268 fn verify_each_is_unstable_and_off_unless_it_was_asked_for() {
5269 let (opts, _) = compile(&["-c", "-Zverify-each", "a.c"]);
5270 assert!(opts.verify_each);
5271 assert!(!USAGE.contains("verify-each"), "an unstable option stays out of the usage text");
5272 }
5273
5274 #[test]
5275 fn dash_o_needs_an_argument() {
5276 let e = parse_args(&args(&["a.c", "-o"])).unwrap_err();
5277 assert_eq!(e.message, "-o requires an argument");
5278 }
5279
5280 #[test]
5281 fn dash_d_and_dash_u_are_read_joined_or_separated_and_keep_their_order() {
5282 let (opts, _) = compile(&["-DFOO=1", "-D", "BAR", "-UBAZ", "-U", "QUX", "a.c"]);
5283 assert_eq!(opts.defines, ["FOO=1", "BAR"]);
5284 assert_eq!(opts.undefines, ["BAZ", "QUX"]);
5285 }
5286
5287 #[test]
5288 fn the_include_flags_land_on_the_chain_each_one_names() {
5289 let (opts, _) = compile(&[
5292 "-Ii",
5293 "-iquote",
5294 "q",
5295 "-isystem",
5296 "sys",
5297 "-idirafter",
5298 "after",
5299 "--sysroot=/nowhere-at-all",
5300 "a.c",
5301 ]);
5302 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
5303 assert_eq!(dirs, ["q", "i", "sys", runtime::DIR, "after"]);
5306 assert!(!opts.search.dirs()[1].is_system);
5307 assert!(opts.search.dirs()[2].is_system);
5308 }
5309
5310 #[test]
5311 fn the_librarys_headers_come_after_the_compilers_own_and_go_away_with_them() {
5312 let (opts, _) = compile(&["a.c"]);
5316 let dirs = opts.search.dirs();
5317 let ours = dirs.iter().position(|d| d.path.to_str() == Some(runtime::DIR));
5318 assert_eq!(ours, Some(0), "{dirs:?}");
5319 assert!(dirs[1..].iter().all(|d| d.is_system), "{dirs:?}");
5320 let (bare, _) = compile(&["-nostdinc", "a.c"]);
5321 assert!(bare.search.dirs().is_empty(), "{:?}", bare.search.dirs());
5322 }
5323
5324 #[test]
5325 fn a_sysroot_moves_the_librarys_directories_and_nothing_else() {
5326 let (opts, _) = compile(&["-isystem", "sys", "--sysroot=/nowhere-at-all", "a.c"]);
5327 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
5328 assert_eq!(dirs, ["sys", runtime::DIR]);
5329 }
5330
5331 #[test]
5332 fn a_cross_compile_reads_the_targets_own_headers_rather_than_the_ones_next_door() {
5333 let (opts, _) = compile(&["--target=riscv64-linux-musl", "-c", "a.c"]);
5338 let dirs: Vec<&std::path::Path> =
5339 opts.search.dirs().iter().map(|d| d.path.as_path()).collect();
5340 let root = cache::dir().join("sysroots").join("riscv64-linux-musl");
5341 let kernel = cache::dir().join("kernel-headers");
5342 assert_eq!(dirs.len(), 5, "{dirs:?}");
5343 assert_eq!(dirs[0], std::path::Path::new(runtime::DIR));
5344 assert_eq!(dirs[1], root.join("include").join("riscv64"));
5345 assert_eq!(dirs[2], root.join("include").join("generic"));
5346 assert_eq!(dirs[3], kernel.join("riscv"));
5349 assert_eq!(dirs[4], kernel.join("generic"));
5350 }
5351
5352 #[test]
5353 fn a_cross_compile_to_something_that_is_not_linux_reads_no_kernel_headers() {
5354 let (opts, _) = compile(&["--target=x86_64-pc-windows-gnu", "-c", "a.c"]);
5361 let dirs: Vec<&std::path::Path> =
5362 opts.search.dirs().iter().map(|d| d.path.as_path()).collect();
5363 assert_eq!(dirs.len(), 2, "{dirs:?}");
5364 assert!(!dirs.iter().any(|dir| dir.ends_with("kernel-headers")), "{dirs:?}");
5365 }
5366
5367 #[test]
5368 fn the_glibc_version_macro_goes_with_the_bundled_tree_and_with_nothing_else() {
5369 let gnu = format!("--target={}-linux-gnu", cross_arch());
5383 let (bundled, _) = compile(&[&gnu, "-c", "a.c"]);
5384 let (link, _) = linking(&[&gnu, "-c", "a.c"]);
5385 let distro = link::distro_cross(bundled.target, &link).is_some();
5386 assert_eq!(bundled.glibc_minor, if distro { None } else { Some(44) });
5387 let pin = format!("{gnu}.2.28");
5388 let (pinned, _) = compile(&[&pin, "-c", "a.c"]);
5389 assert_eq!(pinned.glibc_minor, Some(28));
5390
5391 let (named, _) = compile(&[&gnu, "--sysroot=/nowhere-at-all", "-c", "a.c"]);
5392 assert_eq!(named.glibc_minor, None);
5393 let (none, _) = compile(&[&gnu, "-nostdinc", "-c", "a.c"]);
5394 assert_eq!(none.glibc_minor, None);
5395 let musl = format!("--target={}-linux-musl", cross_arch());
5396 let (musl, _) = compile(&[&musl, "-c", "a.c"]);
5397 assert_eq!(musl.glibc_minor, None);
5398
5399 if let Some(host) = Triple::host() {
5404 let native = format!("--target={}", host.tuple());
5405 let (native, _) = compile(&[&native, "-c", "a.c"]);
5406 assert_eq!(native.glibc_minor, None);
5407 }
5408 }
5409
5410 #[test]
5411 fn a_pinned_release_on_this_machines_own_target_reads_the_bundled_tree() {
5412 let Some(host) = Triple::host() else { return };
5420 if host.os != rucc_target::Os::Linux || host.env != rucc_target::Env::Gnu {
5421 return;
5422 }
5423 let pin = format!("--target={}.2.28", host.tuple());
5424 let (opts, _) = compile(&[&pin, "-c", "a.c"]);
5425 assert_eq!(opts.glibc_minor, Some(28));
5426 let root = cache::dir().join("sysroots").join(format!("{}.2.28", host.tuple()));
5427 let dirs: Vec<&std::path::Path> =
5428 opts.search.dirs().iter().map(|d| d.path.as_path()).collect();
5429 assert!(dirs.iter().any(|dir| dir.starts_with(&root)), "{dirs:?}");
5430 assert!(!dirs.iter().any(|dir| *dir == std::path::Path::new("/usr/include")), "{dirs:?}");
5433 }
5434
5435 fn cross_arch() -> &'static str {
5441 match Triple::host().map(|host| host.arch) {
5442 Some(rucc_target::Arch::X86_64) => "aarch64",
5443 _ => "x86_64",
5444 }
5445 }
5446
5447 #[test]
5448 fn a_glibc_newer_than_the_bundled_tree_is_refused_by_name() {
5449 let target = format!("--target={}-linux-gnu.2.99", cross_arch());
5456 let message = refused(&[&target, "-c", "a.c"]);
5457 assert!(message.contains("asked for glibc 2.99"), "{message}");
5458 assert!(message.contains("bundled headers are glibc 2.44"), "{message}");
5459 assert!(message.contains("--sysroot"), "{message}");
5460 }
5461
5462 #[test]
5463 fn a_sysroot_the_user_named_is_still_what_a_cross_compile_reads() {
5464 let (opts, _) =
5468 compile(&["--target=riscv64-linux-musl", "--sysroot=/nowhere-at-all", "-c", "a.c"]);
5469 let dirs: Vec<&std::path::Path> =
5470 opts.search.dirs().iter().map(|d| d.path.as_path()).collect();
5471 assert_eq!(dirs, [std::path::Path::new(runtime::DIR)]);
5472 }
5473
5474 #[test]
5475 fn dash_i_dash_moves_the_bracket_directories_into_the_quoted_chain() {
5476 let (opts, _) =
5477 compile(&["-Iinc1", "-iquote", "inc2", "-I-", "-Iinc3", "-nostdinc", "a.c"]);
5478 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
5479 assert_eq!(dirs, ["inc1", "inc2", "inc3"]);
5480 assert_eq!(opts.search.start(IncludeForm::Angled), 2);
5482 assert!(!opts.search.searches_current_dir());
5483 }
5484
5485 #[test]
5486 fn the_prefix_flags_stick_what_iprefix_said_on_the_front_of_what_follows_it() {
5487 let (opts, _) = compile(&[
5488 "-iprefix",
5489 "/tools/",
5490 "-iwithprefix",
5491 "late",
5492 "-iwithprefixbefore",
5493 "early",
5494 "-iprefix",
5495 "/other/",
5496 "-iwithprefix",
5497 "last",
5498 "-nostdinc",
5499 "a.c",
5500 ]);
5501 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
5502 assert_eq!(dirs, ["/tools/early", "/tools/late", "/other/last"]);
5505 assert!(!opts.search.dirs()[0].is_system);
5506 assert!(opts.search.dirs()[1].is_system);
5507 }
5508
5509 #[test]
5510 fn the_files_named_on_the_command_line_keep_their_order_and_which_flag_named_them() {
5511 let (opts, _) =
5512 compile(&["-include", "one.h", "-imacros", "two.h", "-include", "3.h", "a.c"]);
5513 let names: Vec<&str> = opts.preincludes.iter().map(|p| p.name.as_str()).collect();
5514 assert_eq!(names, ["one.h", "two.h", "3.h"]);
5515 assert_eq!(opts.preincludes.iter().filter(|p| p.macros_only).count(), 1);
5516 }
5517
5518 #[test]
5519 fn nostdinc_takes_the_compilers_own_headers_off_the_path() {
5520 let (opts, _) = compile(&["-Ii", "-nostdinc", "a.c"]);
5521 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
5522 assert_eq!(dirs, ["i"]);
5523 }
5524
5525 #[test]
5526 fn the_dialect_flags_set_the_language_and_the_extensions_separately() {
5527 let (opts, _) = compile(&["-std=gnu11", "a.c"]);
5528 assert_eq!(opts.std, Std::C11);
5529 assert!(opts.gnu_extensions);
5530
5531 let (opts, _) = compile(&["-std=iso9899:1999", "a.c"]);
5532 assert_eq!(opts.std, Std::C99);
5533 assert!(!opts.gnu_extensions);
5534
5535 let (opts, _) = compile(&["-ansi", "a.c"]);
5536 assert_eq!(opts.std, Std::C89);
5537 assert!(!opts.gnu_extensions);
5538
5539 let (opts, _) = compile(&["-std=gnu2y", "a.c"]);
5540 assert_eq!(opts.std, Std::C2y);
5541 assert!(opts.gnu_extensions);
5542
5543 let e = parse_args(&args(&["-std=c94jr", "a.c"])).unwrap_err();
5544 assert!(e.message.contains("unknown dialect"), "{}", e.message);
5545 }
5546
5547 #[test]
5548 fn the_dump_letters_are_a_family_and_everything_else_beginning_with_d_is_not() {
5549 let (opts, _) = compile(&["-dM", "a.c"]);
5550 assert!(opts.dumps.macros);
5551
5552 let (opts, _) = compile(&["-dDM", "a.c"]);
5555 assert!(opts.dumps.macros);
5556 let (opts, _) = compile(&["-dD", "a.c"]);
5557 assert!(!opts.dumps.macros);
5558
5559 let (opts, _) = compile(&["a.c"]);
5560 assert!(!opts.dumps.any());
5561
5562 assert_eq!(printed(&["-dumpversion", "a.c"]), "16");
5565 }
5566
5567 #[test]
5568 fn the_msvc_runtime_is_a_compile_flag_and_a_link_one() {
5569 let (link, _) = linking(&["a.c"]);
5570 assert_eq!(link.crt, rucc_sysroot::Crt::Static);
5571 let (link, _) = linking(&["-fms-runtime-lib=dll", "a.c"]);
5572 assert_eq!(link.crt, rucc_sysroot::Crt::Dll);
5573 let (opts, _) = compile(&["-fms-runtime-lib=dll", "-c", "a.c"]);
5574 assert!(opts.ms_dll_runtime);
5575 let (opts, _) = compile(&["-fms-runtime-lib=dll", "-fms-runtime-lib=static", "-c", "a.c"]);
5576 assert!(!opts.ms_dll_runtime, "the last one wins");
5577 let e = parse_args(&args(&["-fms-runtime-lib=dll_dbg", "a.c"])).unwrap_err();
5578 assert!(e.message.contains("debug"), "{}", e.message);
5579 let e = parse_args(&args(&["-fms-runtime-lib=shared", "a.c"])).unwrap_err();
5580 assert!(e.message.contains("static or dll"), "{}", e.message);
5581 }
5582
5583 #[test]
5584 fn the_gcc_version_claimed_is_a_flag_and_the_short_spellings_are_the_ones_people_write() {
5585 let (opts, _) = compile(&["a.c"]);
5586 assert_eq!(
5587 opts.gnuc,
5588 GnucVersion { major: 16, minor: 0, patch: 0 },
5589 "the release this compiler is written against, and the earliest one of that series"
5590 );
5591
5592 let (opts, _) = compile(&["-fgnuc-version=15.1.0", "a.c"]);
5593 assert_eq!(opts.gnuc, GnucVersion { major: 15, minor: 1, patch: 0 });
5594
5595 let (opts, _) = compile(&["-fgnuc-version=15", "a.c"]);
5598 assert_eq!(opts.gnuc, GnucVersion { major: 15, minor: 0, patch: 0 });
5599
5600 assert!(opts.gnuc_given, "a version that was written down is one that was given");
5601 assert!(!compile(&["a.c"]).0.gnuc_given);
5602
5603 let (opts, _) = compile(&["-fms-compatibility-version=19.29.30133", "a.c"]);
5604 assert_eq!(opts.msc.msc_ver(), 1929);
5605 assert_eq!(opts.msc.msc_full_ver(), 192_930_133);
5606
5607 let (opts, _) = compile(&["-fgnuc-version=13.2", "a.c"]);
5608 assert_eq!(opts.gnuc, GnucVersion { major: 13, minor: 2, patch: 0 });
5609
5610 let e = parse_args(&args(&["-fgnuc-version=15.x", "a.c"])).unwrap_err();
5611 assert!(e.message.contains("minor that is not a number"), "{}", e.message);
5612
5613 let e = parse_args(&args(&["-fgnuc-version=1.2.3.4", "a.c"])).unwrap_err();
5614 assert!(e.message.contains("more than three"), "{}", e.message);
5615 }
5616
5617 #[test]
5618 fn pedantic_has_two_spellings_and_is_not_the_same_knob_as_the_dialect() {
5619 let (opts, _) = compile(&["-std=c17", "-pedantic", "a.c"]);
5620 assert!(opts.pedantic);
5621 assert_eq!(opts.std, Std::C17);
5622
5623 let (opts, _) = compile(&["-Wpedantic", "a.c"]);
5626 assert!(opts.pedantic);
5627
5628 let (opts, _) = compile(&["-std=c17", "a.c"]);
5629 assert!(!opts.pedantic, "a dialect on its own does not diagnose an extension");
5630 }
5631
5632 #[test]
5633 fn dash_p_and_dash_ffreestanding_reach_the_options() {
5634 let (opts, _) = compile(&["-E", "-P", "-ffreestanding", "a.c"]);
5635 assert!(!opts.line_markers);
5636 assert!(!opts.hosted);
5637 assert_eq!(opts.emit, EmitKind::Preprocessed);
5638 }
5639
5640 #[test]
5647 fn the_builtin_flags_are_read_in_both_directions_and_one_name_at_a_time() {
5648 let (opts, _) = compile(&["-c", "a.c"]);
5649 assert!(opts.builtins, "a library name means the library function by default");
5650 assert!(opts.no_builtin.is_empty());
5651
5652 let (opts, _) = compile(&["-c", "-fno-builtin", "a.c"]);
5653 assert!(!opts.builtins);
5654
5655 let (opts, _) = compile(&["-c", "-fno-builtin", "-fbuiltin", "a.c"]);
5656 assert!(opts.builtins, "the last mention decides");
5657
5658 let (opts, _) = compile(&["-c", "-fno-builtin-memcpy", "-fno-builtin-nonesuch", "a.c"]);
5659 assert!(opts.builtins, "one name is not the family");
5660 assert_eq!(opts.no_builtin, vec!["memcpy".to_owned(), "nonesuch".to_owned()]);
5661 }
5662
5663 #[test]
5671 fn visibility_takes_the_four_spellings_gcc_takes_and_refuses_the_rest() {
5672 let (opts, _) = compile(&["-c", "a.c"]);
5673 assert_eq!(opts.visibility, Visibility::Default, "exported unless something says not");
5674
5675 for (written, wanted) in [
5676 ("default", Visibility::Default),
5677 ("hidden", Visibility::Hidden),
5678 ("internal", Visibility::Hidden),
5679 ("protected", Visibility::Protected),
5680 ] {
5681 let (opts, _) = compile(&["-c", &format!("-fvisibility={written}"), "a.c"]);
5682 assert_eq!(opts.visibility, wanted, "{written}");
5683 }
5684
5685 let (opts, _) = compile(&["-c", "-fvisibility=hidden", "-fvisibility=default", "a.c"]);
5688 assert_eq!(opts.visibility, Visibility::Default, "the last mention decides");
5689
5690 let failed = parse_args(&args(&["-fvisibility=none", "a.c"])).expect_err("refused");
5694 assert!(failed.to_string().contains("is not a visibility"), "{failed}");
5695 }
5696
5697 #[test]
5700 fn how_far_a_multiply_and_an_addition_may_be_fused_is_asked_for() {
5701 let (opts, _) = compile(&["-c", "a.c"]);
5702 assert_eq!(opts.fp_contract, Contract::Off, "a licence nobody granted is not assumed");
5703
5704 for (written, wanted) in
5705 [("off", Contract::Off), ("on", Contract::On), ("fast", Contract::Fast)]
5706 {
5707 let (opts, _) = compile(&["-c", &format!("-ffp-contract={written}"), "a.c"]);
5708 assert_eq!(opts.fp_contract, wanted, "{written}");
5709 }
5710
5711 let (opts, _) = compile(&["-c", "-ffp-contract=fast", "-ffp-contract=off", "a.c"]);
5712 assert_eq!(opts.fp_contract, Contract::Off, "the last mention decides");
5713
5714 for bad in ["-ffp-contract=none", "-ffp-contract=", "-ffp-contract=Fast"] {
5718 let failed = parse_args(&args(&[bad, "a.c"])).expect_err("refused");
5719 assert!(failed.to_string().contains("is not a contraction"), "{bad}: {failed}");
5720 }
5721
5722 let failed = parse_args(&args(&["-fexcess-precision=long", "a.c"])).expect_err("refused");
5726 assert!(failed.to_string().contains("is not an excess precision"), "{failed}");
5727 }
5728
5729 #[test]
5733 fn a_prefix_mapping_flag_goes_on_the_list_its_spelling_names() {
5734 let (opts, _) = compile(&["-c", "a.c"]);
5735 assert!(opts.prefix_map.macros.is_empty(), "nothing is rewritten unless it is asked for");
5736 assert!(opts.prefix_map.debug.is_empty(), "nor here");
5737 assert!(opts.prefix_map.profile.is_empty(), "nor here");
5738
5739 let (opts, _) = compile(&["-c", "-fmacro-prefix-map=/build=.", "a.c"]);
5740 assert_eq!(opts.prefix_map.macros.apply("/build/a.c"), "./a.c", "the one it names");
5741 assert!(opts.prefix_map.debug.is_empty(), "and not the two it does not");
5742
5743 let (opts, _) = compile(&["-c", "-fdebug-prefix-map=/build=.", "a.c"]);
5744 assert_eq!(opts.prefix_map.debug.apply("/build/a.c"), "./a.c", "the one it names");
5745 assert!(opts.prefix_map.macros.is_empty(), "and not the two it does not");
5746
5747 let (opts, _) = compile(&["-c", "-fprofile-prefix-map=/build=.", "a.c"]);
5748 assert_eq!(opts.prefix_map.profile.apply("/build/a.c"), "./a.c", "the one it names");
5749 assert!(opts.prefix_map.macros.is_empty(), "and not the two it does not");
5750
5751 let (opts, _) = compile(&["-c", "-ffile-prefix-map=/build=.", "a.c"]);
5752 for list in [&opts.prefix_map.macros, &opts.prefix_map.debug, &opts.prefix_map.profile] {
5753 assert_eq!(list.apply("/build/a.c"), "./a.c", "all three at once");
5754 }
5755
5756 let (opts, _) =
5760 compile(&["-c", "-ffile-prefix-map=/a=one", "-ffile-prefix-map=/b=two", "a.c"]);
5761 assert_eq!(opts.prefix_map.macros.apply("/a/x.c"), "one/x.c", "the earlier one still acts");
5762 assert_eq!(opts.prefix_map.macros.apply("/b/x.c"), "two/x.c", "and so does the later one");
5763
5764 for bad in ["-fmacro-prefix-map=nope", "-ffile-prefix-map=", "-fdebug-prefix-map=/build"] {
5768 let failed = parse_args(&args(&[bad, "a.c"])).expect_err("refused");
5769 assert!(failed.to_string().contains("is not a rewrite for"), "{bad}: {failed}");
5770 }
5771 }
5772
5773 #[test]
5781 fn a_section_per_function_and_a_section_per_variable_are_asked_for_one_at_a_time() {
5782 let (opts, _) = compile(&["-c", "a.c"]);
5783 assert!(!opts.function_sections, "one text section unless something says otherwise");
5784 assert!(!opts.data_sections);
5785
5786 let (opts, _) = compile(&["-c", "-ffunction-sections", "a.c"]);
5787 assert!(opts.function_sections);
5788 assert!(!opts.data_sections, "one flag is not the other");
5789
5790 let (opts, _) = compile(&["-c", "-fdata-sections", "a.c"]);
5791 assert!(opts.data_sections);
5792 assert!(!opts.function_sections);
5793
5794 let (opts, _) = compile(&[
5797 "-c",
5798 "-ffunction-sections",
5799 "-fno-function-sections",
5800 "-fdata-sections",
5801 "-fno-data-sections",
5802 "a.c",
5803 ]);
5804 assert!(!opts.function_sections, "the last mention decides");
5805 assert!(!opts.data_sections, "the last mention decides");
5806 }
5807
5808 #[test]
5811 fn gnu89_inline_is_off_until_it_is_asked_for_and_the_last_mention_decides() {
5812 let (opts, _) = compile(&["-c", "a.c"]);
5813 assert!(!opts.gnu89_inline, "C's reading of inline by default");
5814
5815 let (opts, _) = compile(&["-c", "-fgnu89-inline", "a.c"]);
5816 assert!(opts.gnu89_inline);
5817
5818 let (opts, _) = compile(&["-c", "-fgnu89-inline", "-fno-gnu89-inline", "a.c"]);
5819 assert!(!opts.gnu89_inline, "the last mention decides");
5820
5821 let (opts, _) = compile(&["-c", "-std=c89", "a.c"]);
5826 assert!(!opts.gnu89_inline);
5827 }
5828
5829 #[test]
5832 fn the_two_frame_flags_are_read_in_both_directions() {
5833 let (opts, _) = compile(&["-c", "a.c"]);
5834 assert_eq!(opts.frame_pointer, None, "nothing said, so the level decides");
5835 assert!(opts.keeps_frame_pointer(), "and at -O0 gcc keeps one, so this does too");
5836 let (opts, _) = compile(&["-c", "-O1", "a.c"]);
5837 assert!(!opts.keeps_frame_pointer(), "gcc omits it above -O0 and so does this");
5838 assert!(opts.red_zone, "the psABI has one and nothing said not to use it");
5839
5840 let (opts, _) = compile(&["-c", "-fno-omit-frame-pointer", "-mno-red-zone", "a.c"]);
5841 assert_eq!(opts.frame_pointer, Some(true));
5842 assert!(!opts.red_zone);
5843
5844 let (opts, _) = compile(&[
5845 "-c",
5846 "-fno-omit-frame-pointer",
5847 "-fomit-frame-pointer",
5848 "-mno-red-zone",
5849 "-mred-zone",
5850 "a.c",
5851 ]);
5852 assert_eq!(opts.frame_pointer, Some(false), "the last one wins, as it does in gcc");
5853 assert!(!opts.keeps_frame_pointer(), "and it wins over the level too");
5854 assert!(opts.red_zone);
5855 }
5856
5857 #[test]
5860 fn the_stack_protector_is_four_flags_and_the_last_one_wins() {
5861 let (opts, _) = compile(&["-c", "a.c"]);
5862 assert_eq!(opts.protector, Protector::None, "gcc protects nothing unless it was asked");
5863
5864 for (flag, want) in [
5865 ("-fstack-protector", Protector::Buffers),
5866 ("-fstack-protector-strong", Protector::Strong),
5867 ("-fstack-protector-all", Protector::All),
5868 ] {
5869 let (opts, _) = compile(&["-c", flag, "a.c"]);
5870 assert_eq!(opts.protector, want, "{flag}");
5871 }
5872
5873 for off in ["-fno-stack-protector", "-fno-stack-protector-strong"] {
5876 let (opts, _) = compile(&["-c", "-fstack-protector-strong", off, "a.c"]);
5877 assert_eq!(opts.protector, Protector::None, "{off}");
5878 }
5879 let (opts, _) = compile(&["-c", "-fno-stack-protector", "-fstack-protector-all", "a.c"]);
5880 assert_eq!(opts.protector, Protector::All, "the last one wins either way round");
5881 }
5882
5883 #[test]
5886 fn taking_a_frame_a_page_at_a_time_is_off_until_it_is_asked_for() {
5887 let (opts, _) = compile(&["-c", "a.c"]);
5888 assert!(!opts.stack_clash, "gcc takes a frame in one subtraction unless it was asked");
5889
5890 let (opts, _) = compile(&["-c", "-fstack-clash-protection", "a.c"]);
5891 assert!(opts.stack_clash);
5892
5893 let (opts, _) =
5896 compile(&["-c", "-fstack-clash-protection", "-fno-stack-clash-protection", "a.c"]);
5897 assert!(!opts.stack_clash);
5898 let (opts, _) =
5899 compile(&["-c", "-fno-stack-clash-protection", "-fstack-clash-protection", "a.c"]);
5900 assert!(opts.stack_clash, "the last one wins either way round");
5901
5902 let (opts, _) =
5904 compile(&["-c", "-fstack-clash-protection", "-fstack-protector-strong", "a.c"]);
5905 assert!(opts.stack_clash);
5906 assert_eq!(opts.protector, Protector::Strong);
5907 }
5908
5909 #[test]
5913 fn which_control_flow_edges_are_checked_is_asked_for_by_name() {
5914 let (opts, _) = compile(&["-c", "a.c"]);
5915 assert_eq!(opts.control, Control::None, "gcc's default on the targets this compiler has");
5916
5917 for (arg, want) in [
5918 ("-fcf-protection", Control::Full),
5919 ("-fcf-protection=full", Control::Full),
5920 ("-fcf-protection=branch", Control::Branch),
5921 ("-fcf-protection=return", Control::Return),
5922 ("-fcf-protection=none", Control::None),
5923 ("-fcf-protection=check", Control::Check),
5924 ] {
5925 let (opts, _) = compile(&["-c", arg, "a.c"]);
5926 assert_eq!(opts.control, want, "{arg}");
5927 }
5928
5929 let (opts, _) = compile(&["-c", "-fcf-protection=full", "-fno-cf-protection", "a.c"]);
5932 assert_eq!(opts.control, Control::None);
5933 let (opts, _) = compile(&["-c", "-fno-cf-protection", "-fcf-protection=branch", "a.c"]);
5934 assert_eq!(opts.control, Control::Branch, "the last one wins either way round");
5935 }
5936
5937 #[test]
5947 fn the_profiler_and_where_its_hook_goes_are_two_separate_questions() {
5948 let (opts, _) = compile(&["-c", "a.c"]);
5949 assert!(!opts.profile);
5950 assert_eq!(opts.hook, Hook::Platform, "neither was named, so the target decides");
5951
5952 for arg in ["-pg", "-p"] {
5953 let (opts, _) = compile(&["-c", arg, "a.c"]);
5954 assert!(opts.profile, "{arg}");
5955 let (link, _) = linking(&[arg, "a.c"]);
5956 assert!(link.profile, "{arg} changes the link as well");
5957 }
5958
5959 for (arg, want) in [("-mfentry", Hook::Early), ("-mno-fentry", Hook::Late)] {
5960 let (opts, _) = compile(&["-c", arg, "a.c"]);
5961 assert_eq!(opts.hook, want, "{arg}");
5962 assert!(!opts.profile, "{arg} asks for no call of its own");
5963 }
5964
5965 let (opts, _) = compile(&["-c", "-mfentry", "-mno-fentry", "-pg", "a.c"]);
5966 assert_eq!(opts.hook, Hook::Late, "the last one wins");
5967 assert!(opts.profile);
5968 }
5969
5970 #[test]
5976 fn the_room_a_patcher_is_promised_is_a_number_of_bytes_and_where_they_go() {
5977 let (opts, _) = compile(&["-c", "a.c"]);
5978 assert_eq!(opts.patchable, Patchable::default());
5979 assert!(!opts.patchable.any(), "nothing is reserved unless it was asked for");
5980
5981 let (opts, _) = compile(&["-c", "-fpatchable-function-entry=16", "a.c"]);
5982 assert_eq!(opts.patchable, Patchable { total: 16, before: 0 });
5983
5984 let (opts, _) = compile(&["-c", "-fpatchable-function-entry=5,3", "a.c"]);
5985 assert_eq!(opts.patchable, Patchable { total: 5, before: 3 });
5986 assert_eq!(opts.patchable.after(), 2);
5987
5988 let (opts, _) = compile(&[
5991 "-c",
5992 "-fpatchable-function-entry=5,3",
5993 "-fpatchable-function-entry=2",
5994 "a.c",
5995 ]);
5996 assert_eq!(opts.patchable, Patchable { total: 2, before: 0 });
5997 }
5998
5999 #[test]
6001 fn room_in_front_of_the_label_that_is_more_than_the_room_asked_for_is_refused() {
6002 for arg in ["-fpatchable-function-entry=1,2", "-fpatchable-function-entry=x"] {
6003 let e = parse_args(&args(&["-c", arg, "a.c"])).unwrap_err();
6004 assert!(e.message.contains("is not an amount of room to reserve"), "{}", e.message);
6005 }
6006 }
6007
6008 #[test]
6014 fn what_overflows_rather_than_being_undefined_is_asked_for_two_ways() {
6015 let (opts, _) = compile(&["-c", "a.c"]);
6016 assert_eq!(opts.wrapping, Wrapping::NONE, "nothing wraps unless it was asked for");
6017
6018 let (opts, _) = compile(&["-c", "-fwrapv", "a.c"]);
6019 assert_eq!(opts.wrapping, Wrapping { signed: true, pointer: false, trap: false });
6020
6021 let (opts, _) = compile(&["-c", "-fwrapv-pointer", "a.c"]);
6022 assert_eq!(opts.wrapping, Wrapping { signed: false, pointer: true, trap: false });
6023
6024 let (opts, _) = compile(&["-c", "-fno-strict-overflow", "a.c"]);
6025 assert_eq!(opts.wrapping, Wrapping::ALL);
6026
6027 let (opts, _) = compile(&["-c", "-fwrapv", "-fno-wrapv", "a.c"]);
6031 assert_eq!(opts.wrapping, Wrapping::NONE);
6032
6033 let (opts, _) = compile(&["-c", "-fno-strict-overflow", "-fstrict-overflow", "a.c"]);
6034 assert_eq!(opts.wrapping, Wrapping::NONE);
6035
6036 let (opts, _) = compile(&["-c", "-fno-strict-overflow", "-fno-wrapv-pointer", "a.c"]);
6037 assert_eq!(opts.wrapping, Wrapping { signed: true, pointer: false, trap: false });
6038 }
6039
6040 #[test]
6047 fn a_signed_overflow_that_stops_is_the_other_answer_and_not_a_third_one() {
6048 let (opts, _) = compile(&["-c", "-ftrapv", "a.c"]);
6049 assert_eq!(opts.wrapping, Wrapping { signed: false, pointer: false, trap: true });
6050
6051 let (opts, _) = compile(&["-c", "-fwrapv", "-ftrapv", "a.c"]);
6052 assert_eq!(opts.wrapping, Wrapping { signed: false, pointer: false, trap: true });
6053
6054 let (opts, _) = compile(&["-c", "-ftrapv", "-fwrapv", "a.c"]);
6055 assert_eq!(opts.wrapping, Wrapping { signed: true, pointer: false, trap: false });
6056
6057 let (opts, _) = compile(&["-c", "-ftrapv", "-fno-strict-overflow", "a.c"]);
6058 assert_eq!(opts.wrapping, Wrapping::ALL);
6059
6060 let (opts, _) = compile(&["-c", "-ftrapv", "-fno-trapv", "a.c"]);
6061 assert_eq!(opts.wrapping, Wrapping::NONE);
6062
6063 let (opts, _) = compile(&["-c", "-ftrapv", "-fstrict-overflow", "a.c"]);
6066 assert_eq!(opts.wrapping, Wrapping { signed: false, pointer: false, trap: true });
6067 }
6068
6069 #[test]
6077 fn the_signedness_of_a_plain_char_is_asked_for_in_four_ways() {
6078 let (opts, _) = compile(&["-c", "a.c"]);
6079 assert_eq!(opts.char_signed, None);
6080
6081 for flag in ["-fsigned-char", "-fno-unsigned-char"] {
6082 let (opts, _) = compile(&["-c", flag, "a.c"]);
6083 assert_eq!(opts.char_signed, Some(true), "{flag}");
6084 }
6085
6086 for flag in ["-funsigned-char", "-fno-signed-char"] {
6087 let (opts, _) = compile(&["-c", flag, "a.c"]);
6088 assert_eq!(opts.char_signed, Some(false), "{flag}");
6089 }
6090
6091 let (opts, _) = compile(&["-c", "-funsigned-char", "-fsigned-char", "a.c"]);
6094 assert_eq!(opts.char_signed, Some(true));
6095
6096 let (opts, _) =
6100 compile(&["-c", "--target=aarch64-unknown-linux-gnu", "-fsigned-char", "a.c"]);
6101 assert!(Session::new(*opts).target.char_is_signed);
6102 let (opts, _) = compile(&["-c", "--target=aarch64-unknown-linux-gnu", "a.c"]);
6103 assert!(!Session::new(*opts).target.char_is_signed);
6104 }
6105
6106 #[test]
6108 fn the_smallest_enumeration_is_asked_for_and_taken_back() {
6109 let (opts, _) = compile(&["-c", "a.c"]);
6110 assert!(!opts.short_enums);
6111
6112 let (opts, _) = compile(&["-c", "-fshort-enums", "a.c"]);
6113 assert!(opts.short_enums);
6114
6115 let (opts, _) = compile(&["-c", "-fshort-enums", "-fno-short-enums", "a.c"]);
6116 assert!(!opts.short_enums);
6117
6118 let (opts, _) = compile(&["-c", "-fno-short-enums", "-fshort-enums", "a.c"]);
6119 assert!(opts.short_enums);
6120 }
6121
6122 #[test]
6127 fn the_microsoft_reading_of_a_member_follows_the_target_until_it_is_asked_for() {
6128 let (opts, _) = compile(&[LINUX, "-c", "a.c"]);
6131 assert!(!Session::new(*opts).ms_extensions());
6132
6133 let (opts, _) = compile(&["-c", "--target=x86_64-pc-windows-gnu", "a.c"]);
6134 assert!(Session::new(*opts).ms_extensions());
6135
6136 let (opts, _) = compile(&["-c", "-fms-extensions", "a.c"]);
6137 assert!(Session::new(*opts).ms_extensions());
6138
6139 let (opts, _) =
6140 compile(&["-c", "--target=x86_64-pc-windows-gnu", "-fno-ms-extensions", "a.c"]);
6141 assert!(!Session::new(*opts).ms_extensions());
6142 }
6143
6144 #[test]
6150 fn a_control_flow_protection_nothing_means_is_refused() {
6151 let e = parse_args(&args(&["-c", "-fcf-protection=all", "a.c"])).unwrap_err();
6152 assert!(e.message.contains("is not a control flow protection"), "{}", e.message);
6153 assert!(e.message.contains("full, branch, return, none or check"), "{}", e.message);
6154 }
6155
6156 #[test]
6157 fn mingw_subsystem_and_unicode_flags_are_taken_last_one_winning() {
6158 let (link, _) = linking(&["-mwindows", "-municode", "-mthreads", "-static-libgcc", "a.c"]);
6159 assert!(link.gui && link.unicode);
6160 let (link, _) = linking(&["-mwindows", "-mconsole", "a.c"]);
6161 assert!(!link.gui);
6162 let (opts, _) = compile(&["-municode", "-c", "a.c"]);
6163 assert!(opts.defines.iter().any(|define| define == "UNICODE"), "{:?}", opts.defines);
6164 }
6165
6166 #[test]
6167 fn the_link_flags_are_collected_apart_from_the_compilation() {
6168 let (link, _) = linking(&[
6169 "-static",
6170 "-nostartfiles",
6171 "-rdynamic",
6172 "-s",
6173 "-fuse-ld=mold",
6174 "-L/opt/lib",
6175 "-B",
6176 "/opt/tools",
6177 "a.c",
6178 ]);
6179 assert!(link.is_static);
6180 assert!(link.no_startfiles);
6181 assert!(link.export_dynamic);
6182 assert!(link.strip);
6183 assert_eq!(link.use_ld.as_deref(), Some("mold"));
6184 assert_eq!(link.search, vec![PathBuf::from("/opt/lib")]);
6185 assert_eq!(link.prefixes, vec![PathBuf::from("/opt/tools")]);
6186 }
6187
6188 #[test]
6189 fn a_mingw_link_names_its_output_the_way_mingw_gcc_does() {
6190 let mingw = "--target=x86_64-windows-gnu";
6193 let (_, plan) = linking(&[mingw, "a.c", "-o", "foo"]);
6194 assert_eq!(plan.link.expect("expected a link step").output, "foo.exe");
6195 let (_, plan) = linking(&[mingw, "-shared", "a.c", "-o", "x"]);
6196 assert_eq!(plan.link.expect("expected a link step").output, "x.exe");
6197 let (_, plan) = linking(&[mingw, "-shared", "a.c", "-o", "x.dll"]);
6198 assert_eq!(plan.link.expect("expected a link step").output, "x.dll");
6199 let (_, plan) = compile(&[mingw, "-c", "a.c", "-o", "x"]);
6200 assert!(plan.link.is_none());
6201 assert_eq!(plan.jobs[0].output, Output::File("x".into()));
6202 let (_, plan) = linking(&[LINUX, "a.c", "-o", "foo"]);
6203 assert_eq!(plan.link.expect("expected a link step").output, "foo");
6204 }
6205
6206 #[test]
6207 fn a_comma_in_dash_wl_separates_two_arguments() {
6208 let (_, plan) = linking(&[LINUX, "-Wl,-rpath,/opt/lib", "-Xlinker", "--as-needed", "a.c"]);
6212 let link = plan.link.expect("expected a link step");
6213 assert_eq!(
6214 link.inputs,
6215 vec![
6216 link::Item::Linker("-rpath".into()),
6217 link::Item::Linker("/opt/lib".into()),
6218 link::Item::Linker("--as-needed".into()),
6219 link::Item::File("a.o".into()),
6220 ]
6221 );
6222 }
6223
6224 #[test]
6225 fn a_word_for_the_linker_keeps_its_place_among_the_files_too() {
6226 let (_, plan) = linking(&[
6230 "--target=x86_64-unknown-linux-gnu",
6231 "a.c",
6232 "-Wl,--whole-archive",
6233 "libaesni.a",
6234 "-Wl,--no-whole-archive",
6235 "-lm",
6236 ]);
6237 let link = plan.link.expect("expected a link step");
6238 assert_eq!(
6239 link.inputs,
6240 vec![
6241 link::Item::File("a.o".into()),
6242 link::Item::Linker("--whole-archive".into()),
6243 link::Item::File("libaesni.a".into()),
6244 link::Item::Linker("--no-whole-archive".into()),
6245 link::Item::Library("m".into()),
6246 ]
6247 );
6248 assert_eq!(plan.jobs.len(), 2);
6250 }
6251
6252 #[test]
6253 fn a_word_for_the_linker_on_a_dash_c_line_is_dropped_without_a_word() {
6254 let (_, plan) = linking(&["-c", "-Wl,--as-needed", "a.c"]);
6258 assert!(plan.link.is_none());
6259 assert!(plan.notes.is_empty(), "{:?}", plan.notes);
6260 assert_eq!(plan.jobs.len(), 1);
6261 }
6262
6263 #[test]
6264 fn a_library_keeps_its_place_between_the_objects() {
6265 let (_, plan) = linking(&["--target=x86_64-unknown-linux-gnu", "a.c", "-lm", "b.c"]);
6270 let link = plan.link.expect("expected a link step");
6271 assert_eq!(
6272 link.inputs,
6273 vec![
6274 link::Item::File("a.o".into()),
6275 link::Item::Library("m".into()),
6276 link::Item::File("b.o".into()),
6277 ]
6278 );
6279 assert_eq!(plan.jobs.len(), 2);
6281 }
6282
6283 #[test]
6284 fn a_library_on_a_dash_c_line_is_a_note_rather_than_an_error() {
6285 let (_, plan) = linking(&["-c", "-lm", "a.c"]);
6286 assert!(plan.link.is_none());
6287 assert!(plan.notes.iter().any(|n| n.contains("-lm")), "{:?}", plan.notes);
6288 }
6289
6290 #[test]
6291 fn the_sysroot_reaches_the_linker_as_well_as_the_headers() {
6292 let (link, _) = linking(&["--sysroot=/opt/root", "a.c"]);
6293 assert_eq!(link.sysroot, Some(PathBuf::from("/opt/root")));
6294 }
6295
6296 fn printed(s: &[&str]) -> String {
6297 match parse_args(&args(s)).expect("expected an answer") {
6298 Action::Print(line) => line,
6299 other => panic!("expected an answer, got {other:?}"),
6300 }
6301 }
6302
6303 fn refused(s: &[&str]) -> String {
6304 parse_args(&args(s)).expect_err("expected a refusal").message
6305 }
6306
6307 #[test]
6308 fn a_warning_flag_gcc_knows_is_taken_even_though_nothing_reads_it() {
6309 let (opts, _) = compile(&["-Wall", "-Wextra", "-Wno-format-truncation", "-c", "a.c"]);
6313 assert!(!opts.warnings_are_errors);
6314 assert!(opts.warnings);
6315 let (opts, _) = compile(&["-Werror", "-c", "a.c"]);
6317 assert!(opts.warnings_are_errors);
6318 let (opts, _) = compile(&["-w", "-c", "a.c"]);
6319 assert!(!opts.warnings);
6320 let (opts, _) = compile(&["-c", "a.c"]);
6322 assert!(!opts.system_header_warnings);
6323 let (opts, _) = compile(&["-Wsystem-headers", "-c", "a.c"]);
6324 assert!(opts.system_header_warnings);
6325 let (opts, _) = compile(&["-Wsystem-headers", "-Wno-system-headers", "-c", "a.c"]);
6326 assert!(!opts.system_header_warnings);
6327 let (opts, _) = compile(&["-pedantic-errors", "-c", "a.c"]);
6328 assert!(opts.pedantic && opts.warnings_are_errors);
6329 }
6330
6331 #[test]
6332 fn a_warning_flag_gcc_refuses_is_refused_here_too() {
6333 for flag in ["-Wcast-function-type-strict", "-Wunused-command-line-argument"] {
6336 assert_eq!(refused(&[flag, "-c", "a.c"]), format!("unknown option `{flag}`"));
6337 }
6338 assert_eq!(
6339 refused(&["-Werror=unguarded-availability-new", "-c", "a.c"]),
6340 "`-Werror=unguarded-availability-new`: no option `-Wunguarded-availability-new`"
6341 );
6342 assert!(refused(&["-Wno-error=nonsense", "-c", "a.c"]).contains("no option `-Wnonsense`"));
6343 for flag in
6346 ["-Wno-cast-function-type-strict", "-Werror=format", "-Wformat=2", "-Wabi-tag", "-W"]
6347 {
6348 compile(&[flag, "-c", "a.c"]);
6349 }
6350 }
6351
6352 #[test]
6353 fn an_argument_for_a_separate_tool_is_refused_rather_than_dropped() {
6354 assert!(refused(&["-Wa,--noexecstack", "-c", "a.c"]).contains("separate assembler"));
6356 assert!(refused(&["-Wp,-C", "-c", "a.c"]).contains("separate assembler"));
6357 assert!(refused(&["-specs=/x", "a.c"]).contains("-specs= is not supported"));
6358 assert!(refused(&["-mcmodel=kernel", "-c", "a.c"]).contains("small code model"));
6359 assert!(refused(&["-gdwarf-4", "-c", "a.c"]).contains("DWARF 5"));
6360 let no32 = refused(&["--target=x86_64-unknown-linux-gnu", "-m32", "-c", "a.c"]);
6363 assert!(no32.contains("32 bit target"), "{no32}");
6364 }
6365
6366 #[test]
6375 fn the_shape_of_the_debug_output_is_recorded_even_where_there_is_none_of_it() {
6376 let (opts, _) = compile(&["-c", "a.c"]);
6377 assert_eq!(opts.compress, Compress::None, "uncompressed unless somebody asks");
6378
6379 assert_eq!(compile(&["-gz", "-c", "a.c"]).0.compress, Compress::Zlib);
6382 for (spelling, want) in [
6383 ("none", Compress::None),
6384 ("zlib", Compress::Zlib),
6385 ("zlib-gnu", Compress::ZlibGnu),
6386 ("zstd", Compress::Zstd),
6387 ] {
6388 let (opts, _) = compile(&[&format!("-gz={spelling}"), "-c", "a.c"]);
6389 assert_eq!(opts.compress, want, "{spelling}");
6390 }
6391
6392 for bad in ["-gz=gzip", "-gz="] {
6396 let failed = refused(&[bad, "-c", "a.c"]);
6397 assert!(failed.contains("is not a way to compress"), "{bad}: {failed}");
6398 }
6399
6400 let (opts, _) = compile(&["-gno-split-dwarf", "-g", "-c", "a.c"]);
6404 assert!(opts.debug_info, "the negative spelling says nothing about how much");
6405 let failed = refused(&["-gsplit-dwarf", "-c", "a.c"]);
6406 assert!(failed.contains(".dwo"), "the refusal names the file it would have written");
6407 }
6408
6409 #[test]
6415 fn the_link_time_family_is_read_and_checked_and_nothing_is_done_about_it() {
6416 let (opts, _) = compile(&["-c", "a.c"]);
6417 assert!(!opts.lto.requested, "nothing asks unless the command line does");
6418
6419 let (opts, _) = compile(&["-flto", "-c", "a.c"]);
6420 assert!(opts.lto.requested);
6421 assert_eq!(opts.lto.jobs, LtoJobs::One, "bare -flto is one process, the way gcc reads it");
6422
6423 assert!(!compile(&["-flto", "-fno-lto", "-c", "a.c"]).0.lto.requested);
6425 assert!(compile(&["-fno-lto", "-flto", "-c", "a.c"]).0.lto.requested);
6426
6427 for (spelling, want) in [
6429 ("auto", LtoJobs::Auto),
6430 ("jobserver", LtoJobs::Jobserver),
6431 ("1", LtoJobs::One),
6432 ("8", LtoJobs::Count(8)),
6433 ] {
6434 let (opts, _) = compile(&[&format!("-flto={spelling}"), "-c", "a.c"]);
6435 assert_eq!(opts.lto.jobs, want, "{spelling}");
6436 assert!(opts.lto.requested, "{spelling} asks for it too");
6437 }
6438
6439 for bad in ["-flto=0", "-flto=thin", "-flto=full", "-flto=-1"] {
6443 let failed = refused(&[bad, "-c", "a.c"]);
6444 assert!(failed.contains("link time jobs"), "{bad}: {failed}");
6445 }
6446
6447 assert_eq!(compile(&["-c", "a.c"]).0.lto.partition, Partition::Balanced, "gcc's default");
6449 for (spelling, want) in [
6450 ("balanced", Partition::Balanced),
6451 ("1to1", Partition::OneToOne),
6452 ("one", Partition::One),
6453 ("max", Partition::Max),
6454 ("none", Partition::None),
6455 ] {
6456 let (opts, _) = compile(&[&format!("-flto-partition={spelling}"), "-c", "a.c"]);
6457 assert_eq!(opts.lto.partition, want, "{spelling}");
6458 }
6459 assert!(refused(&["-flto-partition=big", "-c", "a.c"]).contains("partitioning model"));
6460
6461 assert_eq!(compile(&["-c", "a.c"]).0.lto.compression, None, "whatever it does by default");
6464 assert_eq!(compile(&["-flto-compression-level=0", "-c", "a.c"]).0.lto.compression, Some(0));
6465 let (opts, _) = compile(&["-flto-compression-level=19", "-c", "a.c"]);
6466 assert_eq!(opts.lto.compression, Some(19));
6467 for bad in ["-flto-compression-level=20", "-flto-compression-level=-1"] {
6468 let failed = refused(&[bad, "-c", "a.c"]);
6469 assert!(failed.contains("compression level"), "{bad}: {failed}");
6470 }
6471
6472 for taken in [
6477 "-ffat-lto-objects",
6478 "-fno-fat-lto-objects",
6479 "-fuse-linker-plugin",
6480 "-fno-use-linker-plugin",
6481 ] {
6482 let (opts, _) = compile(&[taken, "-c", "a.c"]);
6483 assert!(!opts.lto.requested, "{taken} says nothing about whether to do it");
6484 }
6485 }
6486
6487 #[test]
6494 fn reading_a_profile_is_taken_and_writing_one_is_refused() {
6495 let (opts, _) = compile(&["-c", "a.c"]);
6496 assert!(!opts.profile_data.requested, "nothing asks unless the command line does");
6497 assert_eq!(opts.profile_data.path, None);
6498
6499 let (opts, _) = compile(&["-fprofile-use", "-c", "a.c"]);
6500 assert!(opts.profile_data.requested);
6501 assert_eq!(opts.profile_data.path, None, "beside the object, the way gcc looks");
6502
6503 let (opts, _) = compile(&["-fprofile-use=/counts", "-c", "a.c"]);
6504 assert!(opts.profile_data.requested, "naming a path asks for it too");
6505 assert_eq!(opts.profile_data.path.as_deref(), Some("/counts"));
6506
6507 assert!(
6509 !compile(&["-fprofile-use", "-fno-profile-use", "-c", "a.c"]).0.profile_data.requested
6510 );
6511 assert!(
6512 compile(&["-fno-profile-use", "-fprofile-use", "-c", "a.c"]).0.profile_data.requested
6513 );
6514
6515 let (opts, _) = compile(&[
6518 "-fprofile-dir=/build/profiles",
6519 "-fprofile-abs-path",
6520 "-fprofile-correction",
6521 "-fprofile-partial-training",
6522 "-c",
6523 "a.c",
6524 ]);
6525 assert_eq!(opts.profile_data.dir.as_deref(), Some("/build/profiles"));
6526 assert!(opts.profile_data.absolute);
6527 assert!(opts.profile_data.correction);
6528 assert!(opts.profile_data.partial_training);
6529
6530 for writing in [
6534 "-fprofile-generate",
6535 "-fprofile-generate=/build/profiles",
6536 "-fprofile-arcs",
6537 "--coverage",
6538 "-fcondition-coverage",
6539 "-fpath-coverage",
6540 ] {
6541 let failed = refused(&[writing, "-c", "a.c"]);
6542 assert!(failed.contains("instrument"), "{writing}: {failed}");
6543 }
6544 assert!(refused(&["-ftest-coverage", "-c", "a.c"]).contains(".gcno"), "it names the file");
6545
6546 for taken in ["-fno-profile-generate", "-fno-profile-arcs", "-fno-test-coverage"] {
6548 let (opts, _) = compile(&[taken, "-c", "a.c"]);
6549 assert!(!opts.profile_data.requested, "{taken} asks for nothing");
6550 }
6551
6552 for taken in [
6556 "-fprofile-update=single",
6557 "-fprofile-update=atomic",
6558 "-fprofile-update=prefer-atomic",
6559 "-fprofile-reproducible=serial",
6560 "-fprofile-reproducible=parallel-runs",
6561 "-fprofile-reproducible=multithreaded",
6562 "-fprofile-values",
6563 "-fno-profile-values",
6564 "-fprofile-info-section",
6565 "-fprofile-filter-files=a.c",
6566 "-fprofile-exclude-files=b.c",
6567 "-fprofile-note=a.gcno",
6568 ] {
6569 let (opts, _) = compile(&[taken, "-c", "a.c"]);
6570 assert!(!opts.profile_data.requested, "{taken} says nothing about reading one");
6571 }
6572 assert!(refused(&["-fprofile-update=none", "-c", "a.c"]).contains("update method"));
6573 assert!(refused(&["-fprofile-reproducible=any", "-c", "a.c"]).contains("reproducibility"));
6574 }
6575
6576 #[test]
6583 fn a_sanitizer_that_is_still_asked_for_at_the_end_of_the_line_is_refused_by_name() {
6584 for asked in ["address", "undefined", "thread", "kernel-address", "leak", "memory"] {
6585 let failed = refused(&[&format!("-fsanitize={asked}"), "-c", "a.c"]);
6586 assert!(failed.contains(asked), "the refusal names what was asked for: {failed}");
6587 assert!(failed.contains("-fsafety=detect"), "and the nearest thing: {failed}");
6588 }
6589
6590 let failed = refused(&["-fsanitize=address,undefined", "-c", "a.c"]);
6592 assert!(failed.contains("address"), "{failed}");
6593
6594 for bad in ["-fsanitize=bogus", "-fsanitize=address,bogus", "-fno-sanitize=bogus"] {
6597 let failed = refused(&[bad, "-c", "a.c"]);
6598 assert!(failed.contains("is not a sanitizer"), "{bad}: {failed}");
6599 }
6600
6601 assert!(refused(&["-fsanitize=all", "-c", "a.c"]).contains("only `-fno-sanitize=all`"));
6603
6604 for pair in [
6608 ["-fsanitize=address", "-fno-sanitize=address"],
6609 ["-fsanitize=address,undefined", "-fno-sanitize=all"],
6610 ["-fsanitize=undefined", "-fno-sanitize=undefined"],
6611 ] {
6612 let (opts, _) = compile(&[pair[0], pair[1], "-c", "a.c"]);
6613 assert_eq!(opts.safety, rucc_session::Safety::Off, "{pair:?} asked for nothing");
6614 }
6615 assert!(!refused(&["-fno-sanitize=address", "-fsanitize=address", "-c", "a.c"]).is_empty());
6617
6618 for taken in [
6621 "-fsanitize-recover=undefined",
6622 "-fno-sanitize-recover=all",
6623 "-fsanitize-trap=undefined",
6624 "-fno-sanitize-trap=all",
6625 "-fsanitize-undefined-trap-on-error",
6626 "-fsanitize-address-use-after-scope",
6627 "-fno-sanitize-address-use-after-scope",
6628 "-fsanitize-sections=.data",
6629 ] {
6630 let (opts, _) = compile(&[taken, "-c", "a.c"]);
6631 assert_eq!(opts.safety, rucc_session::Safety::Off, "{taken} asks for no checking");
6632 }
6633 assert!(refused(&["-fsanitize-recover=bogus", "-c", "a.c"]).contains("is not a sanitizer"));
6634
6635 let failed = refused(&["-fsanitize-coverage=trace-pc", "-c", "a.c"]);
6638 assert!(failed.contains("feedback"), "{failed}");
6639 let failed = refused(&["-fsanitize-coverage=trace-pc-guard", "-c", "a.c"]);
6640 assert!(failed.contains("trace-pc or trace-cmp"), "gcc takes two of them: {failed}");
6641 }
6642
6643 #[test]
6644 fn the_levels_gcc_spells_differently_are_the_levels_they_mean() {
6645 assert_eq!(compile(&["-O", "-c", "a.c"]).0.opt_level, OptLevel::O1);
6646 assert_eq!(compile(&["-Og", "-c", "a.c"]).0.opt_level, OptLevel::O1);
6647 assert_eq!(compile(&["-O2", "-c", "a.c"]).0.opt_level, OptLevel::O2);
6648 }
6649
6650 #[test]
6651 fn the_machine_flags_that_name_what_we_already_do_are_taken_and_the_rest_are_not() {
6652 let line = ["--target=x86_64-unknown-linux-gnu", "-m64", "-march=x86-64-v3"];
6653 let (opts, _) =
6654 compile(&[&line[..], &["-mtune=native", "-mabi=sysv", "-c", "a.c"]].concat());
6655 assert_eq!(opts.target.to_string(), "x86_64-unknown-linux-gnu");
6656 let wrong = refused(&["--target=x86_64-unknown-linux-gnu", "-mabi=ms", "-c", "a.c"]);
6657 assert!(wrong.contains("sysv convention"), "{wrong}");
6658 }
6659
6660 fn has(line: &[&str], name: &str) -> bool {
6662 let x86 = ["--target=x86_64-unknown-linux-gnu", "-c", "a.c"];
6663 let (opts, _) = compile(&[&x86[..], line].concat());
6664 opts.isa.has(rucc_target::Feature::named(name).expect("a feature"))
6665 }
6666
6667 #[test]
6668 fn the_sse_flags_and_the_processor_levels_name_extensions() {
6669 assert!(has(&["-msse4.2"], "sse4.2") && has(&["-msse4.2"], "crc32"));
6672 assert!(has(&["-msse4.2"], "ssse3") && has(&["-msse4.2"], "popcnt"));
6673 assert!(!has(&[], "sse3") && !has(&[], "popcnt"));
6674 assert!(has(&["-mssse3"], "sse3") && !has(&["-mssse3"], "sse4.1"));
6675 assert!(has(&["-msse4"], "sse4.2") && !has(&["-msse4", "-mno-sse4"], "sse4.1"));
6676 assert!(has(&["-mpopcnt"], "popcnt") && !has(&["-mpopcnt"], "sse3"));
6677 assert!(has(&["-mcrc32"], "crc32"));
6678 assert!(has(&["-mxsave"], "xsave") && !has(&["-mxsave", "-mno-xsave"], "xsave"));
6679 assert!(!has(&["-msse4.2", "-mno-popcnt"], "popcnt"));
6680 assert!(has(&["-march=x86-64-v2"], "sse4.2"));
6682 assert!(!has(&["-march=x86-64-v2", "-mno-sse4.2"], "sse4.2"));
6683 assert!(!has(&["-mno-sse4.2", "-march=x86-64-v2"], "sse4.2"));
6684 assert!(has(&["-mno-sse4.2", "-march=x86-64-v2"], "sse4.1"));
6685 assert!(!has(&["-march=x86-64-v2", "-march=x86-64"], "sse3"));
6686 assert!(!has(&["-march=pentium-m"], "sse3"));
6688 assert!(has(&["-march=x86-64-v3"], "avx2"));
6689 assert!(!has(&["-mno-avx512f"], "avx512f"));
6691 }
6692
6693 #[test]
6694 fn an_extension_this_compiler_cannot_provide_for_a_whole_unit_is_refused() {
6695 let x86 = ["--target=x86_64-unknown-linux-gnu", "-c", "a.c"];
6696 let said = refused(&[&x86[..], &["-mavx2"]].concat());
6697 assert!(said.contains("no intrinsics for avx2"), "{said}");
6698 let said = refused(&[&x86[..], &["-mno-sse2"]].concat());
6699 assert!(said.contains("baseline"), "{said}");
6700 assert!(refused(&[&x86[..], &["-msse5"]].concat()).contains("unknown option"));
6701 let said = refused(&["-msse4.2", "--target=aarch64-linux-gnu", "-c", "a.c"]);
6703 assert!(said.contains("unknown option `-msse4.2`"), "{said}");
6704 let (opts, _) = compile(&["--target=riscv64-linux-gnu", "-march=rv64gc", "-c", "a.c"]);
6705 assert_eq!(opts.isa, rucc_target::Isa::NONE);
6706 }
6707
6708 #[test]
6713 fn the_aarch64_march_decides_the_crc_extension() {
6714 let crc = |line: &[&str]| {
6715 let args = [&["--target=aarch64-linux-gnu", "-c", "a.c"][..], line].concat();
6716 let (opts, _) = compile(&args);
6717 opts.isa.has(rucc_target::Feature::aarch64("crc").expect("a feature"))
6718 };
6719 assert!(crc(&["-march=armv8-a+crc"]));
6720 assert!(crc(&["-march=armv8-a+crc+simd"]));
6721 assert!(crc(&["-march=armv8.1-a"]));
6722 assert!(crc(&["-march=armv9-a"]));
6723 assert!(!crc(&[]));
6724 assert!(!crc(&["-march=armv8-a"]));
6725 assert!(!crc(&["-march=armv8-a+simd"]));
6726 assert!(!crc(&["-march=armv8.2-a+nocrc"]));
6727 assert!(!crc(&["-march=armv8-a+crc", "-march=armv8-a"]));
6729 assert!(crc(&["-march=armv8-a", "-march=armv8-a+crc"]));
6730 let (opts, _) = compile(&["-march=armv8-a+crc", "--target=aarch64-linux-gnu", "-c", "a.c"]);
6732 assert!(opts.isa.has(rucc_target::Feature::aarch64("crc").expect("a feature")));
6733 }
6734
6735 #[test]
6736 fn the_thread_flag_is_a_macro_and_a_library_and_the_library_goes_last() {
6737 let (opts, plan) = compile(&["-pthread", "-c", "a.c"]);
6738 assert!(opts.defines.iter().any(|d| d == "_REENTRANT"));
6739 let names: Vec<&str> = plan.jobs.iter().map(|j| j.input.as_str()).collect();
6742 assert_eq!(names, vec!["a.c"]);
6743 }
6744
6745 #[test]
6746 fn the_version_banner_keeps_our_first_line_and_takes_meson_down_the_gnu_path() {
6747 let text = banner();
6748 let mut lines = text.lines();
6749 assert_eq!(lines.next(), Some(format!("rucc {VERSION}").as_str()));
6751 assert!(text.contains("Free Software Foundation"), "{text}");
6753 assert!(lines.next().is_some_and(|l| l.contains("GCC 16")), "{text}");
6755 assert!(lines.next().is_some() && lines.next().is_none(), "{text}");
6756 }
6757
6758 #[test]
6759 fn the_questions_a_build_system_asks_before_it_compiles_anything() {
6760 let target = "--target=x86_64-unknown-linux-gnu";
6761 assert_eq!(printed(&[target, "-dumpmachine"]), "x86_64-unknown-linux-gnu");
6762 assert_eq!(printed(&[target, "-dumpversion"]), "16");
6763 assert_eq!(printed(&[target, "-dumpfullversion"]), "16.0.0");
6764 assert_eq!(printed(&[target, "-fgnuc-version=15.2", "-dumpversion"]), "15");
6766 assert_eq!(printed(&[target, "-fgnuc-version=15.2", "-dumpfullversion"]), "15.2.0");
6767 assert_eq!(printed(&[target, "-print-multiarch"]), "x86_64-linux-gnu");
6768 assert_eq!(printed(&[target, "-print-file-name=no-such-library.a"]), "no-such-library.a");
6771 assert_eq!(printed(&[target, "-print-prog-name=ld"]), "ld");
6772 let dirs = printed(&[target, "-print-search-dirs"]);
6773 assert!(dirs.starts_with("install: "), "{dirs}");
6774 assert!(dirs.contains("\nlibraries: ="), "{dirs}");
6775 }
6776
6777 #[test]
6778 fn the_sysroot_in_effect_is_the_one_the_command_line_named_or_the_one_for_the_target() {
6779 assert_eq!(printed(&["--sysroot=/opt/cross", "-print-sysroot"]), "/opt/cross");
6782
6783 let root = cache::dir().join("sysroots").join("riscv64-linux-musl");
6787 assert_eq!(
6788 printed(&["--target=riscv64-linux-musl", "-print-sysroot"]),
6789 root.display().to_string()
6790 );
6791
6792 let host = Triple::host().expect("a host this compiler knows");
6797 let own = printed(&[&format!("--target={host}"), "-print-sysroot"]);
6798 if host.os == rucc_target::Os::Windows {
6799 let root = cache::dir().join("sysroots").join(host.tuple().to_string());
6800 assert_eq!(own, root.display().to_string());
6801 } else {
6802 assert_eq!(own, "");
6803 }
6804 }
6805
6806 #[test]
6807 fn the_provenance_of_a_sysroot_is_the_manifest_it_carries() {
6808 let manifest = "rucc sysroot manifest 3\n\
6811 target\tx86_64-linux-musl\n\
6812 kernel\t6.12\n\
6813 include/generic/stdio.h\tmusl-1.2.5\t\
6814 https://musl.libc.org/releases/musl-1.2.5.tar.gz\t\
6815 0000000000000000000000000000000000000000000000000000000000000000\tmit\t\
6816 bundled\n\
6817 lib/libc.so\tmusl-1.2.5\t\
6818 https://musl.libc.org/releases/musl-1.2.5.tar.gz\t\
6819 1111111111111111111111111111111111111111111111111111111111111111\tmit\t\
6820 generated\n";
6821 let tree = TempTree::new("provenance", &[("manifest", manifest)]);
6822 let sysroot = format!("--sysroot={}", tree.0.display());
6823 assert_eq!(printed(&[&sysroot, "-print-sysroot-provenance"]) + "\n", manifest);
6830
6831 let bare = TempTree::new("provenance-bare", &[]);
6835 assert_eq!(
6836 printed(&[&format!("--sysroot={}", bare.0.display()), "-print-sysroot-provenance"]),
6837 ""
6838 );
6839
6840 let host = Triple::host().expect("a host this compiler knows");
6843 assert_eq!(printed(&[&format!("--target={host}"), "-print-sysroot-provenance"]), "");
6844
6845 assert_eq!(printed(&[&sysroot, "--print-sysroot-provenance"]) + "\n", manifest);
6847
6848 assert_eq!(
6853 printed(&[&sysroot, "-print-sysroot-digest"]),
6854 "d705ae6ebeafeb7fda4bd57cecc7882bf49784b17015664a09cfae25a1b2000a"
6855 );
6856 assert_eq!(
6857 printed(&[&sysroot, "--print-sysroot-digest"]),
6858 printed(&[&sysroot, "-print-sysroot-digest"])
6859 );
6860
6861 assert_eq!(
6864 printed(&[&format!("--sysroot={}", bare.0.display()), "-print-sysroot-digest"]),
6865 ""
6866 );
6867 assert_eq!(printed(&[&format!("--target={host}"), "-print-sysroot-digest"]), "");
6868 }
6869
6870 #[test]
6871 fn a_manifest_this_build_cannot_read_is_refused_rather_than_printed() {
6872 let tree = TempTree::new(
6876 "provenance-bad",
6877 &[("manifest", "rucc sysroot manifest 3\ntarget\tx86_64-linux-musl\nlib/libc.a\n")],
6878 );
6879 let message =
6880 refused(&[&format!("--sysroot={}", tree.0.display()), "-print-sysroot-provenance"]);
6881 assert!(message.contains("manifest"), "{message}");
6882 assert!(message.contains("1 fields where an input has six"), "{message}");
6883
6884 let digest =
6887 refused(&[&format!("--sysroot={}", tree.0.display()), "-print-sysroot-digest"]);
6888 assert_eq!(digest, message);
6889 }
6890
6891 #[test]
6892 fn the_two_dependency_flags_that_stop_after_the_rule_stop_after_the_rule() {
6893 let (opts, _) = compile(&["-M", "a.c"]);
6894 assert!(opts.deps.emit && opts.deps.instead_of_compiling);
6895 assert!(opts.deps.system_headers, "plain -M lists them");
6896 assert_eq!(opts.emit, EmitKind::Preprocessed);
6897
6898 let (opts, _) = compile(&["-M", "-c", "a.c"]);
6901 assert_eq!(opts.emit, EmitKind::Preprocessed);
6902
6903 let (opts, _) = compile(&["-MM", "a.c"]);
6904 assert!(!opts.deps.system_headers);
6905 }
6906
6907 #[test]
6908 fn the_two_that_end_in_d_leave_the_compilation_alone() {
6909 let (opts, _) = compile(&["-MD", "-c", "a.c"]);
6910 assert!(opts.deps.emit && !opts.deps.instead_of_compiling);
6911 assert!(opts.deps.system_headers);
6912 assert_eq!(opts.emit, EmitKind::Object);
6913
6914 let (opts, _) = compile(&["-MMD", "-c", "a.c"]);
6915 assert!(opts.deps.emit && !opts.deps.instead_of_compiling);
6916 assert!(!opts.deps.system_headers);
6917 }
6918
6919 #[test]
6920 fn nothing_puts_the_system_headers_back_once_a_flag_has_taken_them_out() {
6921 let (opts, _) = compile(&["-MM", "-M", "a.c"]);
6924 assert!(!opts.deps.system_headers);
6925 let (opts, _) = compile(&["-MD", "-MMD", "-c", "a.c"]);
6926 assert!(!opts.deps.system_headers);
6927 let (opts, _) = compile(&["-MMD", "-MD", "-c", "a.c"]);
6928 assert!(!opts.deps.system_headers);
6929 }
6930
6931 #[test]
6932 fn a_target_arrives_escaped_from_one_flag_and_untouched_from_the_other() {
6933 let (opts, _) = compile(&["-MM", "-MT", "a b.o", "-MQ", "a b.o", "a.c"]);
6934 assert_eq!(opts.deps.targets, vec!["a b.o".to_owned(), "a\\ b.o".to_owned()]);
6935 }
6936
6937 #[test]
6938 fn the_rest_of_the_family_is_a_file_and_a_switch() {
6939 let (opts, _) = compile(&["-MM", "-MF", "dep.d", "-MP", "a.c"]);
6940 assert_eq!(opts.deps.file.as_deref(), Some("dep.d"));
6941 assert!(opts.deps.phony);
6942
6943 for flag in ["-MF", "-MT", "-MQ"] {
6944 let e = parse_args(&args(&[flag])).unwrap_err();
6945 assert!(e.message.contains("requires an argument"), "{}", e.message);
6946 }
6947 }
6948
6949 #[test]
6951 fn a_dependency_file_asked_for_through_the_preprocessor_is_written_where_it_said() {
6952 let (opts, _) = compile(&["-Wp,-MD,applets/.applets.o.d", "-c", "a.c"]);
6953 assert!(opts.deps.emit);
6954 assert!(opts.deps.system_headers);
6955 assert_eq!(opts.deps.file.as_deref(), Some("applets/.applets.o.d"));
6956
6957 let (opts, _) = compile(&["-Wp,-MMD,x.d,-MP,-MT,x.o", "-c", "a.c"]);
6958 assert!(!opts.deps.system_headers);
6959 assert!(opts.deps.phony);
6960 assert_eq!(opts.deps.file.as_deref(), Some("x.d"));
6961 assert_eq!(opts.deps.targets, vec!["x.o".to_owned()]);
6962 }
6963
6964 #[test]
6965 fn a_preprocessor_flag_this_compiler_does_not_read_is_still_refused_whole() {
6966 assert!(refused(&["-Wp,-MD", "-c", "a.c"]).contains("separate assembler"));
6967 assert!(refused(&["-Wp,-MD,x.d,-C", "-c", "a.c"]).contains("-Wp,-MD,x.d,-C"));
6968 }
6969
6970 struct TempTree(PathBuf);
6972
6973 impl Drop for TempTree {
6974 fn drop(&mut self) {
6975 let _ = std::fs::remove_dir_all(&self.0);
6976 }
6977 }
6978
6979 impl TempTree {
6980 fn new(name: &str, files: &[(&str, &str)]) -> TempTree {
6981 let dir = std::env::temp_dir().join(format!("rucc-deps-{}-{name}", std::process::id()));
6982 let _ = std::fs::remove_dir_all(&dir);
6983 std::fs::create_dir_all(&dir).expect("temporary directory should be writable");
6984 for (path, text) in files {
6985 let at = dir.join(path);
6986 if let Some(parent) = at.parent() {
6987 std::fs::create_dir_all(parent).expect("creating a subdirectory should work");
6988 }
6989 std::fs::write(&at, text).expect("writing a temporary file should work");
6990 }
6991 TempTree(dir)
6992 }
6993
6994 fn path(&self, name: &str) -> String {
6995 self.0.join(name).to_string_lossy().into_owned()
6996 }
6997 }
6998
6999 #[test]
7000 fn the_rule_names_what_the_includes_found_and_names_each_of_them_once() {
7001 let tree = TempTree::new(
7005 "found",
7006 &[
7007 ("a.c", "#include \"one.h\"\n#include \"two.h\"\nint main(void) { return X; }\n"),
7008 ("one.h", "#define X 0\n"),
7009 ("two.h", "#include \"one.h\"\n"),
7010 ],
7011 );
7012 let out = tree.path("dep.d");
7013 let code = run(&args(&["-MM", "-MF", &out, "-o", &tree.path("a.i"), &tree.path("a.c")]));
7014 assert_eq!(code, 0);
7015
7016 let text = std::fs::read_to_string(&out).expect("the rule should have been written");
7017 let names: Vec<&str> = text.split_whitespace().collect();
7018 assert_eq!(names.first(), Some(&"a.o:"), "{text}");
7020 assert_eq!(names.iter().filter(|n| n.ends_with("one.h")).count(), 1, "{text}");
7021 assert_eq!(names.iter().filter(|n| n.ends_with("two.h")).count(), 1, "{text}");
7022 assert_eq!(std::fs::read(tree.path("a.i")).expect("the output should exist"), b"");
7025 }
7026
7027 #[test]
7028 fn syntax_only_checks_the_file_and_writes_nothing() {
7029 let tree = TempTree::new(
7032 "syntax-only",
7033 &[
7034 ("good.c", "int f(int x) { return x + 1; }\n"),
7035 ("bad.c", "int f(void) { return y; }\n"),
7036 ],
7037 );
7038 let (opts, _) = compile(&["-fsyntax-only", "a.c"]);
7039 assert_eq!(opts.emit, EmitKind::SyntaxOnly);
7040
7041 let out = tree.path("good.o");
7042 assert_eq!(run(&args(&["-fsyntax-only", "-o", &out, &tree.path("good.c")])), 0);
7043 assert!(!std::path::Path::new(&out).exists(), "-fsyntax-only wrote {out}");
7044 assert!(!std::path::Path::new(&tree.path("good.s")).exists());
7045 assert_ne!(run(&args(&["-fsyntax-only", &tree.path("bad.c")])), 0);
7046 }
7047
7048 fn stack_usage_files(line: &[&str]) -> Vec<Option<String>> {
7050 let mut words = vec![LINUX, "-fstack-usage"];
7051 words.extend_from_slice(line);
7052 let (_, plan) = compile(&words);
7053 plan.jobs.iter().map(|job| job.stack_usage.clone()).collect()
7054 }
7055
7056 #[test]
7057 fn a_stack_usage_file_is_named_the_way_gcc_names_it() {
7058 let cases: &[(&[&str], &[Option<&str>])] = &[
7062 (&["-c", "sub/a.c"], &[Some("a.su")]),
7063 (&["-c", "sub/a.c", "-o", "out/x.o"], &[Some("out/x.su")]),
7064 (&["-c", "sub/a.c", "b.c"], &[Some("a.su"), Some("b.su")]),
7065 (&["-S", "sub/a.c", "-o", "out/y.s"], &[Some("out/y.su")]),
7066 (&["-S", "sub/a.c", "-o", "-"], &[Some("a.su")]),
7067 (&["-E", "sub/a.c", "-o", "out/z.i"], &[None]),
7068 (&["-fsyntax-only", "sub/a.c"], &[Some("a.su")]),
7069 (&["-fsyntax-only", "sub/a.c", "-o", "out/x.o"], &[Some("out/x.o-a.su")]),
7070 (&["sub/a.c"], &[Some("a.su")]),
7071 (&["sub/a.c", "-lm"], &[Some("a.su")]),
7072 (&["sub/a.c", "b.c"], &[Some("a-a.su"), Some("a-b.su")]),
7073 (&["sub/a.c", "b.o"], &[Some("a-a.su"), None]),
7074 (&["sub/a.c", "-o", "out/prog"], &[Some("out/prog-a.su")]),
7075 (&["sub/a.c", "-o", "out/lib.so"], &[Some("out/lib.so-a.su")]),
7076 (&["sub/a.c", "-o", "out/prog.exe"], &[Some("out/prog-a.su")]),
7077 (&["sub/a.c", "-o", "out/prog", "-dumpbase", "zz"], &[Some("out/zz-a.su")]),
7078 (&["sub/a.c", "-o", "out/prog", "-dumpdir", "dd-"], &[Some("dd-a.su")]),
7079 (
7080 &["sub/a.c", "b.c", "-dumpdir", "dd/", "-dumpbase", "zz"],
7081 &[Some("dd/zz-a.su"), Some("dd/zz-b.su")],
7082 ),
7083 (&["-c", "sub/a.c", "-dumpbase", "foo", "-o", "out/w.o"], &[Some("out/foo.su")]),
7084 (&["-c", "sub/a.c", "-dumpdir", "dd/", "-dumpbase", "sub/zz"], &[Some("sub/zz.su")]),
7085 (&["-c", "sub/a.c", "-dumpbase", "zz.c", "-dumpbase-ext", ".c"], &[Some("zz.su")]),
7086 (&["-c", "sub/a.c", "-dumpdir", "pre", "-o", "out/x.o"], &[Some("prex.su")]),
7087 (&["-c", "sub/a.c", "-save-temps=cwd", "-o", "out/x.o"], &[Some("x.su")]),
7088 ];
7089 for (line, want) in cases {
7090 let want: Vec<Option<String>> = want.iter().map(|w| w.map(str::to_owned)).collect();
7091 assert_eq!(stack_usage_files(line), want, "{line:?}");
7092 }
7093 let (_, plan) = compile(&[LINUX, "-c", "sub/a.c"]);
7095 assert_eq!(plan.jobs[0].stack_usage, None);
7096 }
7097
7098 #[test]
7099 fn a_stack_usage_file_has_a_line_per_function_where_gcc_would_put_it() {
7100 let tree = TempTree::new(
7101 "stack-usage",
7102 &[
7103 ("inc/h.h", "static inline int twice(int x) { return x * 2; }\n"),
7104 (
7105 "a.c",
7106 "#include \"inc/h.h\"\n\
7107 static int helper(int);\n\
7108 int grows(int n) { char v[n]; v[0] = (char)n; return v[n - 1] + twice(n); }\n\
7109 static int\n\
7110 helper(int x)\n\
7111 {\n\
7112 return x + 1;\n\
7113 }\n\
7114 int calls(int x) { return helper(x) + grows(x); }\n",
7115 ),
7116 ],
7117 );
7118 let (source, object) = (tree.path("a.c"), tree.path("a.o"));
7119 assert_eq!(run(&args(&["-O0", "-fstack-usage", "-c", &source, "-o", &object])), 0);
7120 let text = std::fs::read_to_string(tree.path("a.su")).expect("a.su should be written");
7121
7122 let line = |function: &str| {
7123 let suffix = format!(":{function}");
7124 let line =
7125 text.lines().find(|line| line.split('\t').next().unwrap().ends_with(&suffix));
7126 line.unwrap_or_else(|| panic!("no line for {function} in\n{text}"))
7127 };
7128 let expect = |function: &str, at: String, qualifier: &str| {
7129 let fields: Vec<&str> = line(function).split('\t').collect();
7130 assert_eq!(fields.len(), 3, "{text}");
7131 assert_eq!(fields[0], format!("{at}:{function}"), "{text}");
7132 let bytes: u32 = fields[1].parse().expect("the bytes should be a number");
7133 assert!(bytes >= 8 && bytes % 8 == 0, "{function} takes {bytes} bytes");
7134 assert_eq!(fields[2], qualifier, "{text}");
7135 };
7136 expect("grows", format!("{source}:3:5"), "dynamic");
7138 expect("helper", format!("{source}:5:1"), "static");
7141 expect("calls", format!("{source}:9:5"), "static");
7142 expect("twice", format!("{}:1:19", tree.path("inc/h.h")), "static");
7144 assert_eq!(text.lines().count(), 4, "{text}");
7145 }
7146
7147 #[test]
7148 fn a_stack_usage_file_is_empty_when_there_is_nothing_to_report_and_absent_under_dash_e() {
7149 let tree = TempTree::new(
7150 "stack-usage-empty",
7151 &[
7152 ("good.c", "int f(int x) { return x + 1; }\n"),
7153 ("bad.c", "int f(void) { return y; }\n"),
7154 ],
7155 );
7156 let good = tree.path("good.c");
7157 assert_eq!(
7160 run(&args(&["-fstack-usage", "-fsyntax-only", &good, "-o", &tree.path("x")])),
7161 0
7162 );
7163 assert_eq!(std::fs::read_to_string(tree.path("x-good.su")).unwrap(), "");
7164 let bad = tree.path("bad.c");
7165 assert_ne!(run(&args(&["-fstack-usage", "-c", &bad, "-o", &tree.path("bad.o")])), 0);
7166 assert_eq!(std::fs::read_to_string(tree.path("bad.su")).unwrap(), "");
7167 assert_eq!(run(&args(&["-fstack-usage", "-E", &good, "-o", &tree.path("e.i")])), 0);
7169 assert!(!std::path::Path::new(&tree.path("e.su")).exists());
7170 }
7171
7172 #[test]
7173 fn a_header_that_is_only_reached_under_a_guard_is_still_a_dependency() {
7174 let tree = TempTree::new(
7177 "guarded",
7178 &[
7179 ("a.c", "#include \"g.h\"\n#include \"g.h\"\nint main(void) { return 0; }\n"),
7180 ("g.h", "#ifndef G\n#define G\n#endif\n"),
7181 ],
7182 );
7183 let out = tree.path("dep.d");
7184 let code = run(&args(&["-MM", "-MF", &out, "-o", &tree.path("a.i"), &tree.path("a.c")]));
7185 assert_eq!(code, 0);
7186 let text = std::fs::read_to_string(&out).expect("the rule should have been written");
7187 assert_eq!(text.split_whitespace().filter(|n| n.ends_with("g.h")).count(), 1, "{text}");
7188 }
7189
7190 #[test]
7191 fn every_imacros_file_is_read_before_every_include_file_whatever_order_they_were_written() {
7192 let tree = TempTree::new(
7197 "preinclude",
7198 &[
7199 ("a.c", "int main(void) { return 0; }\n"),
7200 ("i.h", "#ifdef FROM_MACROS\nint saw_it;\n#else\nint missed_it;\n#endif\n"),
7201 ("m.h", "#define FROM_MACROS 1\nint macros_text;\n"),
7202 ],
7203 );
7204 let out = tree.path("a.i");
7205 let code = run(&args(&[
7206 "-E",
7207 "-include",
7208 &tree.path("i.h"),
7209 "-imacros",
7210 &tree.path("m.h"),
7211 "-o",
7212 &out,
7213 &tree.path("a.c"),
7214 ]));
7215 assert_eq!(code, 0);
7216 let text = std::fs::read_to_string(&out).expect("the output should have been written");
7217 assert!(text.contains("saw_it"), "{text}");
7218 assert!(!text.contains("macros_text"), "{text}");
7221 }
7222
7223 #[test]
7224 fn a_file_the_command_line_named_is_a_prerequisite_the_same_as_one_a_directive_named() {
7225 let tree = TempTree::new(
7226 "preinclude-deps",
7227 &[
7228 ("a.c", "int main(void) { return 0; }\n"),
7229 ("i.h", "int from_include;\n"),
7230 ("m.h", "#define M 1\n"),
7231 ],
7232 );
7233 let out = tree.path("dep.d");
7234 let code = run(&args(&[
7235 "-MM",
7236 "-MF",
7237 &out,
7238 "-include",
7239 &tree.path("i.h"),
7240 "-imacros",
7241 &tree.path("m.h"),
7242 "-o",
7243 &tree.path("a.i"),
7244 &tree.path("a.c"),
7245 ]));
7246 assert_eq!(code, 0);
7247 let text = std::fs::read_to_string(&out).expect("the rule should have been written");
7248 assert!(text.contains("i.h"), "{text}");
7249 assert!(text.contains("m.h"), "{text}");
7250 }
7251
7252 #[test]
7253 fn a_command_line_include_that_is_nowhere_on_the_path_is_an_error_and_not_a_warning() {
7254 let tree = TempTree::new(
7258 "preinclude-missing",
7259 &[("sub/a.c", "int main(void) { return 0; }\n"), ("sub/beside.h", "int x;\n")],
7260 );
7261 let code = run(&args(&["-E", "-include", "beside.h", "-o", "-", &tree.path("sub/a.c")]));
7262 assert_eq!(code, 1);
7263 }
7264
7265 #[test]
7266 fn a_command_line_that_links_names_the_executable_and_not_the_object_it_went_through() {
7267 let (opts, plan) = compile(&["-MD", "sub/a.c", "-o", "prog"]);
7271 assert_eq!(plan.output.as_deref(), Some("prog"));
7272 assert_eq!(deps::default_target("sub/a.c", deps_target_output(&opts, &plan)), "prog");
7273 assert_eq!(
7274 deps::default_file(&opts.deps, "sub/a.c", plan.output.as_deref()).as_deref(),
7275 Some("prog.d")
7276 );
7277 }
7278
7279 #[test]
7280 fn the_plan_keeps_the_output_name_because_the_rule_is_written_from_it() {
7281 let (_, plan) = compile(&["-MMD", "-c", "sub/a.c", "-o", "obj/x.o"]);
7282 assert_eq!(plan.output.as_deref(), Some("obj/x.o"));
7283 let (_, plan) = compile(&["-MMD", "-c", "sub/a.c"]);
7284 assert_eq!(plan.output, None);
7285 }
7286
7287 #[test]
7288 fn usage_fits_on_a_screen() {
7289 assert!(USAGE.lines().count() < 74, "usage text has grown past one screen");
7371 }
7372}