1#![doc(html_root_url = "https://docs.rs/rucc-driver/0.4.0")]
28
29pub mod compile;
30pub mod library;
31pub mod link;
32mod map;
33pub mod phase;
34pub mod preprocess;
35pub mod schedule;
36
37use std::fmt::Write as _;
38use std::io::Write as _;
39use std::path::PathBuf;
40
41use rucc_codegen::coverage::{self, Fired};
42use rucc_session::{Dumps, EmitKind, Options, Session, Std, runtime};
43use rucc_target::Triple;
44
45use crate::link::LinkOptions;
46
47pub use crate::compile::{Artifact, Compiled, compile, compile_ir};
48pub use crate::phase::{Input, InputKind, Job, LinkJob, Output, Phase, Plan};
49pub use crate::preprocess::{OsFileSystem, Preprocessed, preprocess};
50pub use crate::schedule::Jobs;
51
52pub const VERSION: &str = env!("CARGO_PKG_VERSION");
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum Action {
58 Help,
60 Version,
62 PrintConfig(Box<Options>),
64 PrintPlan {
66 opts: Box<Options>,
68 plan: Box<Plan>,
70 link: Box<LinkOptions>,
72 },
73 Compile {
75 opts: Box<Options>,
77 plan: Box<Plan>,
79 link: Box<LinkOptions>,
81 jobs: Jobs,
83 verbose: bool,
85 },
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct CliError {
91 pub message: String,
94}
95
96impl std::fmt::Display for CliError {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.write_str(&self.message)
99 }
100}
101
102impl std::error::Error for CliError {}
103
104fn err(message: impl Into<String>) -> CliError {
105 CliError { message: message.into() }
106}
107
108pub const USAGE: &str = "\
113rucc, an optimizing C compiler
114
115usage: rucc [options] file...
116
117options:
118 -c compile and assemble, do not link
119 -S compile only, emit assembly
120 -E preprocess only
121 -o <file> write output to <file>, or to standard output for -
122 -D <name>[=<value>], -U <name> define a macro, or undefine one after every -D
123 -I <dir> add <dir> to the include search path
124 -iquote -isystem -idirafter <dir> the other chains, -nostdinc drops ours
125 --sysroot=<dir> look for the library's headers under <dir>, -isysroot too
126 -P, -dM with -E: leave out the markers, or dump the macros
127 -std=<dialect> c89 through c23, and the gnu spellings
128 -fgnuc-version=<v> the GCC release to claim, default 7.0.0
129 -x <lang> treat later inputs as <lang>, or none to stop
130 -O<level> optimize: 0, 1, 2, 3, s, z
131 -g, -fno-omit-frame-pointer, -mno-red-zone debug info, keep a frame pointer, no red zone
132 -l<name>, -L <dir>, -B <dir> link a library, where to look for one, where our own tools are
133 -static -shared -pie -no-pie -nostdlib -nostartfiles -nodefaultlibs -rdynamic -s how to link
134 -Wl,<arg>, -Xlinker <arg>, -fuse-ld=<name> hand an argument to the linker, or pick one
135 -Werror -pedantic warnings are errors, diagnose what the standard forbids
136 -j[n] compile n translation units at once, default all
137 -v, -### print each phase as it runs, or without running any
138 --target=<triple> generate code for <triple>
139 --emit=<kind> exe, obj, asm, preprocessed, tast, ir, mir-final
140 --print-config print the resolved configuration and exit
141 --version print the version and exit
142 -h, --help print this message and exit
143
144See spec/04-driver-and-cli.md for the full flag reference.
145";
146
147fn joined_or_next(
151 arg: &str,
152 at: usize,
153 args: &[String],
154 i: &mut usize,
155) -> Result<String, CliError> {
156 if arg.len() > at {
157 return Ok(arg[at..].to_owned());
158 }
159 let next = args.get(*i).ok_or_else(|| err(format!("{arg} requires an argument")))?;
160 *i += 1;
161 Ok(next.clone())
162}
163
164pub fn parse_args(args: &[String]) -> Result<Action, CliError> {
171 let host = Triple::host()
172 .ok_or_else(|| err("this host is not a supported target and no --target was given"))?;
173 let mut opts = Options::new(host);
174 let mut inputs: Vec<Input> = Vec::new();
175 let mut print_config = false;
176 let mut print_plan = false;
177 let mut verbose = false;
178 let mut jobs = Jobs::default();
179 let mut nostdinc = false;
180 let mut sysroot: Option<PathBuf> = None;
181 let mut output = None;
182 let mut link = LinkOptions::default();
183 let mut forced: Option<InputKind> = None;
186
187 let mut i = 0;
188 while i < args.len() {
189 let arg = args[i].as_str();
190 i += 1;
191 match arg {
192 "-h" | "--help" => return Ok(Action::Help),
193 "--version" => return Ok(Action::Version),
194 "--print-config" => print_config = true,
195 "-###" => print_plan = true,
196 "-v" => verbose = true,
197 "-c" => opts.emit = EmitKind::Object,
198 "-S" => opts.emit = EmitKind::Asm,
199 "-E" => opts.emit = EmitKind::Preprocessed,
200 "-g" => opts.debug_info = true,
201 "-Werror" => opts.warnings_are_errors = true,
202 "-P" => opts.line_markers = false,
203 "-ansi" => {
204 opts.std = Std::C89;
205 opts.gnu_extensions = false;
206 }
207 "-pedantic" | "-Wpedantic" => opts.pedantic = true,
210 "-ffreestanding" => opts.hosted = false,
211 "-fhosted" => opts.hosted = true,
212 "-fno-omit-frame-pointer" => opts.frame_pointer = true,
215 "-fomit-frame-pointer" => opts.frame_pointer = false,
216 "-mno-red-zone" => opts.red_zone = false,
217 "-mred-zone" => opts.red_zone = true,
218 "-nostdinc" => nostdinc = true,
222 "-o" => {
223 output = Some(args.get(i).ok_or_else(|| err("-o requires an argument"))?.clone());
224 i += 1;
225 }
226 "-isysroot" => {
233 let dir = args.get(i).ok_or_else(|| err("-isysroot requires an argument"))?;
234 i += 1;
235 sysroot = Some(PathBuf::from(dir));
236 }
237 "-iquote" | "-isystem" | "-idirafter" => {
238 let dir = args.get(i).ok_or_else(|| err(format!("{arg} requires an argument")))?;
239 i += 1;
240 match arg {
241 "-iquote" => opts.search.push_quote(dir.clone()),
242 "-isystem" => opts.search.push_system(dir.clone()),
243 _ => opts.search.push_after(dir.clone()),
244 }
245 }
246 "-x" => {
247 let lang = args.get(i).ok_or_else(|| err("-x requires an argument"))?;
248 i += 1;
249 forced = if lang == "none" {
250 None
251 } else {
252 Some(InputKind::from_x_arg(lang).map_err(|e| err(format!("{e}")))?)
253 };
254 }
255 _ if arg.starts_with("-D") => {
263 let value = joined_or_next(arg, 2, args, &mut i)?;
264 opts.defines.push(value);
265 }
266 _ if arg.starts_with("-U") => {
267 let value = joined_or_next(arg, 2, args, &mut i)?;
268 opts.undefines.push(value);
269 }
270 _ if arg.starts_with("-I") => {
271 let dir = joined_or_next(arg, 2, args, &mut i)?;
272 opts.search.push_bracket(dir);
273 }
274 _ if arg.starts_with("-std=") => {
275 let name = &arg["-std=".len()..];
276 let (std, gnu) = Std::from_flag(name)
277 .ok_or_else(|| err(format!("unknown dialect `{name}`, see --help")))?;
278 opts.std = std;
279 opts.gnu_extensions = gnu;
280 }
281 _ if Dumps::is_family(arg) => {
290 opts.dumps.add(&arg[2..]);
291 }
292 _ if arg.starts_with("-fgnuc-version=") => {
293 let v = &arg["-fgnuc-version=".len()..];
294 opts.gnuc = v.parse().map_err(err)?;
295 }
296 "-fnested-functions" => {
301 return Err(err(
302 "nested functions are not supported: a call to one goes through a trampoline \
303 written on the stack, which no target that enforces an unexecutable stack \
304 allows",
305 ));
306 }
307 "-fno-nested-functions" => {}
308 "-static" => link.is_static = true,
312 "-shared" => link.shared = true,
313 "-pie" => link.pie = Some(true),
314 "-no-pie" | "-nopie" => link.pie = Some(false),
315 "-nostdlib" => link.no_stdlib = true,
316 "-nostartfiles" => link.no_startfiles = true,
317 "-nodefaultlibs" => link.no_defaultlibs = true,
318 "-fno-builtins-lib" => link.no_builtins_lib = true,
319 "-fbuiltins-lib" => link.no_builtins_lib = false,
320 "-rdynamic" | "-export-dynamic" => link.export_dynamic = true,
321 "-s" => link.strip = true,
322 "-Xlinker" => {
323 let next = args.get(i).ok_or_else(|| err("-Xlinker requires an argument"))?;
324 i += 1;
325 link.passthrough.push(next.clone());
326 }
327 _ if arg.starts_with("-Wl,") => {
328 link.passthrough.extend(arg["-Wl,".len()..].split(',').map(str::to_owned));
331 }
332 _ if arg.starts_with("-fuse-ld=") => {
333 link.use_ld = Some(arg["-fuse-ld=".len()..].to_owned());
334 }
335 _ if arg.starts_with("-l") && arg.len() > 2 => {
336 inputs.push(Input::library(&arg[2..]));
337 }
338 "-l" => {
339 let next = args.get(i).ok_or_else(|| err("-l requires an argument"))?;
340 i += 1;
341 inputs.push(Input::library(next));
342 }
343 _ if arg.starts_with("-L") => {
344 link.search.push(PathBuf::from(joined_or_next(arg, 2, args, &mut i)?));
345 }
346 _ if arg.starts_with("-B") => {
347 link.prefixes.push(PathBuf::from(joined_or_next(arg, 2, args, &mut i)?));
348 }
349 _ if arg.starts_with("-j") => {
350 jobs = Jobs::parse(&arg[2..]).map_err(err)?;
351 }
352 _ if arg.starts_with("--sysroot=") => {
353 sysroot = Some(PathBuf::from(&arg["--sysroot=".len()..]));
354 }
355 _ if arg.starts_with("--target=") => {
356 let t = &arg["--target=".len()..];
357 opts.target = t.parse().map_err(|e| err(format!("{e}")))?;
358 }
359 _ if arg.starts_with("--emit=") => {
360 let k = &arg["--emit=".len()..];
361 opts.emit = k
362 .parse()
363 .map_err(|()| err(format!("unknown --emit kind `{k}`, see --help")))?;
364 }
365 _ if arg.starts_with("-O") => {
366 opts.opt_level = arg[2..]
367 .parse()
368 .map_err(|()| err(format!("unknown optimization level `{arg}`")))?;
369 }
370 _ if arg.starts_with("-Zrule-coverage=") => {
376 let file = &arg["-Zrule-coverage=".len()..];
377 if file.is_empty() {
378 return Err(err("-Zrule-coverage= needs a file to write to"));
379 }
380 opts.rule_coverage = Some(file.to_owned());
381 }
382 _ if arg.starts_with("-Z") => {
383 return Err(err(format!(
384 "`{arg}` is not an unstable option this compiler has, see \
385 spec/04-driver-and-cli.md section 4.11 for the ones it does"
386 )));
387 }
388 _ if arg.starts_with('-') && arg.len() > 1 => {
389 return Err(err(format!("unknown option `{arg}`")));
394 }
395 _ => inputs.push(Input { path: arg.to_owned(), forced, library: false }),
396 }
397 }
398
399 link.sysroot = sysroot.clone();
406 if !nostdinc {
407 opts.search.push_system(runtime::DIR);
408 for dir in library::system_dirs(opts.target, sysroot.as_deref()) {
412 opts.search.push_system(dir);
413 }
414 }
415 opts.search.remove_duplicates();
419
420 if print_config {
423 return Ok(Action::PrintConfig(Box::new(opts)));
424 }
425 let plan = Plan::new(&opts, &inputs, output.as_deref()).map_err(|e| err(e.message))?;
426 if print_plan {
427 return Ok(Action::PrintPlan {
428 opts: Box::new(opts),
429 plan: Box::new(plan),
430 link: Box::new(link),
431 });
432 }
433 Ok(Action::Compile {
434 opts: Box::new(opts),
435 plan: Box::new(plan),
436 link: Box::new(link),
437 jobs,
438 verbose,
439 })
440}
441
442#[must_use]
447pub fn print_config(opts: &Options) -> String {
448 let sess = Session::new(opts.clone());
449 let t = &sess.target;
450 let mut out = String::new();
451 let _ = writeln!(out, "version: {VERSION}");
452 let _ = writeln!(out, "target: {}", t.triple);
453 let _ = writeln!(out, "arch: {}", t.triple.arch.as_str());
454 let _ = writeln!(out, "os: {}", t.triple.os.as_str());
455 let _ = writeln!(out, "env: {}", t.triple.env.as_str());
456 let _ = writeln!(out, "object-format: {}", t.object_format.as_str());
457 let _ = writeln!(out, "pointer-width: {}", t.pointer_width);
458 let _ = writeln!(out, "long-width: {}", t.long_width);
459 let _ = writeln!(out, "long-double-width: {}", t.long_double_width);
460 let _ = writeln!(out, "endian: {}", if t.little_endian { "little" } else { "big" });
461 let _ = writeln!(out, "char-signed: {}", t.char_is_signed);
462 let _ = writeln!(out, "va-list: {}", t.va_list.as_str());
463 let regs: Vec<String> = t
466 .regs
467 .classes()
468 .map(|(class, info)| format!("{} {}", info.name, t.regs.len(class)))
469 .collect();
470 let _ = writeln!(
471 out,
472 "registers: {}",
473 if regs.is_empty() { "none".to_string() } else { regs.join(", ") }
474 );
475 let _ = writeln!(out, "opt-level: {}", sess.opts.opt_level);
476 let _ = writeln!(out, "emit: {}", sess.opts.emit.as_str());
477 let _ = writeln!(out, "debug-info: {}", sess.opts.debug_info);
478 let _ = writeln!(out, "frame-pointer: {}", sess.opts.frame_pointer);
479 let _ = writeln!(out, "red-zone: {}", sess.opts.red_zone);
480 for dir in sess.opts.search.dirs() {
483 let system = if dir.is_system { " (system)" } else { "" };
484 let _ = writeln!(out, "include: {}{system}", dir.path.display());
485 }
486 out
487}
488
489fn preprocess_all(opts: &Options, plan: &Plan) -> i32 {
495 let fs = OsFileSystem::new();
496 let mut stderr = std::io::stderr().lock();
497 let mut failed = false;
498 for job in &plan.jobs {
499 if !job.phases.first().is_some_and(|p| *p == Phase::Preprocess) {
500 continue;
503 }
504 let result = preprocess(opts, &job.input, &fs);
505 for message in &result.messages {
506 let _ = writeln!(stderr, "{message}");
507 }
508 if result.failed() {
509 failed = true;
510 continue;
511 }
512 if let Err(e) = write_out(&job.output, result.text.as_bytes()) {
513 let _ = writeln!(stderr, "rucc: error: {e}");
514 failed = true;
515 }
516 }
517 i32::from(failed)
518}
519
520fn compile_all(opts: &Options, plan: &Plan) -> i32 {
526 let fs = OsFileSystem::new();
527 let mut stderr = std::io::stderr().lock();
528 let mut failed = false;
529 let mut fired = Fired::new();
530 for job in &plan.jobs {
531 if !job.phases.contains(&Phase::Compile) {
532 continue;
533 }
534 let result = if job.kind == InputKind::Ir {
538 compile_ir(opts, &job.input, &fs)
539 } else {
540 compile(opts, &job.input, &fs)
541 };
542 fired.merge(&result.fired);
543 for message in &result.messages {
544 let _ = writeln!(stderr, "{message}");
545 }
546 if result.failed() {
547 failed = true;
548 continue;
549 }
550 if let Err(e) = write_out(&job.output, result.artifact.bytes()) {
551 let _ = writeln!(stderr, "rucc: error: {e}");
552 failed = true;
553 }
554 }
555 failed |= !write_coverage(opts, &fired, &mut stderr);
556 i32::from(failed)
557}
558
559struct Scratch {
566 dir: PathBuf,
568}
569
570impl Scratch {
571 fn new() -> Result<Scratch, String> {
577 let dir = std::env::temp_dir().join(format!("rucc-{}", std::process::id()));
578 std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
579 Ok(Scratch { dir })
580 }
581}
582
583impl Drop for Scratch {
584 fn drop(&mut self) {
585 let _ = std::fs::remove_dir_all(&self.dir);
586 }
587}
588
589fn link_line(opts: &Options, link: &LinkOptions, job: &LinkJob) -> Result<String, link::Error> {
596 let linker = link::find(opts.target, link)?;
597 let args = link::line(opts.target, link, &job.inputs, &job.output)?;
598 Ok(link::render(&linker, &args))
599}
600
601fn link_all(opts: &Options, plan: &Plan, link: &LinkOptions, verbose: bool) -> i32 {
608 let Some(job) = &plan.link else {
609 let mut stderr = std::io::stderr().lock();
612 let _ = writeln!(stderr, "rucc: error: there is nothing to link");
613 return 1;
614 };
615 let linker = match link::find(opts.target, link) {
618 Ok(linker) => linker,
619 Err(why) => return complain(why),
620 };
621
622 let scratch = match Scratch::new() {
623 Ok(scratch) => scratch,
624 Err(why) => return complain(format!("could not make a place for the object files: {why}")),
625 };
626
627 let fs = OsFileSystem::new();
628 let mut failed = false;
629 let mut produced: Vec<String> = Vec::with_capacity(plan.jobs.len());
632 let mut fired = Fired::new();
633 {
634 let mut stderr = std::io::stderr().lock();
635 for (at, job) in plan.jobs.iter().enumerate() {
636 let out = match &job.output {
637 Output::Temporary(hint) => {
638 scratch.dir.join(format!("{at}-{hint}")).display().to_string()
641 }
642 Output::File(path) => path.clone(),
643 Output::Stdout => continue,
646 };
647 produced.push(out.clone());
648 if !job.phases.contains(&Phase::Compile) {
649 continue;
650 }
651 let result = if job.kind == InputKind::Ir {
652 compile_ir(opts, &job.input, &fs)
653 } else {
654 compile(opts, &job.input, &fs)
655 };
656 fired.merge(&result.fired);
657 for message in &result.messages {
658 let _ = writeln!(stderr, "{message}");
659 }
660 if result.failed() {
661 failed = true;
662 continue;
663 }
664 if !matches!(result.artifact, Artifact::Object(_)) {
665 let _ = writeln!(
670 stderr,
671 "rucc: internal error: {}: no object file was produced for the link",
672 job.input
673 );
674 failed = true;
675 continue;
676 }
677 if let Err(e) = std::fs::write(&out, result.artifact.bytes()) {
678 let _ = writeln!(stderr, "rucc: error: {out}: {e}");
679 failed = true;
680 }
681 }
682 failed |= !write_coverage(opts, &fired, &mut stderr);
683 }
684 if failed {
685 return 1;
689 }
690
691 let mut outputs = produced.into_iter();
695 let mut items = Vec::with_capacity(job.inputs.len());
696 for item in &job.inputs {
697 match item {
698 link::Item::Library(name) => items.push(link::Item::Library(name.clone())),
699 link::Item::File(_) => match outputs.next() {
700 Some(path) => items.push(link::Item::File(path)),
701 None => return complain("the plan asks the linker for a file nothing produced"),
702 },
703 }
704 }
705
706 let args = match link::line(opts.target, link, &items, &job.output) {
707 Ok(args) => args,
708 Err(why) => return complain(why),
709 };
710 if verbose {
711 let mut stderr = std::io::stderr().lock();
712 let _ = writeln!(stderr, "{}", link::render(&linker, &args));
713 }
714 match link::run(&linker, &args) {
715 Ok(()) => 0,
716 Err(link::Error::Refused { .. }) => 1,
719 Err(why) => complain(why),
720 }
721}
722
723fn complain(why: impl std::fmt::Display) -> i32 {
725 let mut stderr = std::io::stderr().lock();
726 let _ = writeln!(stderr, "rucc: error: {why}");
727 1
728}
729
730fn write_coverage(opts: &Options, fired: &Fired, stderr: &mut impl std::io::Write) -> bool {
739 let Some(path) = &opts.rule_coverage else { return true };
740 let Some(table) = coverage::table(opts.target.arch) else {
741 let _ = writeln!(
742 stderr,
743 "rucc: error: there are no lowering rules for {} yet, so there is no coverage of them \
744 to report",
745 opts.target
746 );
747 return false;
748 };
749 match std::fs::write(path, fired.listing(table)) {
750 Ok(()) => true,
751 Err(e) => {
752 let _ = writeln!(stderr, "rucc: error: {path}: {e}");
753 false
754 }
755 }
756}
757
758fn write_out(output: &Output, bytes: &[u8]) -> Result<(), String> {
765 match output {
766 Output::Stdout => {
767 let mut stdout = std::io::stdout().lock();
768 stdout.write_all(bytes).map_err(|e| format!("writing to standard output: {e}"))
769 }
770 Output::File(path) | Output::Temporary(path) => {
771 std::fs::write(path, bytes).map_err(|e| format!("{path}: {e}"))
772 }
773 }
774}
775
776pub fn run(args: &[String]) -> i32 {
781 match parse_args(args) {
782 Ok(Action::Help) => {
783 print!("{USAGE}");
784 0
785 }
786 Ok(Action::Version) => {
787 println!("rucc {VERSION}");
788 0
789 }
790 Ok(Action::PrintConfig(opts)) => {
791 print!("{}", print_config(&opts));
792 0
793 }
794 Ok(Action::PrintPlan { opts, plan, link }) => {
795 print!("{}", plan.render());
796 if let Some(job) = &plan.link {
800 match link_line(&opts, &link, job) {
801 Ok(line) => println!("{line}"),
802 Err(why) => {
803 let mut stderr = std::io::stderr().lock();
804 let _ = writeln!(stderr, "rucc: error: {why}");
805 return 1;
806 }
807 }
808 }
809 0
810 }
811 Ok(Action::Compile { opts, plan, link, jobs, verbose }) => {
812 {
813 let mut stderr = std::io::stderr().lock();
814 if verbose {
815 let _ = write!(stderr, "{}", plan.render());
816 let _ = writeln!(stderr, "workers: {}", jobs.count());
817 }
818 }
819 if opts.emit == EmitKind::Preprocessed {
820 return preprocess_all(&opts, &plan);
821 }
822 if opts.emit != EmitKind::Executable {
823 return compile_all(&opts, &plan);
824 }
825 link_all(&opts, &plan, &link, verbose)
826 }
827 Err(e) => {
828 let mut stderr = std::io::stderr().lock();
829 let _ = writeln!(stderr, "rucc: error: {e}");
830 let _ = writeln!(stderr, "rucc: note: run `rucc --help` for usage");
831 1
832 }
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use rucc_session::{GnucVersion, OptLevel};
839
840 use super::*;
841
842 fn args(s: &[&str]) -> Vec<String> {
843 s.iter().map(|x| (*x).to_owned()).collect()
844 }
845
846 #[test]
847 fn help_and_version_win_over_everything_else() {
848 assert_eq!(parse_args(&args(&["-c", "--help", "x.c"])).unwrap(), Action::Help);
849 assert_eq!(parse_args(&args(&["--version"])).unwrap(), Action::Version);
850 }
851
852 fn compile(s: &[&str]) -> (Box<Options>, Box<Plan>) {
853 match parse_args(&args(s)).expect("expected a compilation") {
854 Action::Compile { opts, plan, .. } => (opts, plan),
855 other => panic!("expected a compilation, got {other:?}"),
856 }
857 }
858
859 fn linking(s: &[&str]) -> (Box<LinkOptions>, Box<Plan>) {
860 match parse_args(&args(s)).expect("expected a compilation") {
861 Action::Compile { link, plan, .. } => (link, plan),
862 other => panic!("expected a compilation, got {other:?}"),
863 }
864 }
865
866 #[test]
867 fn collects_inputs_and_flags() {
868 let (opts, plan) = compile(&["-c", "-O2", "-g", "a.c", "b.c"]);
869 let paths: Vec<&str> = plan.jobs.iter().map(|j| j.input.as_str()).collect();
870 assert_eq!(paths, vec!["a.c", "b.c"]);
871 assert_eq!(opts.opt_level, OptLevel::O2);
872 assert_eq!(opts.emit, EmitKind::Object);
873 assert!(opts.debug_info);
874 }
875
876 #[test]
879 fn an_unstable_option_is_taken_and_one_that_does_not_exist_is_refused() {
880 let (opts, _) = compile(&["-c", "-Zrule-coverage=/tmp/rules.cov", "a.c"]);
881 assert_eq!(opts.rule_coverage.as_deref(), Some("/tmp/rules.cov"));
882
883 let (plain, _) = compile(&["-c", "a.c"]);
884 assert_eq!(plain.rule_coverage, None, "nothing is measured unless it was asked for");
885
886 assert!(parse_args(&args(&["-Zrule-coverage=", "a.c"])).is_err(), "a file with no name");
887 let unknown = parse_args(&args(&["-Zwhat", "a.c"])).expect_err("there is no such option");
888 assert!(unknown.message.contains("4.11"), "{}", unknown.message);
889 }
890
891 #[test]
892 fn a_bare_dash_o_means_o1_the_way_gcc_reads_it() {
893 let (opts, _) = compile(&["-O", "a.c"]);
894 assert_eq!(opts.opt_level, OptLevel::O1);
895 }
896
897 #[test]
898 fn dash_x_applies_to_later_inputs_only_and_none_stops_it() {
899 let (_, plan) = compile(&["a.o", "-x", "c", "b.txt", "-x", "none", "c.o"]);
900 assert_eq!(plan.jobs[0].kind, InputKind::LinkerInput);
901 assert_eq!(plan.jobs[1].kind, InputKind::C);
902 assert_eq!(plan.jobs[2].kind, InputKind::LinkerInput);
903 }
904
905 #[test]
906 fn dash_j_reaches_the_scheduler_and_defaults_to_the_machine() {
907 let (_, _, jobs) = match parse_args(&args(&["-j4", "a.c"])).unwrap() {
908 Action::Compile { opts, plan, jobs, .. } => (opts, plan, jobs),
909 other => panic!("expected a compilation, got {other:?}"),
910 };
911 assert_eq!(jobs.count(), 4);
912
913 let default = match parse_args(&args(&["a.c"])).unwrap() {
914 Action::Compile { jobs, .. } => jobs,
915 other => panic!("expected a compilation, got {other:?}"),
916 };
917 assert_eq!(default, Jobs::available());
918 assert!(parse_args(&args(&["-j0", "a.c"])).is_err());
919 }
920
921 #[test]
922 fn triple_hash_prints_the_plan_and_runs_nothing() {
923 let a = parse_args(&args(&["-###", "-c", "a.c"])).unwrap();
924 let Action::PrintPlan { plan, .. } = a else { panic!("expected a plan dump") };
925 assert!(plan.render().contains("a.c: preprocess, compile, assemble -> a.o"));
926 }
927
928 #[test]
929 fn dash_x_names_what_it_accepts_when_it_does_not_know_a_language() {
930 let e = parse_args(&args(&["-x", "fortran", "a.c"])).unwrap_err();
931 assert!(e.message.contains("assembler-with-cpp"), "{}", e.message);
932 }
933
934 #[test]
935 fn an_unknown_flag_is_an_error_rather_than_a_shrug() {
936 let e = parse_args(&args(&["-fno-such-thing", "a.c"])).unwrap_err();
937 assert!(e.message.contains("unknown option"), "{}", e.message);
938 }
939
940 #[test]
941 fn asking_for_nested_functions_is_told_why_it_is_not_coming() {
942 let e = parse_args(&args(&["-fnested-functions", "a.c"])).unwrap_err();
943 assert!(e.message.contains("trampoline"), "{}", e.message);
944 assert!(parse_args(&args(&["-fno-nested-functions", "a.c"])).is_ok());
945 }
946
947 #[test]
948 fn an_unsupported_target_names_itself() {
949 let e = parse_args(&args(&["--target=sparc64-linux-gnu", "a.c"])).unwrap_err();
950 assert!(e.message.contains("sparc64"), "{}", e.message);
951 }
952
953 #[test]
954 fn no_inputs_is_an_error_but_print_config_needs_none() {
955 assert!(parse_args(&args(&[])).is_err());
956 assert!(matches!(parse_args(&args(&["--print-config"])), Ok(Action::PrintConfig(_))));
957 }
958
959 #[test]
960 fn print_config_reports_the_target_it_was_given_not_the_host() {
961 let a = parse_args(&args(&["--print-config", "--target=riscv64-linux-musl"])).unwrap();
962 let Action::PrintConfig(opts) = a else { panic!("expected a configuration dump") };
963 let text = print_config(&opts);
964 assert!(text.contains("target: riscv64-unknown-linux-musl"), "{text}");
965 assert!(text.contains("char-signed: false"), "{text}");
966 assert!(text.contains("object-format: elf"), "{text}");
967 assert!(text.contains("va-list: void-pointer"), "{text}");
968 assert!(text.contains("registers: none"), "{text}");
971 }
972
973 #[test]
974 fn print_config_has_one_key_per_line_and_a_fixed_order() {
975 let opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
976 let text = print_config(&opts);
977 let keys: Vec<&str> =
978 text.lines().map(|l| l.split(':').next().unwrap_or_default()).collect();
979 assert_eq!(keys[0], "version");
980 assert_eq!(keys[1], "target");
981 assert_eq!(keys.len(), 18);
982 assert!(text.ends_with('\n'));
983 }
984
985 #[test]
986 fn dash_o_needs_an_argument() {
987 let e = parse_args(&args(&["a.c", "-o"])).unwrap_err();
988 assert_eq!(e.message, "-o requires an argument");
989 }
990
991 #[test]
992 fn dash_d_and_dash_u_are_read_joined_or_separated_and_keep_their_order() {
993 let (opts, _) = compile(&["-DFOO=1", "-D", "BAR", "-UBAZ", "-U", "QUX", "a.c"]);
994 assert_eq!(opts.defines, ["FOO=1", "BAR"]);
995 assert_eq!(opts.undefines, ["BAZ", "QUX"]);
996 }
997
998 #[test]
999 fn the_include_flags_land_on_the_chain_each_one_names() {
1000 let (opts, _) = compile(&[
1003 "-Ii",
1004 "-iquote",
1005 "q",
1006 "-isystem",
1007 "sys",
1008 "-idirafter",
1009 "after",
1010 "--sysroot=/nowhere-at-all",
1011 "a.c",
1012 ]);
1013 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
1014 assert_eq!(dirs, ["q", "i", "sys", runtime::DIR, "after"]);
1017 assert!(!opts.search.dirs()[1].is_system);
1018 assert!(opts.search.dirs()[2].is_system);
1019 }
1020
1021 #[test]
1022 fn the_librarys_headers_come_after_the_compilers_own_and_go_away_with_them() {
1023 let (opts, _) = compile(&["a.c"]);
1027 let dirs = opts.search.dirs();
1028 let ours = dirs.iter().position(|d| d.path.to_str() == Some(runtime::DIR));
1029 assert_eq!(ours, Some(0), "{dirs:?}");
1030 assert!(dirs[1..].iter().all(|d| d.is_system), "{dirs:?}");
1031 let (bare, _) = compile(&["-nostdinc", "a.c"]);
1032 assert!(bare.search.dirs().is_empty(), "{:?}", bare.search.dirs());
1033 }
1034
1035 #[test]
1036 fn a_sysroot_moves_the_librarys_directories_and_nothing_else() {
1037 let (opts, _) = compile(&["-isystem", "sys", "--sysroot=/nowhere-at-all", "a.c"]);
1038 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
1039 assert_eq!(dirs, ["sys", runtime::DIR]);
1040 }
1041
1042 #[test]
1043 fn nostdinc_takes_the_compilers_own_headers_off_the_path() {
1044 let (opts, _) = compile(&["-Ii", "-nostdinc", "a.c"]);
1045 let dirs: Vec<&str> = opts.search.dirs().iter().filter_map(|d| d.path.to_str()).collect();
1046 assert_eq!(dirs, ["i"]);
1047 }
1048
1049 #[test]
1050 fn the_dialect_flags_set_the_language_and_the_extensions_separately() {
1051 let (opts, _) = compile(&["-std=gnu11", "a.c"]);
1052 assert_eq!(opts.std, Std::C11);
1053 assert!(opts.gnu_extensions);
1054
1055 let (opts, _) = compile(&["-std=iso9899:1999", "a.c"]);
1056 assert_eq!(opts.std, Std::C99);
1057 assert!(!opts.gnu_extensions);
1058
1059 let (opts, _) = compile(&["-ansi", "a.c"]);
1060 assert_eq!(opts.std, Std::C89);
1061 assert!(!opts.gnu_extensions);
1062
1063 let e = parse_args(&args(&["-std=c94jr", "a.c"])).unwrap_err();
1064 assert!(e.message.contains("unknown dialect"), "{}", e.message);
1065 }
1066
1067 #[test]
1068 fn the_dump_letters_are_a_family_and_everything_else_beginning_with_d_is_not() {
1069 let (opts, _) = compile(&["-dM", "a.c"]);
1070 assert!(opts.dumps.macros);
1071
1072 let (opts, _) = compile(&["-dDM", "a.c"]);
1075 assert!(opts.dumps.macros);
1076 let (opts, _) = compile(&["-dD", "a.c"]);
1077 assert!(!opts.dumps.macros);
1078
1079 let (opts, _) = compile(&["a.c"]);
1080 assert!(!opts.dumps.any());
1081
1082 let e = parse_args(&args(&["-dumpversion", "a.c"])).unwrap_err();
1085 assert!(e.message.contains("unknown option"), "{}", e.message);
1086 }
1087
1088 #[test]
1089 fn the_gcc_version_claimed_is_a_flag_and_the_short_spellings_are_the_ones_people_write() {
1090 let (opts, _) = compile(&["a.c"]);
1091 assert_eq!(
1092 opts.gnuc,
1093 GnucVersion { major: 7, minor: 0, patch: 0 },
1094 "the lowest claim a modern glibc gives its own declarations to"
1095 );
1096
1097 let (opts, _) = compile(&["-fgnuc-version=15.1.0", "a.c"]);
1098 assert_eq!(opts.gnuc, GnucVersion { major: 15, minor: 1, patch: 0 });
1099
1100 let (opts, _) = compile(&["-fgnuc-version=15", "a.c"]);
1103 assert_eq!(opts.gnuc, GnucVersion { major: 15, minor: 0, patch: 0 });
1104
1105 let (opts, _) = compile(&["-fgnuc-version=13.2", "a.c"]);
1106 assert_eq!(opts.gnuc, GnucVersion { major: 13, minor: 2, patch: 0 });
1107
1108 let e = parse_args(&args(&["-fgnuc-version=15.x", "a.c"])).unwrap_err();
1109 assert!(e.message.contains("minor that is not a number"), "{}", e.message);
1110
1111 let e = parse_args(&args(&["-fgnuc-version=1.2.3.4", "a.c"])).unwrap_err();
1112 assert!(e.message.contains("more than three"), "{}", e.message);
1113 }
1114
1115 #[test]
1116 fn pedantic_has_two_spellings_and_is_not_the_same_knob_as_the_dialect() {
1117 let (opts, _) = compile(&["-std=c17", "-pedantic", "a.c"]);
1118 assert!(opts.pedantic);
1119 assert_eq!(opts.std, Std::C17);
1120
1121 let (opts, _) = compile(&["-Wpedantic", "a.c"]);
1124 assert!(opts.pedantic);
1125
1126 let (opts, _) = compile(&["-std=c17", "a.c"]);
1127 assert!(!opts.pedantic, "a dialect on its own does not diagnose an extension");
1128 }
1129
1130 #[test]
1131 fn dash_p_and_dash_ffreestanding_reach_the_options() {
1132 let (opts, _) = compile(&["-E", "-P", "-ffreestanding", "a.c"]);
1133 assert!(!opts.line_markers);
1134 assert!(!opts.hosted);
1135 assert_eq!(opts.emit, EmitKind::Preprocessed);
1136 }
1137
1138 #[test]
1141 fn the_two_frame_flags_are_read_in_both_directions() {
1142 let (opts, _) = compile(&["-c", "a.c"]);
1143 assert!(!opts.frame_pointer, "gcc omits it above -O0 and so does this");
1144 assert!(opts.red_zone, "the psABI has one and nothing said not to use it");
1145
1146 let (opts, _) = compile(&["-c", "-fno-omit-frame-pointer", "-mno-red-zone", "a.c"]);
1147 assert!(opts.frame_pointer);
1148 assert!(!opts.red_zone);
1149
1150 let (opts, _) = compile(&[
1151 "-c",
1152 "-fno-omit-frame-pointer",
1153 "-fomit-frame-pointer",
1154 "-mno-red-zone",
1155 "-mred-zone",
1156 "a.c",
1157 ]);
1158 assert!(!opts.frame_pointer, "the last one wins, as it does in gcc");
1159 assert!(opts.red_zone);
1160 }
1161
1162 #[test]
1163 fn the_link_flags_are_collected_apart_from_the_compilation() {
1164 let (link, _) = linking(&[
1165 "-static",
1166 "-nostartfiles",
1167 "-rdynamic",
1168 "-s",
1169 "-fuse-ld=mold",
1170 "-L/opt/lib",
1171 "-B",
1172 "/opt/tools",
1173 "a.c",
1174 ]);
1175 assert!(link.is_static);
1176 assert!(link.no_startfiles);
1177 assert!(link.export_dynamic);
1178 assert!(link.strip);
1179 assert_eq!(link.use_ld.as_deref(), Some("mold"));
1180 assert_eq!(link.search, vec![PathBuf::from("/opt/lib")]);
1181 assert_eq!(link.prefixes, vec![PathBuf::from("/opt/tools")]);
1182 }
1183
1184 #[test]
1185 fn a_comma_in_dash_wl_separates_two_arguments() {
1186 let (link, _) = linking(&["-Wl,-rpath,/opt/lib", "-Xlinker", "--as-needed", "a.c"]);
1187 assert_eq!(link.passthrough, vec!["-rpath", "/opt/lib", "--as-needed"]);
1188 }
1189
1190 #[test]
1191 fn a_library_keeps_its_place_between_the_objects() {
1192 let (_, plan) = linking(&["--target=x86_64-unknown-linux-gnu", "a.c", "-lm", "b.c"]);
1197 let link = plan.link.expect("expected a link step");
1198 assert_eq!(
1199 link.inputs,
1200 vec![
1201 link::Item::File("a.o".into()),
1202 link::Item::Library("m".into()),
1203 link::Item::File("b.o".into()),
1204 ]
1205 );
1206 assert_eq!(plan.jobs.len(), 2);
1208 }
1209
1210 #[test]
1211 fn a_library_on_a_dash_c_line_is_a_note_rather_than_an_error() {
1212 let (_, plan) = linking(&["-c", "-lm", "a.c"]);
1213 assert!(plan.link.is_none());
1214 assert!(plan.notes.iter().any(|n| n.contains("-lm")), "{:?}", plan.notes);
1215 }
1216
1217 #[test]
1218 fn the_sysroot_reaches_the_linker_as_well_as_the_headers() {
1219 let (link, _) = linking(&["--sysroot=/opt/root", "a.c"]);
1220 assert_eq!(link.sysroot, Some(PathBuf::from("/opt/root")));
1221 }
1222
1223 #[test]
1224 fn usage_fits_on_a_screen() {
1225 assert!(USAGE.lines().count() < 34, "usage text has grown past one screen");
1228 }
1229}