1use std::path::Path;
14
15use rucc_base::Interner;
16use rucc_codegen::coverage::Fired;
17use rucc_codegen::elsewhere::Elsewhere;
18use rucc_codegen::lowering::Lowerings;
19use rucc_codegen::pipeline::{self, Machine, Recording};
20use rucc_codegen::pressure::Pressure;
21use rucc_diag::{Diagnostic, Severity, Span};
22use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
23use rucc_lex::{Convert, Keywords, PpToken, convert};
24use rucc_lower::Protector as LowerProtector;
25use rucc_sema::{Checker, Context as CheckContext};
26use rucc_session::{
27 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
28};
29use rucc_target::TargetInfo;
30use rucc_tuple::{Arch, ObjectFormat};
31
32use crate::preprocess::render;
33
34#[derive(Debug, Clone, PartialEq, Eq, Default)]
41pub enum Artifact {
42 #[default]
45 Nothing,
46 Text(String),
48 Object {
55 bytes: Vec<u8>,
57 defines: Vec<String>,
61 },
62}
63
64impl Artifact {
65 #[must_use]
67 pub fn bytes(&self) -> &[u8] {
68 match self {
69 Artifact::Nothing => &[],
70 Artifact::Text(text) => text.as_bytes(),
71 Artifact::Object { bytes, .. } => bytes,
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct Compiled {
79 pub artifact: Artifact,
81 pub messages: Vec<String>,
83 pub errors: u32,
85 pub fired: Fired,
91 pub pressure: Pressure,
96 pub lowerings: Lowerings,
101 pub dumps: Vec<rucc_opt::Dump>,
107 pub remarks: String,
113 pub deps: Vec<rucc_pp::Dependency>,
118 pub temps: Temps,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Default)]
134pub struct Temps {
135 pub preprocessed: Option<String>,
137 pub assembly: Option<String>,
139}
140
141impl Compiled {
142 #[must_use]
144 pub fn failed(&self) -> bool {
145 self.errors > 0
146 }
147
148 #[must_use]
153 pub fn text(&self) -> &str {
154 match &self.artifact {
155 Artifact::Text(text) => text,
156 _ => "",
157 }
158 }
159}
160
161#[must_use]
174pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
175 let mut sess = Session::new(opts.clone());
176 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
180 let mut diagnostics: Vec<Diagnostic> = Vec::new();
181 let mut fired = Fired::new();
183 let mut pressure = Pressure::new();
185 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
186 let mut dumps = Vec::new();
188 let mut remarks = String::new();
189 let mut temps = Temps::default();
191
192 let bytes = match fs.read(Path::new(name)) {
193 Ok(bytes) => bytes,
194 Err(e) => return failure(format!("{name}: {e}")),
195 };
196 let Ok(file) = sess.sources.add_shared(name, bytes, None) else {
197 return failure(format!("{name}: the source map has no room left for this file"));
198 };
199
200 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
204 let predef = rucc_pp::Predef::for_options(opts);
205 let expanded: Vec<PpToken> = {
206 let mut tokens = Vec::new();
207 {
212 let mut cx =
213 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
214 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
215 cx.pedantic = opts.pedantic;
216 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
217 return failure(format!(
218 "{name}: the source map has no room for the built in macros"
219 ));
220 }
221 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
222 return failure(format!("{name}: the source map has no room for the command line"));
223 }
224 tokens.append(&mut pp.run(file, &mut cx));
225 }
226 if opts.save_temps.wanted() {
227 temps.preprocessed = Some(rucc_pp::print(
228 file,
229 &tokens,
230 pp.line_directives(),
231 &sess.sources,
232 &sess.interner,
233 rucc_pp::PrintOptions { line_markers: opts.line_markers },
234 ));
235 }
236 tokens.iter().map(|token| token.to_pp()).collect()
237 };
238 diagnostics.extend(pp.take_diagnostics());
239 let deps = pp.dependencies().to_vec();
242
243 let cx = Convert {
246 keywords: &keywords,
247 interner: &sess.interner,
248 target: &sess.target,
249 std: opts.std,
250 gnu: opts.gnu_extensions,
251 pedantic: opts.pedantic,
252 };
253 let (tokens, complaints) = convert(&expanded, &cx);
254 diagnostics.extend(complaints);
255
256 let parsed = rucc_parse::parse(
257 &tokens,
258 rucc_parse::Context {
259 interner: &sess.interner,
260 std: opts.std,
261 gnu: opts.gnu_extensions,
262 pedantic: opts.pedantic,
263 error_limit: opts.error_limit as usize,
264 },
265 );
266 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
267 diagnostics.extend(parsed.diagnostics);
268
269 let mut artifact = Artifact::Nothing;
270 let mut instrumented = Instrumented::default();
273 if !parse_failed {
274 let mut checker = Checker::new(
275 &parsed.ast,
276 CheckContext {
277 names: &sess.interner,
278 target: &sess.target,
279 std: opts.std,
280 gnu: opts.gnu_extensions,
281 pedantic: opts.pedantic,
282 permissive: opts.permissive,
283 gnu89_inline: opts.gnu89_inline,
284 error_limit: opts.error_limit as usize,
285 builtins: opts.builtins && opts.hosted,
288 no_builtin: &opts.no_builtin,
289 short_enums: opts.short_enums,
290 trapping_math: opts.trapping_math,
291 },
292 );
293 checker.check_unit();
294 let checked = checker.finish();
295 if !checked.failed() {
296 match opts.emit {
297 EmitKind::Tast => {
298 artifact = Artifact::Text(rucc_sema::print(
299 &checked.tast,
300 &checked.types,
301 &sess.interner,
302 ));
303 }
304 EmitKind::TypeGranules => {
308 artifact = Artifact::Text(rucc_types::granule_report(
309 &checked.types,
310 &sess.interner,
311 &sess.target,
312 ));
313 }
314 EmitKind::Ir
315 | EmitKind::MirFinal
316 | EmitKind::Asm
317 | EmitKind::Object
318 | EmitKind::Archive
319 | EmitKind::Executable
320 | EmitKind::SafetySummary => {
321 let mut read = |named: &str| {
326 fs.read(Path::new(named))
327 .map(|bytes| bytes.as_slice().to_vec())
328 .map_err(|why| why.to_string())
329 };
330 let mut lowered = rucc_lower::lower(
331 name,
332 rucc_lower::Context {
333 tast: &checked.tast,
334 types: &checked.types,
335 target: &sess.target,
336 names: &mut sess.interner,
337 visibility: match opts.visibility {
338 Visibility::Default => IrVisibility::Default,
339 Visibility::Hidden => IrVisibility::Hidden,
340 Visibility::Protected => IrVisibility::Protected,
341 },
342 protector: match opts.protector {
343 Protector::None => LowerProtector::None,
344 Protector::Buffers => LowerProtector::Buffers,
345 Protector::Strong => LowerProtector::Strong,
346 Protector::All => LowerProtector::All,
347 },
348 wrapping: rucc_lower::Wrapping {
349 signed: opts.wrapping.signed,
350 pointer: opts.wrapping.pointer,
351 trap: opts.wrapping.trap,
352 },
353 aliasing: opts.strict_aliasing,
354 padding: opts.padding == Padding::Ignored,
355 contract: match opts.fp_contract {
356 Contract::Off => FpContract::Off,
357 Contract::On => FpContract::On,
358 Contract::Fast => FpContract::Fast,
359 },
360 read: &mut read,
361 },
362 );
363 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
367 if !failed {
368 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
373 for error in errors {
374 diagnostics.push(internal(&format!("invalid IR, {error}")));
375 }
376 } else if let Err(complaints) =
377 instrument(&mut lowered.module, &mut sess.interner, opts)
378 .map(|done| instrumented = done)
379 {
380 diagnostics.extend(complaints);
381 } else if let Err(complaints) = optimize(
382 &mut lowered.module,
383 &sess.interner,
384 &sess.target,
385 opts,
386 name,
387 &mut dumps,
388 &mut remarks,
389 ) {
390 diagnostics.extend(complaints);
391 } else if opts.emit == EmitKind::SafetySummary {
392 artifact = Artifact::Text(
397 rucc_safety::summarize(
398 &lowered.module,
399 &sess.interner,
400 name,
401 opts.safety.as_str(),
402 instrumented.checks,
403 instrumented.interposed,
404 instrumented.crossings,
405 )
406 .render(),
407 );
408 } else if opts.emit == EmitKind::Ir {
409 artifact =
414 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
415 } else {
416 match generate(
419 &mut lowered.module,
420 &mut sess.interner,
421 &sess.target,
422 opts,
423 &mut Recording {
424 fired: &mut fired,
425 pressure: &mut pressure,
426 lowerings: &mut lowerings,
427 },
428 &mut temps.assembly,
429 ) {
430 Ok(made) => artifact = made,
431 Err(complaints) => diagnostics.extend(complaints),
432 }
433 }
434 }
435 diagnostics.extend(lowered.diagnostics);
436 }
437 _ => {}
438 }
439 }
440 diagnostics.extend(checked.diagnostics);
441 }
442
443 let mut messages = Vec::with_capacity(diagnostics.len());
444 let mut errors = 0;
445 for diag in &diagnostics {
446 if !opts.warnings && diag.severity == Severity::Warning {
450 continue;
451 }
452 if diag.severity.is_fatal()
453 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
454 {
455 errors += 1;
456 }
457 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
458 }
459 if errors > 0 {
460 artifact = Artifact::Nothing;
462 }
463 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
466}
467
468#[must_use]
478pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
479 let mut sess = Session::new(opts.clone());
480 if opts.emit != EmitKind::Ir {
481 return failure(format!(
482 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
483 the C in front of it became",
484 opts.emit.as_str()
485 ));
486 }
487 let bytes = match fs.read(Path::new(name)) {
488 Ok(bytes) => bytes,
489 Err(e) => return failure(format!("{name}: {e}")),
490 };
491 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
492 return failure(format!("{name}: this is not text, so it is not IR"));
493 };
494
495 let module = match rucc_ir::parse(text, &mut sess.interner) {
496 Ok(module) => module,
497 Err(error) => {
498 return failure(format!("{name}:{}: {}", error.line, error.message));
499 }
500 };
501 let mut diagnostics: Vec<Diagnostic> = Vec::new();
502 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
503 for error in errors {
504 diagnostics.push(invalid(&format!("invalid IR, {error}")));
505 }
506 }
507 let mut messages = Vec::with_capacity(diagnostics.len());
508 for diag in &diagnostics {
509 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
510 }
511 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
512 let artifact = if errors > 0 {
513 Artifact::Nothing
514 } else {
515 Artifact::Text(rucc_ir::print(&module, &sess.interner))
516 };
517 Compiled {
519 artifact,
520 messages,
521 errors,
522 fired: Fired::new(),
523 pressure: Pressure::new(),
524 lowerings: Lowerings::new(),
525 dumps: Vec::new(),
526 remarks: String::new(),
527 deps: Vec::new(),
528 temps: Temps::default(),
529 }
530}
531
532fn instrument(
555 module: &mut rucc_ir::Module,
556 names: &mut Interner,
557 opts: &Options,
558) -> Result<Instrumented, Vec<Diagnostic>> {
559 if !opts.safety.instruments() {
560 return Ok(Instrumented::default());
561 }
562 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
563 checks.freed = rucc_safety::ending::checks(module, names);
571 let interposed = rucc_safety::redirect(module, names);
576 let crossings = rucc_safety::witness(module, names);
579 match rucc_ir::verify(module, names) {
580 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
581 Err(errors) => Err(errors
582 .iter()
583 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
584 .collect()),
585 }
586}
587
588#[derive(Clone, Copy, Debug, Default)]
594struct Instrumented {
595 checks: rucc_safety::Counts,
597 interposed: usize,
599 crossings: rucc_safety::Sites,
601}
602
603fn optimize(
615 module: &mut rucc_ir::Module,
616 names: &Interner,
617 target: &TargetInfo,
618 opts: &Options,
619 file: &str,
620 dumps: &mut Vec<rucc_opt::Dump>,
621 remarks: &mut String,
622) -> Result<(), Vec<Diagnostic>> {
623 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
624 settings.interposition = match opts.interposition {
630 true => replaceable(target, opts),
631 false => IrPic::Executable,
632 };
633 settings.toggles.clone_from(&opts.passes);
634 settings.fuel = opts.pass_fuel.iter().cloned().collect();
635 settings.global_fuel = opts.pass_fuel_global;
636 settings.verify |= opts.verify_each;
637 for (on, spec) in &opts.pass_gates {
638 if let Err(why) = settings.gates.add(*on, spec) {
641 return Err(vec![internal(&why)]);
642 }
643 }
644 for spec in &opts.dump_ir {
645 if let Err(why) = settings.dumps.add(spec) {
648 return Err(vec![internal(&why)]);
649 }
650 }
651 let mut wants = rucc_opt::Wants::none();
652 for spec in &opts.opt_info {
653 if let Err(why) = wants.add(spec) {
656 return Err(vec![internal(&why)]);
657 }
658 }
659 let report = rucc_opt::run(module, names, &settings);
660 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
661 dumps.extend(report.dumps);
662 match report.broke.is_empty() {
663 true => Ok(()),
664 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
665 }
666}
667
668fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
702 match (target.tuple.os().object_format(), opts.pic) {
703 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
704 _ => IrPic::Executable,
705 }
706}
707
708fn generate(
709 module: &mut rucc_ir::Module,
710 names: &mut Interner,
711 target: &TargetInfo,
712 opts: &Options,
713 recording: &mut Recording<'_>,
714 assembly: &mut Option<String>,
715) -> Result<Artifact, Vec<Diagnostic>> {
716 let Some(machine) = Machine::for_target(target) else {
717 return Err(vec![unsupported(&format!(
718 "there is no back end for {} in this compiler yet, so there is nothing to generate",
719 target.tuple
720 ))]);
721 };
722 if opts.protector != Protector::None && machine.conv.guard.is_none() {
727 return Err(vec![unsupported(&format!(
728 "{} is not supported for {} yet, because the stack protector on that target is not \
729 the one this compiler writes",
730 opts.protector, target.tuple
731 ))]);
732 }
733 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
739 return Err(vec![unsupported(&format!(
740 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
741 for it there is not the note this compiler writes",
742 opts.control, target.tuple
743 ))]);
744 }
745 let profile = match machine.conv.trace {
751 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
752 None if opts.profile => {
753 return Err(vec![unsupported(&format!(
754 "-pg is not supported for {} yet, because the profiler's hook on that target is \
755 not the one this compiler calls",
756 target.tuple
757 ))]);
758 }
759 None => None,
760 };
761 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
766 return Err(vec![unsupported(&format!(
767 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
768 the room is there is not the section this compiler writes",
769 target.tuple
770 ))]);
771 }
772 let flags = pipeline::Flags {
773 frame_pointer: opts.frame_pointer,
774 red_zone: opts.red_zone,
775 stack_clash: opts.stack_clash,
776 landing: opts.control.branch(),
777 profile: match profile {
778 None => pipeline::Profile::No,
779 Some(true) => pipeline::Profile::Early,
780 Some(false) => pipeline::Profile::Late,
781 },
782 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
783 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
790 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
795 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
800 accurate: opts.cycle_accurate_model,
802 };
803
804 if opts.safety.instruments() {
813 rucc_opt::heap::annotate(module, names);
823 rucc_safety::handover::arrange(module);
830 rucc_safety::lower(module, names);
831 if let Err(errors) = rucc_ir::verify(module, names) {
832 return Err(errors
833 .iter()
834 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
835 .collect());
836 }
837 }
838
839 let elsewhere = Elsewhere::of(module, replaceable(target, opts));
847
848 let mut funcs = Vec::new();
849 let mut complaints = Vec::new();
850 for id in module.funcs() {
851 if module[id].is_declaration() {
852 continue;
853 }
854 match pipeline::compile_recording(
855 &mut module[id],
856 names,
857 &machine,
858 &elsewhere,
859 flags,
860 recording,
861 ) {
862 Ok(func) => funcs.push(func),
863 Err(why) => {
864 let name = names.resolve(module[id].name).to_owned();
865 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
868 let said = format!("cannot generate code for '{name}': {why}");
869 complaints.push(unsupported_at(&said, span));
870 }
871 }
872 }
873 if !complaints.is_empty() {
874 return Err(complaints);
875 }
876 let (globals, aliases) = match opts.emit {
882 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
883 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
884 rucc_asm::aliases(module, names).map_err(refused)?,
885 ),
886 _ => (rucc_asm::Globals::default(), Vec::new()),
887 };
888 let unwind = opts.unwinds();
892 match opts.emit {
893 EmitKind::Asm => {
894 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
895 .map(Artifact::Text)
896 .map_err(refused)
897 }
898 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
902 if opts.save_temps.wanted() {
903 let listing = rucc_asm::print(
904 &funcs,
905 &globals,
906 &aliases,
907 names,
908 target,
909 unwind,
910 output(opts, target),
911 );
912 *assembly = Some(listing.map_err(refused)?);
913 }
914 let text = rucc_asm::assemble(&funcs, names, target, unwind).map_err(refused)?;
915 let data = globals.image();
916 let bytes = rucc_object::write(&text, &data, &aliases, target, output(opts, target))
919 .map_err(wrote)?;
920 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
925 Ok(Artifact::Object { bytes, defines })
926 }
927 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
928 }
929}
930
931fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
943 let mut features = 0;
944 if target.tuple.arch() == Arch::X86_64 {
945 if opts.control.branch() {
946 features |= rucc_object::Property::IBT;
947 }
948 if opts.control.ret() {
949 features |= rucc_object::Property::SHSTK;
950 }
951 }
952 rucc_object::Output {
953 sections: rucc_object::Sections {
954 functions: opts.function_sections,
955 data: opts.data_sections,
956 },
957 property: rucc_object::Property { features },
958 }
959}
960
961fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
967 match why {
968 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
969 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
970 }
971}
972
973fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
980 match why {
981 rucc_asm::Error::Thread { .. }
982 | rucc_asm::Error::IFunc { .. }
983 | rucc_asm::Error::Frame { .. } => {
984 vec![unsupported(&why.to_string())]
985 }
986 _ => vec![internal(&why.to_string())],
987 }
988}
989
990fn unsupported(message: &str) -> Diagnostic {
996 unsupported_at(message, Span::DUMMY)
997}
998
999fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1005 Diagnostic::error(message.to_owned(), span)
1006 .with_code("E0653")
1007 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1008}
1009
1010fn invalid(message: &str) -> Diagnostic {
1012 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1013}
1014
1015fn internal(message: &str) -> Diagnostic {
1017 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1018 .with_code("E0652")
1019 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1020}
1021
1022fn failure(message: String) -> Compiled {
1025 Compiled {
1026 artifact: Artifact::Nothing,
1027 messages: vec![format!("rucc: error: {message}")],
1028 errors: 1,
1029 fired: Fired::new(),
1030 pressure: Pressure::new(),
1031 lowerings: Lowerings::new(),
1032 dumps: Vec::new(),
1033 remarks: String::new(),
1034 deps: Vec::new(),
1035 temps: Temps::default(),
1036 }
1037}
1038
1039#[cfg(test)]
1040mod tests {
1041 use rucc_session::{MemoryFileSystem, Std};
1042 use rucc_target::Triple;
1043
1044 use super::*;
1045
1046 fn options() -> Options {
1047 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1048 opts.emit = EmitKind::Tast;
1049 opts
1050 }
1051
1052 fn run(opts: &Options, source: &str) -> Compiled {
1053 let mut fs = MemoryFileSystem::new();
1054 fs.insert("/main.c", source.to_owned().into_bytes());
1055 compile(opts, "/main.c", &fs)
1056 }
1057
1058 fn freestanding() -> Options {
1062 let mut opts = options();
1063 opts.hosted = false;
1064 opts.search.push_system(rucc_session::runtime::DIR);
1065 opts
1066 }
1067
1068 fn shipped(source: &str) -> String {
1070 let result = run(&freestanding(), source);
1071 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1072 result.text().to_owned()
1073 }
1074
1075 fn tast(source: &str) -> String {
1077 let result = run(&options(), source);
1078 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1079 result.text().to_owned()
1080 }
1081
1082 #[test]
1083 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1084 let text = shipped(concat!(
1085 "#include <stdarg.h>\n",
1086 "int sum(int n, ...) {\n",
1087 " va_list ap, copy;\n",
1088 " va_start(ap, n);\n",
1089 " va_copy(copy, ap);\n",
1090 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1091 " va_end(ap);\n",
1092 " va_end(copy);\n",
1093 " return total;\n",
1094 "}\n",
1095 ));
1096 assert!(text.contains("va-start"), "{text}");
1097 assert!(text.contains("va-copy"), "{text}");
1098 assert!(text.contains("va-arg"), "{text}");
1099 assert!(text.contains("va-end"), "{text}");
1100 }
1101
1102 #[test]
1106 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1107 let text = shipped(concat!(
1108 "#define __need___va_list\n",
1109 "#include <stdarg.h>\n",
1110 "int vprint(const char *f, __gnuc_va_list ap);\n",
1111 "#ifdef va_start\n",
1112 "#error va_start should not be defined\n",
1113 "#endif\n",
1114 "#ifdef _VA_LIST_DEFINED\n",
1115 "#error va_list should not have been made\n",
1116 "#endif\n",
1117 ));
1118 assert!(text.contains("vprint"), "{text}");
1119 }
1120
1121 #[test]
1124 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1125 let text = shipped(concat!(
1126 "#define __need_size_t\n",
1127 "#include <stddef.h>\n",
1128 "#ifdef offsetof\n",
1129 "#error offsetof should not be defined yet\n",
1130 "#endif\n",
1131 "#define __need_ptrdiff_t\n",
1132 "#include <stddef.h>\n",
1133 "#include <stddef.h>\n",
1134 "size_t a;\n",
1135 "ptrdiff_t b;\n",
1136 "wchar_t c;\n",
1137 "max_align_t d;\n",
1138 "void *e = NULL;\n",
1139 "struct P { int x; long y; };\n",
1140 "size_t f = offsetof(struct P, y);\n",
1141 ));
1142 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1143 assert!(text.contains("decl #1 b : long"), "{text}");
1144 }
1145
1146 #[test]
1147 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1148 let text = shipped(concat!(
1149 "#include <limits.h>\n",
1150 "#include <float.h>\n",
1151 "int bits = CHAR_BIT;\n",
1152 "long big = LONG_MAX;\n",
1153 "int low = INT_MIN;\n",
1154 "int radix = FLT_RADIX;\n",
1155 "int digits = DBL_MANT_DIG;\n",
1156 ));
1157 assert!(text.contains("const 8 : int"), "{text}");
1158 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1159 assert!(text.contains("const 2 : int"), "{text}");
1160 assert!(text.contains("const 53 : int"), "{text}");
1161 }
1162
1163 #[test]
1167 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1168 let text = shipped(concat!(
1169 "#include <stdint.h>\n",
1170 "int64_t a = INT64_C(1);\n",
1171 "uint_least16_t b;\n",
1172 "intptr_t c;\n",
1173 "uintmax_t d = UINTMAX_MAX;\n",
1174 "int wide = sizeof(int_fast64_t);\n",
1175 ));
1176 assert!(text.contains("decl #0 a : long"), "{text}");
1177 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1178 assert!(text.contains("decl #2 c : long"), "{text}");
1179 }
1180
1181 #[test]
1192 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1193 let text = shipped(concat!(
1194 "#include <mmintrin.h>\n",
1195 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1196 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1197 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1198 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1199 "void done(void) { _mm_empty(); }\n",
1200 ));
1201 assert!(text.contains("add"), "{text}");
1202 assert!(text.contains("pack"), "{text}");
1203 assert!(text.contains("shift"), "{text}");
1204 }
1205
1206 #[test]
1211 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1212 let text = shipped(concat!(
1213 "#include <mm_malloc.h>\n",
1214 "void *get(void) { return _mm_malloc(64, 16); }\n",
1215 "void put(void *p) { _mm_free(p); }\n",
1216 ));
1217 assert!(text.contains("get"), "{text}");
1218 assert!(text.contains("put"), "{text}");
1219 }
1220
1221 #[test]
1233 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1234 let text = shipped(concat!(
1235 "#include <xmmintrin.h>\n",
1236 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1237 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1238 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1239 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1240 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1241 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1242 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1243 "void *room(void) { return _mm_malloc(64, 16); }\n",
1244 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1245 ));
1246 assert!(text.contains("add"), "{text}");
1247 assert!(text.contains("mask"), "{text}");
1248 assert!(text.contains("pick"), "{text}");
1249 assert!(text.contains("wide"), "{text}");
1250 }
1251
1252 #[test]
1259 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1260 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1261 for absent in [
1262 "_mm_sqrt_ps",
1263 "_mm_sqrt_ss",
1264 "_mm_rsqrt_ps",
1265 "_mm_rsqrt_ss",
1266 "_mm_getcsr",
1267 "_mm_setcsr",
1268 ] {
1269 let defined = text.contains(&format!("{absent}("));
1270 assert!(!defined, "{absent} is defined and the header says it is not");
1271 assert!(text.contains(absent), "{absent} is absent and unexplained");
1272 }
1273 }
1274
1275 #[test]
1276 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1277 let text = shipped(concat!(
1278 "#include <emmintrin.h>\n",
1279 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1280 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1281 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1282 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1283 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1284 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1285 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1286 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1287 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1288 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1289 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1290 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1291 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1292 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1293 ));
1294 assert!(text.contains("wide"), "{text}");
1295 assert!(text.contains("pack"), "{text}");
1296 assert!(text.contains("near"), "{text}");
1297 assert!(text.contains("half"), "{text}");
1298 }
1299
1300 #[test]
1304 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1305 let text = shipped(concat!(
1306 "#include <immintrin.h>\n",
1307 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1308 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1309 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1310 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1311 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1312 "}\n",
1313 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1314 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1315 ));
1316 assert!(text.contains("matching"), "{text}");
1317 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1318 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1319 }
1320
1321 #[test]
1325 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1326 let text = shipped(concat!(
1327 "#include <immintrin.h>\n",
1328 "#include <emmintrin.h>\n",
1329 "#include <immintrin.h>\n",
1330 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1331 ));
1332 assert!(text.contains("twice"), "{text}");
1333 }
1334
1335 #[test]
1339 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1340 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1341 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1342 let defined = text.contains(&format!("{absent}("));
1343 assert!(!defined, "{absent} is defined and the header says it is not");
1344 assert!(text.contains(absent), "{absent} is absent and unexplained");
1345 }
1346 }
1347
1348 #[test]
1349 fn the_three_formality_headers_still_have_to_work() {
1350 let text = shipped(concat!(
1351 "#include <stdbool.h>\n",
1352 "#include <stdalign.h>\n",
1353 "#include <iso646.h>\n",
1354 "#include <stdnoreturn.h>\n",
1355 "int t = true and not false;\n",
1356 "_Alignas(16) char buf[16];\n",
1357 "int a = alignof(long);\n",
1358 ));
1359 assert!(text.contains("decl #0 t : int"), "{text}");
1360 assert!(text.contains("const 8 : unsigned long"), "{text}");
1361 }
1362
1363 #[test]
1371 fn every_shipped_header_can_be_included_twice() {
1372 let once: String = rucc_session::runtime::names()
1373 .iter()
1374 .map(|name| format!("#include <{name}>\n"))
1375 .collect();
1376 let twice = once.repeat(2);
1377 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1378 }
1379
1380 #[test]
1381 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1382 let fs = MemoryFileSystem::new();
1383 let result = compile(&options(), "/nope.c", &fs);
1384 assert!(result.failed());
1385 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1386 assert!(result.text().is_empty());
1387 }
1388
1389 #[test]
1390 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1391 let text = tast("int x = 1;\n");
1392 let expected = "\
1393decl #0 x : int object external static defined
1394 init
1395 +0
1396 const 1 : int
1397";
1398 assert_eq!(text, expected);
1399 }
1400
1401 #[test]
1402 fn the_macros_are_expanded_before_anything_is_parsed() {
1403 let text = tast("#define N 2\nint a[N];\n");
1407 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1408 }
1409
1410 #[test]
1416 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1417 let text = tast(concat!(
1418 "#pragma pack(4)\n",
1419 "struct s { int a; };\n",
1420 "#pragma pack()\n",
1421 "int b;\n",
1422 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1423 ));
1424 assert!(text.contains("decl #0 b : int"), "{text}");
1425 assert!(text.contains("decl #1 c : int"), "{text}");
1426 }
1427
1428 #[test]
1436 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1437 tast(concat!(
1438 "struct A { char c; int i; } __attribute__((packed));\n",
1439 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1440 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1441 "struct B { char c; int i; } __attribute__((aligned));\n",
1444 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1445 "struct C { char c; int i __attribute__((packed)); };\n",
1446 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1447 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1448 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1449 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1450 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1451 "struct E { char c; _Alignas(8) int i; };\n",
1452 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1453 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1454 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1455 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1456 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1459 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1460 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1461 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1462 "struct I { [[gnu::packed]] char c; int i; };\n",
1465 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1466 "struct J { char c; [[gnu::packed]] int i; };\n",
1467 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1468 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1469 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1470 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1471 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1472 "union L { char c; int i; } __attribute__((packed));\n",
1473 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1474 "struct O { char c; int i; } __attribute__((__packed__));\n",
1478 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
1479 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
1480 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
1481 ));
1482 }
1483
1484 #[test]
1497 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
1498 let text = tast(concat!(
1499 "struct one { int x; };\n",
1500 "struct two { long y; };\n",
1501 "typedef union { struct one *a; struct two *b; void *any; }\n",
1502 " __attribute__((__transparent_union__)) arg;\n",
1503 "int takes(arg v);\n",
1504 "int f(struct one *p, struct two *q, char *c) {\n",
1505 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
1506 "}\n",
1507 "int takes(struct one *p);\n",
1509 "int (*as_a_member)(struct one *) = takes;\n",
1510 "int (*as_the_union)(arg) = takes;\n",
1511 ));
1512 assert!(text.contains("compound-literal"), "{text}");
1513 }
1514
1515 #[test]
1521 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
1522 let text = tast(concat!(
1523 "struct sockaddr { int family; };\n",
1524 "struct sockaddr_in { int family; int addr; };\n",
1525 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
1526 " addr_arg __attribute__((__transparent_union__));\n",
1527 "int bind_to(int fd, addr_arg where);\n",
1528 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
1529 ));
1530 assert!(text.contains("compound-literal"), "{text}");
1531 }
1532
1533 #[test]
1541 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
1542 let result = run(
1543 &options(),
1544 concat!(
1545 "union wider { int small; double large; } __attribute__((transparent_union));\n",
1546 "struct plain { int x; } __attribute__((transparent_union));\n",
1547 ),
1548 );
1549 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
1550 assert!(!result.failed(), "{:?}", result.messages);
1551 for message in &result.messages {
1552 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
1553 }
1554 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
1555 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
1556 }
1557
1558 #[test]
1567 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
1568 let packed = body(concat!(
1569 "struct P { char c; int v; } __attribute__((packed));\n",
1570 "int f(struct P *p) { return p->v; }\n",
1571 ));
1572 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
1573 let plain = body(concat!(
1575 "struct P { char c; int v; };\n",
1576 "int f(struct P *p) { return p->v; }\n",
1577 ));
1578 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
1579 }
1580
1581 #[test]
1588 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
1589 let stepped = body(concat!(
1590 "struct P { char c; int v[4]; } __attribute__((packed));\n",
1591 "int f(struct P *p, int i) { return p->v[i]; }\n",
1592 ));
1593 assert!(stepped.contains(", align 1,"), "{stepped}");
1594 assert!(!stepped.contains(", align 4,"), "{stepped}");
1595 let nested = body(concat!(
1596 "struct Inner { int v; };\n",
1597 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
1598 "int f(struct P *p) { return p->in.v; }\n",
1599 ));
1600 assert!(nested.contains(", align 1,"), "{nested}");
1601 assert!(!nested.contains(", align 4,"), "{nested}");
1602 }
1603
1604 #[test]
1613 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
1614 tast(concat!(
1615 "int v __attribute__((aligned(64)));\n",
1616 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
1617 "__attribute__((aligned(32))) int w;\n",
1620 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
1621 "[[gnu::aligned(16)]] int x;\n",
1622 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
1623 "int y __attribute__((aligned(2)));\n",
1626 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
1627 "void f(void) { int a __attribute__((aligned(128)));\n",
1629 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
1630 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1633 "void g(void) __attribute__((aligned(256)));\n",
1636 "void g(void) {}\n",
1637 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
1638 ));
1639 }
1640
1641 #[test]
1645 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
1646 let text = asm(concat!(
1647 "int v __attribute__((aligned(64)));\n",
1648 "void g(void) __attribute__((aligned(256)));\n",
1649 "void g(void) {}\n",
1650 "void plain(void) {}\n",
1651 ));
1652 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
1653 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1654 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
1655 }
1656
1657 #[test]
1666 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
1667 tast(concat!(
1668 "typedef int L __attribute__((aligned(2)));\n",
1669 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
1670 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
1671 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
1673 "struct T { char c; L x; };\n",
1674 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
1675 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
1676 "typedef int H __attribute__((aligned(16)));\n",
1678 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
1679 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
1680 "struct U { char c; H x; };\n",
1681 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
1682 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
1683 "typedef L M __attribute__((aligned(8)));\n",
1686 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
1687 "typedef L N;\n",
1690 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
1691 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1693 ));
1694 let text = asm(concat!(
1695 "typedef int L __attribute__((aligned(2)));\n",
1696 "typedef int H __attribute__((aligned(16)));\n",
1697 "L low;\n",
1698 "H high;\n",
1699 ));
1700 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
1701 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
1702 }
1703
1704 #[test]
1712 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
1713 tast(concat!(
1714 "typedef int __attribute__((vector_size(16))) v4si;\n",
1715 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
1716 "typedef char __attribute__((vector_size(16))) v16qi;\n",
1717 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
1718 "typedef int __attribute__((vector_size(4))) v1si;\n",
1721 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
1722 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
1724 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
1725 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
1726 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
1727 "v4si g;\n",
1730 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
1731 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
1732 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
1735 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
1737 ));
1738 }
1739
1740 #[test]
1750 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
1751 tast(concat!(
1752 "typedef int __attribute__((vector_size(8))) v2si;\n",
1753 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
1754 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
1755 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
1757 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
1758 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
1761 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
1762 ));
1763 }
1764
1765 #[test]
1773 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
1774 let result = run(
1775 &options(),
1776 concat!(
1777 "typedef int __attribute__((vector_size(16))) v4si;\n",
1778 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
1779 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
1780 " v4si v = { 1, 2, 3, 4 };\n",
1781 " v[0] = n;\n",
1782 " v[1] += n;\n",
1783 " v[2]++;\n",
1784 " *&v[3] = n;\n",
1785 " v4ui shifted = a >> b;\n",
1787 " shifted <<= b;\n",
1788 " *out = v + (v4si)shifted + (1 << b);\n",
1791 "}\n",
1792 "void refused(const v4si c) {\n",
1795 " c[0] = 1;\n",
1796 "}\n",
1797 ),
1798 );
1799 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
1800 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
1801 }
1802
1803 #[test]
1810 fn a_record_that_asks_for_the_other_byte_order_is_refused_rather_than_laid_out_in_this_one() {
1811 let opts = options();
1812 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
1813 assert_eq!(
1814 run(&opts, big).messages,
1815 ["/main.c:1:36: error: 'scalar_storage_order' is not implemented yet [E0688]\n\
1816 /main.c:1:36: note: every scalar in this record would be read in the wrong byte \
1817 order"]
1818 );
1819
1820 let armoured =
1821 "struct s { int i; } __attribute__((__scalar_storage_order__(\"little-endian\")));\n";
1822 let messages = run(&opts, armoured).messages;
1823 assert!(messages[0].contains("[E0688]"), "{messages:?}");
1824
1825 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
1828 assert!(run(&opts, front).messages[0].contains("[E0688]"), "{front}");
1829 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
1830 assert!(run(&opts, standard).messages[0].contains("[E0688]"), "{standard}");
1831 }
1832
1833 #[test]
1843 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
1844 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
1846 assert_eq!(
1847 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
1848 1
1849 );
1850 assert_eq!(
1851 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
1852 1
1853 );
1854 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
1855 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
1857 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
1858 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
1860 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
1861 }
1862
1863 fn bit_field_byte(record: &str) -> u64 {
1865 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
1866 let body = body(&source);
1867 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
1868 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
1869 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
1870 }
1871
1872 #[test]
1878 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
1879 tast(concat!(
1880 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
1881 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
1882 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
1883 "struct b { char c; __attribute__((packed)) int i; };\n",
1884 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
1885 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
1886 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
1887 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
1888 ));
1889 }
1890
1891 #[test]
1897 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
1898 tast(concat!(
1899 "#pragma pack(1)\n",
1900 "struct A { char c; int i; };\n",
1901 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1902 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1903 "#pragma pack()\n",
1904 "struct B { char c; int i; };\n",
1905 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
1906 "#pragma pack(2)\n",
1907 "struct C { char c; int i; double d; };\n",
1908 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
1909 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
1910 "struct K { char c; int i __attribute__((aligned(8))); };\n",
1912 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
1913 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
1914 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
1916 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
1917 "#pragma pack()\n",
1918 "#pragma pack(push, 1)\n",
1919 "struct D { char c; short s; };\n",
1920 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
1921 "#pragma pack(pop)\n",
1922 "struct E { char c; short s; };\n",
1923 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
1924 "struct H { char c;\n",
1926 "#pragma pack(1)\n",
1927 " int i; };\n",
1928 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
1929 "#pragma pack(1)\n",
1930 "struct I { char c;\n",
1931 "#pragma pack()\n",
1932 " int i; };\n",
1933 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1934 "#pragma pack()\n",
1935 "#pragma pack(push, 8)\n",
1937 "#pragma pack(push, 1)\n",
1938 "struct P { char c; int i; };\n",
1939 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
1940 "#pragma pack(pop)\n",
1941 "struct Q { char c; int i; };\n",
1942 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
1943 "#pragma pack(pop)\n",
1944 "#pragma pack(16)\n",
1946 "struct R { char c; int i; };\n",
1947 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
1948 "#pragma pack()\n",
1949 "#pragma pack(1)\n",
1950 "struct S { char c; int i : 5; int j : 20; };\n",
1951 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
1952 "union T { char c; int i; };\n",
1953 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
1954 "#pragma pack()\n",
1955 ));
1956 }
1957
1958 #[test]
1962 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
1963 let result = run(
1964 &options(),
1965 concat!(
1966 "#pragma pack 4\n",
1967 "#pragma pack(pop)\n",
1968 "#pragma pack(3)\n",
1969 "#pragma pack(1) junk\n",
1970 "#pragma pack(push, 1\n",
1971 "#pragma pack(x)\n",
1972 "#pragma pack(0)\n",
1975 "#pragma pack(push)\n",
1976 "struct s { char c; int i; };\n",
1977 "#pragma pack(pop)\n",
1978 "#pragma pack(pop, foo)\n",
1979 ),
1980 );
1981 let expected = [
1982 "missing `(` after `#pragma pack` - ignored",
1983 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
1984 "alignment must be a small power of two, not 3",
1985 "junk at end of `#pragma pack`",
1986 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
1987 "unknown action `x` for `#pragma pack` - ignored",
1988 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
1989 ];
1990 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
1991 for (message, want) in result.messages.iter().zip(expected) {
1992 assert!(message.contains(want), "expected {want:?} in {message:?}");
1993 }
1994 }
1995
1996 #[test]
2000 fn the_wide_integer_answers_to_all_three_of_its_names() {
2001 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2002 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2003 assert!(text.contains("decl #1 b : __int128"), "{text}");
2004 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2005 }
2006
2007 #[test]
2008 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2009 let text = tast("long f(int a, long b) { return a + b; }\n");
2013 assert!(text.contains("convert arithmetic"), "{text}");
2014 }
2015
2016 #[test]
2017 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2018 for source in [
2019 "#error stop\n",
2020 "int f(void) { return 1 + ; }\n",
2021 "int f(void) { return undeclared; }\n",
2022 ] {
2023 let result = run(&options(), source);
2024 assert!(result.failed(), "expected this to fail:\n{source}");
2025 assert!(
2026 result.text().is_empty(),
2027 "a file that did not compile wrote a tree:\n{source}"
2028 );
2029 }
2030 }
2031
2032 #[test]
2033 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2034 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2038 assert_eq!(result.errors, 1, "{:?}", result.messages);
2039 }
2040
2041 #[test]
2042 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2043 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2047 assert_eq!(result.errors, 1, "{:?}", result.messages);
2048 }
2049
2050 #[test]
2051 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2052 let source = "int f(void) { char c = 300; return c; }\n";
2053 let plain = run(&options(), source);
2054 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2055 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2056 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2057
2058 let mut opts = options();
2059 opts.warnings_are_errors = true;
2060 let strict = run(&opts, source);
2061 assert!(strict.failed());
2062 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2063 for message in &strict.messages {
2064 assert!(!message.contains("warning:"), "{message}");
2065 }
2066 }
2067
2068 #[test]
2069 fn w_drops_the_warning_before_werror_can_promote_it() {
2070 let source = "int f(void) { char c = 300; return c; }\n";
2071 let mut opts = options();
2072 opts.warnings = false;
2073 let quiet = run(&opts, source);
2074 assert_eq!(quiet.messages, Vec::<String>::new());
2075 assert_eq!(quiet.errors, 0);
2076 assert!(!quiet.text().is_empty(), "and the file still compiles");
2077
2078 opts.warnings_are_errors = true;
2081 let both = run(&opts, source);
2082 assert_eq!(both.messages, Vec::<String>::new());
2083 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2084 }
2085
2086 #[test]
2087 fn the_dialect_reaches_the_keywords_and_the_checking() {
2088 let source = "typeof(1) x;\n";
2091 let mut opts = options();
2092 opts.std = Std::C23;
2093 opts.gnu_extensions = false;
2094 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2095
2096 opts.std = Std::C17;
2097 assert!(run(&opts, source).failed());
2098 }
2099
2100 #[test]
2101 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2102 let mut opts = options();
2103 opts.emit = EmitKind::Object;
2104 let result = run(&opts, "int x = 1;\n");
2105 assert!(!result.failed(), "{:?}", result.messages);
2106 assert!(result.text().is_empty());
2107 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2110 }
2111
2112 fn mir(source: &str) -> String {
2114 let mut opts = options();
2115 opts.emit = EmitKind::MirFinal;
2116 let result = run(&opts, source);
2117 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2118 result.text().to_owned()
2119 }
2120
2121 #[test]
2127 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2128 let text = mir("int add(int a, int b) { return a + b; }\n");
2129 assert!(text.starts_with("mfunc @add {"), "{text}");
2130 assert!(text.contains("x64.add_rr_32"), "{text}");
2131 assert!(text.contains("x64.ret"), "{text}");
2132 assert!(!text.contains('%'), "{text}");
2135 }
2136
2137 #[test]
2139 fn a_function_with_no_body_produces_no_machine_function() {
2140 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2141 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2142 assert!(text.contains("mfunc @f {"), "{text}");
2143 assert!(text.contains("x64.call"), "{text}");
2144 }
2145
2146 #[test]
2148 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2149 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2150 let first = text.find("mfunc @a").expect("the first function");
2151 let second = text.find("mfunc @b").expect("the second function");
2152 assert!(first < second, "{text}");
2153 }
2154
2155 #[test]
2157 fn the_target_decides_which_convention_the_generated_code_follows() {
2158 let mut opts = options();
2159 opts.emit = EmitKind::MirFinal;
2160 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2161 assert!(linux.contains("$rdi"), "{linux}");
2162
2163 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2164 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2165 assert!(windows.contains("$rcx"), "{windows}");
2166 assert!(!windows.contains("$rdi"), "{windows}");
2167 }
2168
2169 #[test]
2171 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2172 let mut opts = options();
2173 opts.emit = EmitKind::MirFinal;
2174 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2175 let result = run(&opts, "int f(int a) { return a; }\n");
2176 assert!(result.failed());
2177 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2178 assert!(result.text().is_empty());
2179 }
2180
2181 #[test]
2188 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2189 let mut opts = options();
2190 opts.emit = EmitKind::MirFinal;
2191 let source = "void a(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n\
2192 void b(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n";
2193 let result = run(&opts, source);
2194 assert!(result.failed());
2195 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2196 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2197 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2198 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2199 assert!(result.text().is_empty());
2200 }
2201
2202 #[test]
2210 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2211 let mut opts = options();
2212 opts.emit = EmitKind::MirFinal;
2213 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2214 let plain = run(&opts, source);
2215 assert!(!plain.failed(), "{:?}", plain.messages);
2216 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2217
2218 opts.stack_clash = true;
2219 let result = run(&opts, source);
2220 assert!(!result.failed(), "{:?}", result.messages);
2221 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2222 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2223 }
2224
2225 #[test]
2239 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
2240 let mut opts = options();
2241 opts.emit = EmitKind::MirFinal;
2242 let source =
2243 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
2244 let result = run(&opts, source);
2245 assert!(result.failed());
2246 assert!(
2247 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
2248 "{result:?}"
2249 );
2250 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
2251 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
2252 }
2253
2254 #[test]
2256 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
2257 let mut opts = options();
2258 opts.emit = EmitKind::MirFinal;
2259 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
2260 let result = run(&opts, source);
2261 assert!(result.failed());
2262 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
2263 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
2264 assert!(!note.contains("spec/17-milestones.md"), "{note}");
2265 }
2266
2267 #[test]
2269 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
2270 let source = "int f(int a) { return a; }\n";
2271 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
2272
2273 let mut opts = options();
2274 opts.emit = EmitKind::MirFinal;
2275 opts.frame_pointer = true;
2276 let kept = run(&opts, source).text().to_owned();
2277 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
2278 }
2279
2280 fn asm(source: &str) -> String {
2282 let mut opts = options();
2283 opts.emit = EmitKind::Asm;
2284 let result = run(&opts, source);
2285 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2286 result.text().to_owned()
2287 }
2288
2289 #[test]
2296 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
2297 let text = asm("int add(int a, int b) { return a + b; }\n");
2298 assert!(text.contains("\t.globl\tadd\n"), "{text}");
2299 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
2300 assert!(text.contains("\nadd:\n"), "{text}");
2301 assert!(text.contains("\taddl\t"), "{text}");
2302 assert!(text.contains("\tret\n"), "{text}");
2303 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
2304 assert!(text.contains(".note.GNU-stack"), "{text}");
2307 }
2308
2309 #[test]
2315 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
2316 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
2317 assert!(text.contains("\tcall\t*%"), "{text}");
2318 assert!(text.contains("\tcall\tg\n"), "{text}");
2319 assert!(text.contains("%rdi"), "{text}");
2323 }
2324
2325 #[test]
2329 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
2330 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
2331 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
2332 }
2333
2334 #[test]
2343 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
2344 let arms = "return 1; return 2;";
2345 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
2346 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
2347 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
2348 assert!(
2349 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2350 "{operator}: {text}"
2351 );
2352 assert!(!text.contains("\tset"), "{operator}: {text}");
2353 assert!(!text.contains("\ttest"), "{operator}: {text}");
2354 }
2355 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
2356 for (operator, jump) in unsigned {
2357 let source =
2358 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
2359 let text = asm(&source);
2360 assert!(
2361 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2362 "{operator}: {text}"
2363 );
2364 }
2365
2366 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
2369 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
2370 }
2371
2372 #[test]
2378 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
2379 let text = asm("int f(int a, int b) { return a < b; }\n");
2380 assert!(text.contains("\tsetl\t"), "{text}");
2381 }
2382
2383 fn optimized(source: &str) -> String {
2385 let mut opts = options();
2386 opts.emit = EmitKind::Asm;
2387 opts.opt_level = rucc_session::OptLevel::O2;
2388 let result = run(&opts, source);
2389 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2390 result.text().to_owned()
2391 }
2392
2393 #[test]
2403 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
2404 let arms: String =
2405 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
2406 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2407 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
2408 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
2409 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
2410 }
2411
2412 #[test]
2419 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
2420 let arms: String = (0..16)
2421 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
2422 .collect::<Vec<_>>()
2423 .join(" ");
2424 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2425 assert!(text.matches("\tcmp").count() > 1, "{text}");
2426 }
2427
2428 #[test]
2430 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2431 let text = asm("long f(void *p) { return (long)p; }\n");
2432 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2437 let mnemonic = line.split_whitespace().next().unwrap_or("");
2438 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2439 }
2440 }
2441
2442 #[test]
2446 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2447 let six = "long a, long b, long c, long d, long e, long f";
2448 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2449
2450 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2457 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2458
2459 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2463 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2464 let eight =
2465 "double a, double b, double c, double d, double e, double f, double g, double h";
2466 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2467 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2468 }
2469
2470 #[test]
2473 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2474 let six = "1, 2, 3, 4, 5, 6";
2475 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2476 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2477
2478 assert!(text.contains("\tmovq\t%"), "{text}");
2479 assert!(text.contains(", (%rsp)\n"), "{text}");
2480 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2481 assert!(text.contains("\tsubq\t$"), "{text}");
2483
2484 let narrow = "int g(int, int, int, int, int, int, int);\n";
2486 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2487 assert!(text.contains("\tmovl\t%"), "{text}");
2488 assert!(text.contains(", (%rsp)\n"), "{text}");
2489 }
2490
2491 #[test]
2494 fn a_variadic_call_counts_registers_and_not_arguments() {
2495 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2496 let decl = "int g(int, ...);\n";
2497 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2498
2499 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2500 assert!(text.contains("\tmovsd\t%"), "{text}");
2501 assert!(text.contains(", (%rsp)\n"), "{text}");
2502 }
2503
2504 #[test]
2509 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2510 let body =
2511 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2512 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2513
2514 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2517 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2518 assert!(!text.contains(", 0(%r"), "{text}");
2519 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2522 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2523
2524 assert!(text.contains("\tsubq\t$"), "{text}");
2526 }
2527
2528 #[test]
2531 fn va_start_writes_the_four_fields_the_psabi_describes() {
2532 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2533 let params = "int a, int b, int c, double d";
2534 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2535
2536 assert!(text.contains(" movl $24, "), "{text}");
2540 assert!(text.contains(" movl $64, "), "{text}");
2541 assert!(text.contains(", 8(%r"), "{text}");
2545 assert!(text.contains(", 16(%r"), "{text}");
2546 let frame: u32 = text
2547 .lines()
2548 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
2549 .expect("a variadic function takes a frame for the save area");
2550 let above = |line: &str| {
2551 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
2552 Some(at > frame)
2553 };
2554 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
2555 }
2556
2557 #[test]
2560 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
2561 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
2562 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
2563 let text = asm(&ints);
2564
2565 assert!(text.contains("$40, "), "{text}");
2568 assert!(text.contains(" cmpl "), "{text}");
2569 assert!(text.contains(" ja "), "{text}");
2573
2574 let arg = "__builtin_va_arg(ap, double)";
2575 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
2576 assert!(text.contains("$160, "), "the last vector slot: {text}");
2577 }
2578
2579 #[test]
2582 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
2583 let decl = "struct pair { long a, b; };\n";
2584 let body = "struct pair p = *q; return p.a + p.b;";
2585 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
2586
2587 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
2588 assert!(!text.contains("\tcall"), "{text}");
2589 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
2591 }
2592
2593 #[test]
2596 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
2597 let decl = "struct bytes { char a[8]; };\n";
2598 let body = "struct bytes p = *q; return p.a[0];";
2599 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
2600
2601 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
2603 }
2604
2605 #[test]
2608 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
2609 let decl = "struct wide { long a, b, c; };\n";
2610 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
2611
2612 assert!(!text.contains("memset"), "nothing calls the library: {text}");
2613 assert!(text.contains("\tmovq\t$0, ") || text.contains("$0, %"), "the zero: {text}");
2614 }
2615
2616 #[test]
2619 fn a_copy_too_large_to_unroll_calls_the_runtime() {
2620 let decl = "struct huge { char a[4096]; };\n";
2621 let mut opts = options();
2622 opts.emit = EmitKind::Asm;
2623 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
2624 let result = run(&opts, &source);
2625 assert!(!result.failed(), "{:?}", result.messages);
2626 let text = result.text();
2627 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
2628 assert!(text.contains("4096"), "the size travels: {text}");
2631 }
2632
2633 #[test]
2636 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
2637 let six = "long a, long b, long c, long d, long e, long f";
2638 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
2639 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
2640
2641 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
2645 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
2646 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
2647 }
2648
2649 #[test]
2651 fn the_target_decides_how_the_assembly_is_spelled() {
2652 let mut opts = options();
2653 opts.emit = EmitKind::Asm;
2654 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
2655 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
2656 assert!(text.contains("__TEXT,__text"), "{text}");
2657 assert!(text.contains("\n_f:\n"), "{text}");
2658 assert!(!text.contains(".note.GNU-stack"), "{text}");
2659 }
2660
2661 fn obj(source: &str) -> Vec<u8> {
2663 let mut opts = options();
2664 opts.emit = EmitKind::Object;
2665 let result = run(&opts, source);
2666 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2667 match result.artifact {
2668 Artifact::Object { bytes, .. } => bytes,
2669 other => panic!("expected an object, got {other:?}"),
2670 }
2671 }
2672
2673 #[test]
2679 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
2680 let bytes = obj("int add(int a, int b) { return a + b; }\n");
2681 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
2682 let text = asm("int add(int a, int b) { return a + b; }\n");
2683 assert!(
2684 text.contains("\taddl\t"),
2685 "and the listing of it is the same instructions:\n{text}"
2686 );
2687 }
2688
2689 #[test]
2691 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
2692 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
2693 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
2694 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
2695 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
2696 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
2699 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
2700 assert!(!text.contains(".globl\thidden"), "{text}");
2701 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2704 }
2705
2706 #[test]
2713 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
2714 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
2715 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
2716 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
2717
2718 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
2721 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
2722
2723 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
2726 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
2727
2728 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
2730 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
2731 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
2732 }
2733
2734 #[test]
2736 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
2737 let text = asm("const char *f(void) { return \"hi\"; }\n");
2738 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
2739 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2740 let label = text
2741 .lines()
2742 .find(|line| line.starts_with(".Lstr"))
2743 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
2744 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
2745 }
2746
2747 #[test]
2749 fn an_address_in_an_initializer_is_left_to_the_linker() {
2750 let source = "int counter;\nint *p = &counter;\n";
2751 let text = asm(source);
2752 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
2753 let bytes = obj(source);
2756 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
2757 }
2758
2759 #[test]
2768 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
2769 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
2772 struct m { void (*x)(void); void (*y)(void); };\n\
2773 const struct m t = { a, b };\n");
2774 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
2775 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
2776
2777 let text =
2780 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
2781 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
2782
2783 let text = asm("const int fixed = 7;\n");
2785 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2786 }
2787
2788 #[test]
2795 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
2796 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
2797 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
2800 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
2801 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
2804 assert!(text.contains("%fs:0"), "{text}");
2805 }
2806
2807 #[test]
2813 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
2814 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
2815 assert!(text.contains("movq\t%fs:0, "), "{text}");
2816 assert!(!text.contains("GOTTPOFF"), "{text}");
2818 }
2819
2820 #[test]
2831 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
2832 for (locality, wanted) in
2833 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
2834 {
2835 let source =
2836 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
2837 let text = asm(&source);
2838 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
2839 }
2840 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
2842 assert!(text.contains("\tprefetcht0\t"), "{text}");
2843 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
2846 assert!(text.contains("\tprefetcht0\t"), "{text}");
2847 assert!(!text.contains("prefetchw"), "{text}");
2848 }
2849
2850 #[test]
2861 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
2862 let text = asm("void stop(void) { __builtin_trap(); }\n");
2863 assert!(text.contains("\tud2\n"), "{text}");
2864 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
2865
2866 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
2867 assert!(text.contains("\tud2\n"), "{text}");
2868 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
2869 }
2870
2871 #[test]
2883 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
2884 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
2885 assert!(!text.contains("assume_aligned"), "{text}");
2886 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
2887
2888 let source = "unsigned long width(void);\n\
2889 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
2890 let text = asm(source);
2891 assert!(!text.contains("assume_aligned"), "{text}");
2892 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
2893 }
2894
2895 #[test]
2905 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
2906 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
2907 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
2908 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
2909 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
2910
2911 let walk = |depth: u32| {
2912 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
2913 asm(&source).matches("movq\t(%r").count()
2914 };
2915 assert_eq!(walk(1), 1, "one link is one load");
2916 assert_eq!(walk(3), 3, "three links are three loads");
2917 }
2918
2919 #[test]
2929 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
2930 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
2931 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
2932 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
2933 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
2934
2935 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
2936 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
2937 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
2938 }
2939
2940 #[test]
2951 fn a_depth_that_is_not_a_small_constant_is_refused() {
2952 let mut opts = options();
2953 opts.emit = EmitKind::Ir;
2954 for source in [
2955 "void *up(int n) { return __builtin_return_address(n); }\n",
2956 "void *up(void) { return __builtin_frame_address(1000); }\n",
2957 ] {
2958 let messages = run(&opts, source).messages;
2959 let named = messages.iter().any(|m| m.contains("E0705"));
2960 assert!(named, "expected a refusal in {messages:?}");
2961 }
2962 }
2963
2964 #[test]
2976 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
2977 let text =
2978 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
2979 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
2980 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
2981 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
2982
2983 let plain = concat!(
2986 "extern void *alloca(__SIZE_TYPE__);\n",
2987 "void use(void *p);\n",
2988 "void f(unsigned long n) { use(alloca(n)); }\n",
2989 );
2990 let text = asm(plain);
2991 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
2992 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
2993
2994 let own = concat!(
2997 "static void *alloca(unsigned long n) { return 0; }\n",
2998 "void *f(unsigned long n) { return alloca(n); }\n",
2999 );
3000 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3001 }
3002
3003 #[test]
3013 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3014 let inner = "{ use(__builtin_alloca(n)); }";
3015 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3016 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3017 let text = asm(&source);
3018 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3022 let taking = line.contains("subq");
3023 let leaving = line.contains("%rbp");
3024 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3025 }
3026 }
3027 }
3028
3029 #[test]
3031 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3032 let source = "int callee(void); int g(void) { return callee(); }\n";
3036 let bytes = obj(source);
3037 assert!(
3038 bytes.windows(7).any(|w| w == b"callee\0"),
3039 "the object has to name the callee for the linker to find it"
3040 );
3041 let text = asm(source);
3042 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3043 }
3044
3045 #[test]
3051 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3052 let mut opts = options();
3053 opts.emit = EmitKind::Executable;
3055 let result = run(&opts, "int main(void) { return 0; }\n");
3056 assert_eq!(result.messages, Vec::<String>::new());
3057 match result.artifact {
3058 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3059 other => panic!("expected an object, got {other:?}"),
3060 }
3061 }
3062
3063 #[test]
3065 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3066 let mut opts = options();
3067 opts.emit = EmitKind::Object;
3068 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3069 let result = run(&opts, "int f(void) { return 0; }\n");
3070 assert!(result.failed(), "an object nobody can read is worse than a message");
3071 assert!(
3072 result.messages.iter().any(|m| m.contains("no object writer")),
3073 "{:?}",
3074 result.messages
3075 );
3076 }
3077
3078 fn ir(source: &str) -> String {
3080 let mut opts = options();
3081 opts.emit = EmitKind::Ir;
3082 let result = run(&opts, source);
3083 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3084 result.text().to_owned()
3085 }
3086
3087 fn errors(source: &str) -> Vec<String> {
3089 let mut opts = options();
3090 opts.emit = EmitKind::Ir;
3091 let result = run(&opts, source);
3092 assert!(result.failed(), "expected this to be refused:\n{source}");
3093 result.messages
3094 }
3095
3096 fn body(source: &str) -> String {
3098 let text = ir(source);
3099 let (_, rest) = text.split_once("{\n").expect("a function definition");
3100 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3101 body.to_owned()
3102 }
3103
3104 #[test]
3112 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3113 let source = "inline int f(int x) { return x + 1; }\n";
3114 let with = |flag: bool| {
3115 let mut opts = options();
3116 opts.emit = EmitKind::Ir;
3117 opts.gnu89_inline = flag;
3118 let result = run(&opts, source);
3119 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3120 result.text().to_owned()
3121 };
3122
3123 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3126
3127 assert!(with(true).contains("block0"), "a body: {}", with(true));
3130 }
3131
3132 #[test]
3139 fn an_access_through_a_type_names_the_type_it_went_through() {
3140 let source = "\
3141struct s { int a; float b; };\n\
3142union u { int i; float f; };\n\
3143int scalar(int *p) { return *p; }\n\
3144float member(struct s *p) { p->a = 1; return p->b; }\n\
3145int element(int *a, long i) { return a[i]; }\n\
3146float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3147 let text = ir(source);
3148 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3149 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3150 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3151 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3154 assert_eq!(named, 6, "six accesses: {text}");
3155 }
3156
3157 #[test]
3164 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3165 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3166 let mut opts = options();
3167 opts.emit = EmitKind::Ir;
3168 opts.strict_aliasing = false;
3169 let result = run(&opts, source);
3170 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3171 let text = result.text().to_owned();
3172 assert!(!text.contains("tbaa"), "not even the root: {text}");
3173 }
3174
3175 #[test]
3183 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3184 let mut opts = options();
3185 opts.emit = EmitKind::Ir;
3186 opts.std = Std::C89;
3187 let compiled = |source: &str| {
3188 let result = run(&opts, source);
3189 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3190 result.text().to_owned()
3191 };
3192
3193 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3194 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3195 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3196
3197 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3199 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3200 }
3201
3202 #[test]
3210 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3211 let mut opts = options();
3212 opts.emit = EmitKind::Ir;
3213 opts.std = Std::C89;
3214 let compiled = |source: &str| {
3215 let result = run(&opts, source);
3216 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3217 result.text().to_owned()
3218 };
3219
3220 let text = compiled("int f(void) { return g(); }\n");
3222 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3223 assert!(text.contains("i32"), "and it gives back an int: {text}");
3224
3225 let text = compiled("int f(char c) { return g(c); }\n");
3228 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3229
3230 let mut opts = options();
3233 opts.std = Std::C89;
3234 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3235 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3236 }
3237
3238 #[test]
3248 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3249 let mut opts = options();
3250 opts.emit = EmitKind::Ir;
3251 opts.std = Std::C89;
3252 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3253 .text()
3254 .to_owned();
3255 assert!(text.contains("func @f()"), "the caller is there: {text}");
3256 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3257 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3258 }
3259
3260 #[test]
3268 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3269 let mut opts = options();
3270 opts.emit = EmitKind::Ir;
3271 opts.std = Std::C89;
3272 let compiled = |source: &str| run(&opts, source).text().to_owned();
3273
3274 let text = compiled("f (c) unsigned char c; { return c; }\n");
3275 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3276 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3277 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3278
3279 let text = compiled("f (s) short s; { return s; }\n");
3281 assert!(text.contains("trunc.i16"), "cut down: {text}");
3282 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3283
3284 let text = compiled("f (x) float x; { return x * 2; }\n");
3287 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3288 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3289
3290 let text = compiled("int f(unsigned char c) { return c; }\n");
3293 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3294 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3295 }
3296
3297 #[test]
3306 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3307 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3309 let cases = [
3310 ("static counted;\n", ["", "error", "warning", "error"]),
3311 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3312 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3313 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3314 (
3315 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3316 ["warning", "error", "warning", "error"],
3317 ),
3318 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3319 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3320 ];
3321
3322 for (source, wanted) in cases {
3323 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3324 let mut opts = options();
3325 opts.std = std;
3326 opts.permissive = permissive;
3327 let said = run(&opts, source).messages.join("\n");
3328 let severity = if said.contains(": error: ") {
3329 "error"
3330 } else if said.contains(": warning: ") {
3331 "warning"
3332 } else {
3333 ""
3334 };
3335 let how = if permissive { " -fpermissive" } else { "" };
3336 assert_eq!(
3337 severity,
3338 wanted,
3339 "under -std={}{how}, {source} was answered with `{said}`",
3340 std.as_str()
3341 );
3342 if wanted.is_empty() {
3343 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3344 }
3345 }
3346 }
3347 }
3348
3349 #[test]
3358 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3359 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3360 let cases = [
3361 (
3362 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3363 "first argument to 'va_arg' not of type 'va_list'",
3364 ["error", "error", "error", "error"],
3365 ),
3366 (
3367 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3368 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3369 ["warning", "error", "warning", "error"],
3370 ),
3371 (
3372 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3373 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3374 cast",
3375 ["warning", "error", "warning", "error"],
3376 ),
3377 (
3378 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3379 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3380 ["warning", "error", "warning", "error"],
3381 ),
3382 ];
3383
3384 for (source, message, wanted) in cases {
3385 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3386 let mut opts = options();
3387 opts.std = std;
3388 opts.permissive = permissive;
3389 let said = run(&opts, source).messages.join("\n");
3390 let how = if permissive { " -fpermissive" } else { "" };
3391 assert!(
3392 said.contains(&format!(": {wanted}: {message}")),
3393 "under -std={}{how}, {source} was answered with `{said}`",
3394 std.as_str()
3395 );
3396 }
3397 }
3398 }
3399
3400 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3402 let mut opts = options();
3403 opts.emit = EmitKind::Ir;
3404 opts.safety = tier;
3405 let result = run(&opts, source);
3406 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3407 result.text().to_owned()
3408 }
3409
3410 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3411
3412 fn padded_ir(padding: Padding, source: &str) -> String {
3414 let mut opts = options();
3415 opts.emit = EmitKind::Ir;
3416 opts.safety = rucc_session::Safety::Detect;
3417 opts.padding = padding;
3418 let result = run(&opts, source);
3419 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3420 result.text().to_owned()
3421 }
3422
3423 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3424 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3425
3426 #[test]
3427 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3428 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3432 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3433 }
3434
3435 #[test]
3436 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3437 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3440 assert!(!text.contains("owns"), "{text}");
3441 }
3442
3443 #[test]
3444 fn a_member_of_a_union_owns_nothing_after_it() {
3445 let text = padded_ir(
3449 Padding::Ignored,
3450 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3451 );
3452 assert!(!text.contains("owns"), "{text}");
3453 }
3454
3455 #[test]
3456 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3457 let text = padded_ir(
3462 Padding::Ignored,
3463 "struct inner { char c; };\n\
3464 struct outer { struct inner in; int x; };\n\
3465 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3466 );
3467 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3468 }
3469
3470 #[test]
3471 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3472 let text = ir(READS_THROUGH_A_POINTER);
3476 assert!(!text.contains("check_"), "{text}");
3477 assert!(!text.contains("cap_of"), "{text}");
3478 }
3479
3480 #[test]
3481 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3482 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3483 assert!(text.contains("cap_of"), "{text}");
3484 assert!(text.contains("check_bounds"), "{text}");
3485 assert!(text.contains("check_live"), "{text}");
3486 assert!(text.contains("check_deriv"), "{text}");
3488 assert!(text.contains("check_type"), "{text}");
3490 }
3491
3492 #[test]
3493 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3494 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3498 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3499 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3500 }
3501 }
3502
3503 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3505 let mut opts = options();
3506 opts.emit = EmitKind::SafetySummary;
3507 opts.safety = tier;
3508 let result = run(&opts, source);
3509 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3510 result.text().to_owned()
3511 }
3512
3513 #[test]
3514 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3515 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3516 assert!(text.contains("\"tier\": \"detect\""), "{text}");
3517 assert!(
3519 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
3520 "{text}"
3521 );
3522 assert!(
3523 text.contains(
3524 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
3525 ),
3526 "{text}"
3527 );
3528 }
3529
3530 #[test]
3531 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
3532 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
3536 assert!(text.contains("\"tier\": \"off\""), "{text}");
3537 assert!(
3538 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
3539 "{text}"
3540 );
3541 }
3542
3543 #[test]
3544 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
3545 let text = summary(
3546 rucc_session::Safety::Detect,
3547 "void *memcpy(void *, const void *, unsigned long);\n\
3548 int puts(const char *);\n\
3549 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
3550 );
3551 assert!(text.contains("\"interposed\": 1"), "{text}");
3552 assert!(text.contains("\"puts\""), "{text}");
3553 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
3557 }
3558
3559 #[test]
3560 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3561 let text = summary(
3565 rucc_session::Safety::Detect,
3566 "void *notes_open(void);\n\
3567 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3568 );
3569 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3570 assert!(text.contains("\"notes_open\""), "{text}");
3571 }
3572
3573 #[test]
3574 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3575 let text = summary(
3578 rucc_session::Safety::Detect,
3579 "static int len(const char *p) { return p ? 1 : 0; }\n\
3580 int f(void) { return len(\"x\"); }\n",
3581 );
3582 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3583 }
3584
3585 fn granules(source: &str) -> String {
3587 let mut opts = options();
3588 opts.emit = EmitKind::TypeGranules;
3589 let result = run(&opts, source);
3590 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3591 result.text().to_owned()
3592 }
3593
3594 #[test]
3595 fn the_granule_report_names_every_record_and_both_keyings() {
3596 let text = granules(
3597 "struct hot { char *p; int a; int b; };\n\
3598 int f(struct hot *h) { return h->a; }\n",
3599 );
3600 assert!(text.contains("struct hot"), "{text}");
3601 assert!(text.contains("every type distinct"), "{text}");
3604 assert!(text.contains("every pointer one type"), "{text}");
3605 assert!(text.contains("budget"), "{text}");
3606 }
3607
3608 #[test]
3609 fn a_record_nothing_uses_is_still_measured() {
3610 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3613 assert!(text.contains("struct unused"), "{text}");
3614 }
3615
3616 #[test]
3617 fn the_granule_report_stops_before_anything_is_lowered() {
3618 let text = granules(
3622 "struct wide { long double d; };\n\
3623 long double f(long double x) { return x * x; }\n",
3624 );
3625 assert!(text.contains("struct wide"), "{text}");
3626 }
3627
3628 #[test]
3629 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
3630 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
3633 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
3634 }
3635
3636 #[test]
3637 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
3638 let text = summary(
3639 rucc_session::Safety::Detect,
3640 "unsigned long f(int *p) { return (unsigned long) p; }\n",
3641 );
3642 assert!(text.contains("\"exposed\": 1"), "{text}");
3643 }
3644
3645 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
3647 let mut opts = options();
3648 opts.emit = EmitKind::Asm;
3649 opts.safety = tier;
3650 let result = run(&opts, source);
3651 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3652 result.text().to_owned()
3653 }
3654
3655 #[test]
3656 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
3657 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3658 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
3659 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
3660 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
3661 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
3662 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
3663 }
3664
3665 #[test]
3666 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
3667 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3671 let section = format!("\t.section\t{},", rucc_safety::SECTION);
3672 assert_eq!(text.matches(§ion).count(), 5, "{text}");
3673 for index in 0..5 {
3674 let name = format!("__rucc_safety_desc_{index}");
3675 assert!(text.contains(&format!("{name}:\n")), "{text}");
3678 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
3679 }
3680 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
3681 }
3682
3683 #[test]
3691 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
3692 let text = ir(concat!(
3693 "int g;\n",
3694 "int a = __builtin_constant_p(1);\n",
3695 "int b = __builtin_constant_p(g);\n",
3696 "int c = __builtin_constant_p(\"abc\");\n",
3697 "int d = __builtin_constant_p(&g);\n",
3698 "int e = __builtin_constant_p(1.5);\n",
3699 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
3700 ));
3701 assert!(text.contains("global @a : i32 = 1,"), "{text}");
3702 assert!(text.contains("global @b : i32 = 0,"), "{text}");
3703 assert!(text.contains("global @c : i32 = 1,"), "{text}");
3704 assert!(text.contains("global @d : i32 = 0,"), "{text}");
3705 assert!(text.contains("global @e : i32 = 1,"), "{text}");
3706 assert!(text.contains("global @h : i32 = 11,"), "{text}");
3707 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
3708
3709 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
3713 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
3714 }
3715
3716 #[test]
3725 fn a_call_to_a_library_builtin_reaches_the_library_function() {
3726 let text = body("void f(void) { __builtin_abort(); }\n");
3727 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
3728
3729 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
3732 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
3733 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
3734 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3735 }
3736
3737 #[test]
3750 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
3751 let text = ir(concat!(
3752 "char d[8];\n",
3753 "void f(const char *s, unsigned long n) {\n",
3754 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3755 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
3756 " __builtin___memset_chk(d, 0, n, 8);\n",
3757 "}\n",
3758 ));
3759 assert!(text.contains("call @__memcpy_chk("), "{text}");
3760 assert!(text.contains("call @__strcpy_chk("), "{text}");
3761 assert!(text.contains("call @__memset_chk("), "{text}");
3762 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
3763 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3764 }
3765
3766 #[test]
3774 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
3775 let text = ir(concat!(
3776 "extern char *p;\n",
3777 "char d[8];\n",
3778 "void f(const char *s, unsigned long n) {\n",
3779 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3780 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3781 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
3782 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3783 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
3784 "}\n",
3785 ));
3786
3787 assert!(
3789 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
3790 "{text}"
3791 );
3792
3793 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3796 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
3797 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3798
3799 assert!(text.contains("call @__sprintf_chk("), "{text}");
3802
3803 let asm = asm(concat!(
3806 "void f(char *p, const char *s, unsigned long n) {\n",
3807 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3808 "}\n",
3809 ));
3810 assert!(asm.contains("call\tmemcpy"), "{asm}");
3811 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
3812 }
3813
3814 #[test]
3822 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
3823 let text = ir(concat!(
3824 "char d[64];\n",
3825 "int f(const char *fmt, ...) {\n",
3826 " __builtin_va_list ap;\n",
3827 " __builtin_va_start(ap, fmt);\n",
3828 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
3829 " __builtin_va_end(ap);\n",
3830 " return n;\n",
3831 "}\n",
3832 ));
3833 assert!(text.contains("call @__vsprintf_chk("), "{text}");
3834 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
3835 }
3836
3837 #[test]
3848 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
3849 let text = body(concat!(
3850 "long long llabs(long long);\n",
3851 "long long f(long long x) { return llabs(x); }\n",
3852 ));
3853 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
3854 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
3855 assert!(text.contains("%3 = xor %0, %2"), "{text}");
3856 assert!(text.contains("%4 = sub %3, %2"), "{text}");
3857 assert!(!text.contains("call"), "the call does not happen:\n{text}");
3858
3859 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
3862 assert!(text.contains("iconst.i32 31"), "{text}");
3863 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
3864 assert!(text.contains("iconst.i64 63"), "{text}");
3865
3866 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
3869 assert!(!text.contains("call"), "{text}");
3870
3871 let text = ir(concat!(
3873 "long long llabs(long long b);\n",
3874 "long long g(long long x) { return llabs(x); }\n",
3875 "long long llabs(long long b) { return 7; }\n",
3876 ));
3877 assert!(!text.contains("call @llabs"), "{text}");
3878 }
3879
3880 #[test]
3887 fn a_byte_swap_is_arithmetic_and_not_a_call() {
3888 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
3889 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
3890
3891 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
3894 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
3895 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
3896 }
3897
3898 #[test]
3904 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
3905 for (name, ty, width) in [
3906 ("__builtin_bswap16", "unsigned short", "i16"),
3907 ("__builtin_bswap32", "unsigned", "i32"),
3908 ("__builtin_bswap64", "unsigned long long", "i64"),
3909 ] {
3910 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
3911 let text = body(&source);
3912 assert_eq!(
3913 text,
3914 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
3915 "{name}"
3916 );
3917 }
3918 }
3919
3920 #[test]
3927 fn the_bit_counts_are_instructions_and_not_calls() {
3928 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
3929 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
3930
3931 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
3932 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
3933
3934 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
3935 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
3936 }
3937
3938 #[test]
3947 fn the_bit_counts_ask_about_the_width_their_name_says() {
3948 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
3949 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
3950 assert!(text.contains("%1 = ctlz %0"), "{text}");
3951 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
3952
3953 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
3956 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
3957 assert!(text.contains("ctlz %1"), "and counted there: {text}");
3958
3959 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
3960 assert!(text.contains("%1 = ctpop %0"), "{text}");
3961 assert!(!text.contains("call"), "{text}");
3962 }
3963
3964 #[test]
3969 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
3970 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
3971 assert!(text.contains("%1 = ctpop %0"), "{text}");
3972 assert!(text.contains("iconst.i32 1"), "{text}");
3973 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
3974 }
3975
3976 #[test]
3982 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
3983 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
3984 assert!(text.contains("%1 = cttz %0"), "{text}");
3985 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
3986 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
3987 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
3988 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
3989 assert!(!text.contains("br_if"), "no branch: {text}");
3990 }
3991
3992 #[test]
4002 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4003 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4004 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4005 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4006 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4007 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4008 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4009 assert!(text.contains("%7 = ctlz %6"), "{text}");
4010 assert!(!text.contains("call"), "{text}");
4011 assert!(!text.contains("br_if"), "no branch: {text}");
4012 }
4013
4014 #[test]
4020 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4021 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4022 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4023 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4024 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4025
4026 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4027 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4028
4029 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4032 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4033 }
4034
4035 #[test]
4043 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4044 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4045 assert!(text.contains("iconst.i64 63"), "{text}");
4046 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4047 assert!(!text.contains("call"), "{text}");
4048
4049 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4050 assert!(text.contains("iconst.i64 63"), "{text}");
4051 assert!(!text.contains("call"), "{text}");
4052 }
4053
4054 #[test]
4062 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4063 let text =
4064 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4065 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4066 assert!(!text.contains("store"), "nothing is written: {text}");
4067 assert!(!text.contains("call"), "{text}");
4068
4069 let text =
4072 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4073 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4074 assert!(!text.contains("store"), "{text}");
4075
4076 let text = body(concat!(
4079 "int g(void);\n",
4080 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4081 ));
4082 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4083 }
4084
4085 #[test]
4095 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4096 let text =
4097 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4098 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4099 assert!(text.contains("store %3 -> %2"), "{text}");
4100 assert!(!text.contains("call"), "{text}");
4101
4102 let text =
4103 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4104 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4105
4106 let text =
4107 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4108 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4109
4110 let text = body(
4113 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4114 );
4115 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4116 }
4117
4118 #[test]
4126 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4127 let text = body(
4128 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4129 );
4130 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4131 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4132 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4133
4134 let text = body(
4137 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4138 );
4139 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4140 assert!(!text.contains("sext."), "{text}");
4141 assert!(!text.contains("zext.i64"), "{text}");
4143 }
4144
4145 #[test]
4153 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4154 let text =
4155 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4156 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4157 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4158 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4159 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4160 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4161 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4162 }
4163
4164 #[test]
4171 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4172 for name in ["add", "sub", "mul"] {
4173 let source = format!(
4174 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4175 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4176 );
4177 let mut opts = options();
4178 opts.emit = EmitKind::MirFinal;
4179 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4180 }
4181 }
4182
4183 #[test]
4186 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4187 let messages =
4188 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4189 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4190
4191 let messages =
4192 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4193 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4194 }
4195
4196 #[test]
4207 fn an_ordered_access_is_ordered_in_the_ir() {
4208 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4209 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4210
4211 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4212 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4213
4214 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4215 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4216
4217 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4218 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4219
4220 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4223 assert!(text.contains("trunc.i8 %1"), "{text}");
4224 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4225 }
4226
4227 #[test]
4236 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4237 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4238 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4239 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4240
4241 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4242 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4243 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4244
4245 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4246 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4247 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4248 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4249 }
4250
4251 #[test]
4261 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4262 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4263 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4264
4265 for weaker in ["1", "2", "3", "4"] {
4266 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4267 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4268 }
4269 }
4270
4271 #[test]
4277 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4278 let text =
4281 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4282 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4283 assert!(text.contains("return %3"), "the value it found: {text}");
4284
4285 let text =
4286 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4287 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4288 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4289
4290 let text = body(
4293 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4294 );
4295 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4296 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4297 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4298 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4299
4300 let text = body(
4303 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4304 );
4305 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4306 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4307 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4308 }
4309
4310 #[test]
4317 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4318 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4319 for (ty, suffix, reg) in widths {
4320 let source = format!(
4321 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4322 );
4323 let text = asm(&source);
4324 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4325 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4326 assert!(text.contains("sete\t"), "{ty}: {text}");
4327 }
4328 let source =
4329 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4330 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4331
4332 for order in ["0", "2", "3", "4", "5"] {
4336 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4337 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4338 let text = asm(&source);
4339 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4340 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4341 }
4342 }
4343
4344 #[test]
4356 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4357 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4358 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4359 assert!(text.contains("return %2"), "the value that was there: {text}");
4360
4361 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4362 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4363 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4364
4365 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4366 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4367 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4368
4369 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4371 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4372
4373 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4376 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4377
4378 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4379 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4380
4381 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4384 assert!(text.contains("release"), "{text}");
4385 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4386
4387 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4391 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4392 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4393
4394 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4397 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4398
4399 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4400 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4401 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4402
4403 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4406 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4407 assert!(text.contains("%3 = and %2, %1"), "{text}");
4408 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4409 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4410 }
4411
4412 #[test]
4423 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4424 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4425 for (ty, suffix, reg) in widths {
4426 for (name, call, insn) in [
4427 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4428 ("or", "__sync_fetch_and_or(p, v)", "or"),
4429 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4430 ] {
4431 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4432 let text = asm(&source);
4433 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4434 assert!(
4435 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4436 "{ty} {name}: {text}"
4437 );
4438 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4439 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4441 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4442 }
4443 }
4444 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4445 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4446
4447 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4451 assert!(text.contains("cmpxchgl\t"), "{text}");
4452 assert!(text.contains("andl\t"), "{text}");
4453 assert!(text.contains("notl\t"), "{text}");
4454 }
4455
4456 #[test]
4465 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4466 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4467 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4468 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4469
4470 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4471 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4472 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4473
4474 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4477 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4478 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4479 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4480 }
4481
4482 #[test]
4493 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4494 for pointer in ["char", "int", "void"] {
4495 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4496 let text = body(&source);
4497 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4498 assert!(
4499 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4500 "{pointer}: {text}"
4501 );
4502 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4503
4504 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4505 let text = body(&source);
4506 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4507 }
4508
4509 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4512 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4513 assert!(text.contains("setne\t"), "{text}");
4514 }
4515
4516 #[test]
4524 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4525 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4526 for (ty, suffix, reg) in widths {
4527 let source =
4528 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4529 let text = asm(&source);
4530 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4531 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4532
4533 let source =
4534 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4535 let text = asm(&source);
4536 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4537 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4538 }
4539 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4540 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4541
4542 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4545 let text = asm(source);
4546 assert!(text.contains("negl\t"), "{text}");
4547 assert!(text.contains("xaddl\t"), "{text}");
4548
4549 for order in ["0", "2", "3", "4", "5"] {
4552 let source =
4553 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4554 let text = asm(&source);
4555 assert!(text.contains("xaddl\t"), "{order}: {text}");
4556 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4557 }
4558
4559 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4563 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4564 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4569 assert!(text.contains("movl\t$0, %eax"), "{text}");
4570 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4571 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4572 }
4573
4574 #[test]
4586 fn the_lock_free_questions_are_answered_as_constants() {
4587 for size in ["1", "2", "4", "8"] {
4588 let source =
4589 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4590 let text = asm(&source);
4591 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4592 assert!(!text.contains("call"), "and is not a call: {text}");
4593 }
4594 for size in ["3", "16", "sizeof(long double)"] {
4595 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4596 let text = asm(&source);
4597 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
4598 assert!(!text.contains("call"), "and is not a call either: {text}");
4599 }
4600
4601 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
4605 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
4606 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
4607 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
4608 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
4609 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
4610 }
4611
4612 #[test]
4624 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
4625 let mut opts = options();
4626 opts.emit = EmitKind::Ir;
4627
4628 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
4629 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
4630 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
4631
4632 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
4633 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
4634 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
4635
4636 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
4637 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
4638 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
4639 }
4640
4641 #[test]
4653 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
4654 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
4655 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
4656 assert!(text.contains("shrq"), "with the value halved first: {text}");
4657 assert!(text.contains("addsd"), "and doubled after: {text}");
4658 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4659
4660 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
4661 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
4662 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
4663 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
4664 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4665 }
4666
4667 #[test]
4678 fn a_plain_name_the_program_took_is_the_programs_own_function() {
4679 let taken = concat!(
4680 "static long long llabs(long long b) { return 7; }\n",
4681 "long long f(long long x) { return llabs(x); }\n",
4682 );
4683 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
4684
4685 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
4686 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
4687
4688 let plain = concat!(
4689 "long long llabs(long long b);\n",
4690 "long long f(long long x) { return llabs(x); }\n",
4691 );
4692 let mut opts = options();
4693 opts.emit = EmitKind::Ir;
4694 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
4695
4696 opts.builtins = false;
4697 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
4698
4699 opts.builtins = true;
4700 opts.no_builtin = vec!["llabs".to_owned()];
4701 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
4702 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
4703 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
4704
4705 opts.no_builtin = Vec::new();
4708 opts.builtins = false;
4709 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
4710 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
4711 }
4712
4713 #[test]
4726 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
4727 let text = ir(concat!(
4728 "long a = __builtin_expect(7, 1);\n",
4729 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
4730 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
4731 ));
4732 assert!(text.contains("global @a : i64 = 7,"), "{text}");
4733 assert!(text.contains("global @b : i64 = 9,"), "{text}");
4734 assert!(text.contains("global @c : i64 = 8,"), "{text}");
4735 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
4736
4737 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
4740 assert!(text.contains("sext"), "{text}");
4741
4742 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
4746 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
4747 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
4748 assert_eq!(body(source), one);
4749
4750 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
4755 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
4756 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
4757 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
4758 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
4759 }
4760
4761 #[test]
4773 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
4774 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
4775 let text = ir(promised);
4776 assert!(text.contains(" unreachable_hint\n"), "{text}");
4777 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
4778
4779 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
4783 assert!(after.contains("return"), "{after}");
4784
4785 let text = asm(promised);
4788 let mine = text.split_once("\nf:\n").expect("a definition").1;
4789 let mine = mine.split_once("\t.size").expect("a definition").0;
4790 let plain = asm("int f(int x) { if (x) return 1; }\n");
4791 let plain = plain.split_once("\nf:\n").expect("a definition").1;
4792 let plain = plain.split_once("\t.size").expect("a definition").0;
4793 assert_eq!(mine, plain);
4794 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
4797 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
4798 assert!(!mine.contains("ud2"), "{mine}");
4799 }
4800
4801 #[test]
4808 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
4809 let mut opts = options();
4810 opts.emit = EmitKind::Ir;
4811 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
4812 assert!(
4813 messages.iter().any(|m| m.contains("__builtin_abort")),
4814 "expected the written name in {messages:?}"
4815 );
4816 }
4817
4818 #[test]
4826 fn a_builtin_nothing_lowers_is_refused_by_name() {
4827 let mut opts = options();
4828 opts.emit = EmitKind::Ir;
4829 let builtin = "__atomic_signal_fence";
4830 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
4831 let messages = run(&opts, &source).messages;
4832 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
4833 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
4834 }
4835
4836 #[test]
4845 fn what_is_refused_is_the_call_and_not_the_name() {
4846 let text = ir(concat!(
4847 "void __atomic_signal_fence(int order) { (void)order; }\n",
4848 "void f(void) { __atomic_signal_fence(5); }\n",
4849 ));
4850 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
4851 }
4852
4853 #[test]
4862 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
4863 let text = ir(concat!(
4864 "struct S { char a[8]; int n; char b[12]; };\n",
4865 "char g[32];\n",
4866 "struct S gs;\n",
4867 "unsigned long whole = __builtin_object_size(g, 0);\n",
4868 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
4869 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
4870 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
4871 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
4872 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
4873 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
4874 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
4875 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
4876 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
4877 ));
4878 for (name, size) in [
4879 ("whole", 32),
4880 ("moved", 28),
4881 ("back", 4),
4882 ("outer", 24),
4883 ("inner", 8),
4884 ("scalar", 4),
4885 ("after", 16),
4886 ("into", 10),
4887 ("text", 6),
4888 ("dyn", 12),
4889 ] {
4890 let said = format!("global @{name} : i64 = {size},");
4891 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
4892 }
4893 }
4894
4895 #[test]
4903 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
4904 let text = body(concat!(
4905 "struct S { char a[8]; int n; char b[12]; };\n",
4906 "unsigned long f(void) {\n",
4907 " char loc[20];\n",
4908 " struct S ls;\n",
4909 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
4910 "}\n",
4911 ));
4912 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
4913 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
4914 }
4915
4916 #[test]
4926 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
4927 let text = ir(concat!(
4928 "struct T { int n; char f[]; };\n",
4929 "extern char *p;\n",
4930 "extern struct T *t;\n",
4931 "unsigned long largest = __builtin_object_size(p, 0);\n",
4932 "unsigned long nearest = __builtin_object_size(p, 1);\n",
4933 "unsigned long least = __builtin_object_size(p, 2);\n",
4934 "unsigned long tight = __builtin_object_size(p, 3);\n",
4935 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
4936 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
4937 ));
4938 for name in ["largest", "nearest", "flex"] {
4939 let said = format!("global @{name} : i64 = -1,");
4943 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
4944 }
4945 for name in ["least", "tight"] {
4946 let said = format!("global @{name} : i64 = 0,");
4947 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
4948 }
4949 assert!(text.contains("global @says : i32 = 1,"), "{text}");
4950 }
4951
4952 #[test]
4959 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
4960 let text = body(concat!(
4961 "extern char *side(void);\n",
4962 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
4963 ));
4964 assert!(!text.contains("call"), "nothing is called: {text}");
4965 }
4966
4967 #[test]
4972 fn a_kind_that_is_not_one_of_the_four_is_refused() {
4973 for source in [
4974 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
4975 + "{ return __builtin_object_size(p, k); }\n",
4976 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
4977 .to_owned(),
4978 "extern char *p;\nunsigned long f(void) ".to_owned()
4979 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
4980 ] {
4981 let messages = errors(&source);
4982 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
4983 assert!(named, "expected a complaint about the kind in {messages:?}");
4984 }
4985 }
4986
4987 #[test]
4993 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
4994 let text = ir(concat!(
4995 "void *buf[5];\n",
4996 "int f(void) {\n",
4997 " if (__builtin_setjmp(buf)) return 2;\n",
4998 " return 1;\n",
4999 "}\n",
5000 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5001 ));
5002 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5003 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5004 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5005 }
5006
5007 #[test]
5015 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5016 let text = ir(concat!(
5017 "void *buf[5];\n",
5018 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5019 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5020 ));
5021 let (saves, plain) = text.split_once("func @g").expect("both functions");
5022 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5023 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5024 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5025 }
5026
5027 #[test]
5036 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5037 let text =
5038 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5039 let body = text.split_once("\nf:\n").expect("the function").1;
5040 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5041 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5042 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5043 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5044 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5045 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5046 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5047 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5048 }
5049
5050 #[test]
5058 fn a_save_destroys_every_register_the_allocator_hands_out() {
5059 let text =
5060 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5061 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5062 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5063 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5064 }
5065 }
5066
5067 #[test]
5074 fn the_restore_puts_the_frame_back_before_it_jumps() {
5075 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5076 let mut opts = options();
5077 opts.emit = EmitKind::Asm;
5078 opts.opt_level = level;
5079 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5080 let result = run(&opts, source);
5081 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5082 let text = result.text().to_owned();
5083 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5084 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5085 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5086 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5087 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5088 }
5089 }
5090
5091 #[test]
5097 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5098 for source in [
5099 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5100 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5101 ] {
5102 let messages = errors(source);
5103 let named = messages.iter().any(|m| m.contains("E0710"));
5104 assert!(named, "expected a complaint about the value in {messages:?}");
5105 }
5106 }
5107
5108 #[test]
5113 fn a_static_function_nothing_refers_to_is_not_emitted() {
5114 let text = ir("static int dropped(void) { return 1; }\n\
5115 static int kept(void) { return 2; }\n\
5116 int main(void) { return kept(); }\n");
5117 assert!(text.contains("func @kept"), "{text}");
5118 assert!(!text.contains("dropped"), "{text}");
5119 }
5120
5121 #[test]
5127 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5128 let text = ir("static int ping(void);\n\
5129 static int pong(void) { return ping(); }\n\
5130 static int ping(void) { return pong(); }\n\
5131 int main(void) { return 0; }\n");
5132 assert!(!text.contains("ping"), "{text}");
5133 assert!(!text.contains("pong"), "{text}");
5134 }
5135
5136 #[test]
5142 fn naming_a_static_function_anywhere_keeps_it() {
5143 let text = ir("static int by_address(void) { return 1; }\n\
5144 static int in_an_image(void) { return 2; }\n\
5145 static int deeper(void) { return 3; }\n\
5146 static int reaches_deeper(void) { return deeper(); }\n\
5147 static int (*table[1])(void) = {in_an_image};\n\
5148 int main(void) {\n\
5149 int (*p)(void) = by_address;\n\
5150 return p() + table[0]() + reaches_deeper();\n\
5151 }\n");
5152 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5153 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5154 }
5155 }
5156
5157 #[test]
5163 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5164 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5165 let source = format!(
5166 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5167 int main(void) {{ return 0; }}\n"
5168 );
5169 let text = ir(&source);
5170 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5171 }
5172 }
5173
5174 #[test]
5177 fn a_function_anything_could_call_is_emitted_without_being_called() {
5178 let text =
5179 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5180 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5181 }
5182
5183 #[test]
5190 fn a_classification_c_has_an_operator_for_is_that_operator() {
5191 for (builtin, operator) in [
5192 ("__builtin_isgreater", "binary >"),
5193 ("__builtin_isgreaterequal", "binary >="),
5194 ("__builtin_isless", "binary <"),
5195 ("__builtin_islessequal", "binary <="),
5196 ] {
5197 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5198 let text = tast(&source);
5199 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5200 }
5201 }
5202
5203 #[test]
5212 fn the_classification_builtins_are_comparisons_and_not_calls() {
5213 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5214 assert_eq!(
5215 text,
5216 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5217 %2\n return %3\n"
5218 );
5219
5220 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5222 assert!(text.contains("fcmp one %0, %1"), "{text}");
5223
5224 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5225 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5226
5227 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5228 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5229 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5230 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5231 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5232 assert!(text.contains("%5 = or %3, %4"), "{text}");
5233
5234 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5237 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5238 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5239 assert!(text.contains("%5 = and %3, %4"), "{text}");
5240
5241 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5242 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5243 assert!(text.contains("icmp slt %1, %2"), "{text}");
5244
5245 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5248 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5249
5250 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5253 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5254 }
5255
5256 #[test]
5263 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5264 let text = ir(concat!(
5265 "int a = __builtin_isinff(1e300);\n",
5266 "int b = __builtin_isinf(1e300);\n",
5267 "int c = __builtin_isnan(0.0);\n",
5271 "int d = __builtin_signbit(-0.0);\n",
5272 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5273 ));
5274 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5275 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5276 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5277 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5278 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5279 }
5280
5281 #[test]
5283 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5284 let mut opts = options();
5285 opts.emit = EmitKind::Ir;
5286 let source = concat!(
5287 "int a(int x) { return __builtin_isnan(x); }\n",
5288 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5289 "int c(double x) { return __builtin_isnan(x, x); }\n",
5290 );
5291 let messages = run(&opts, source).messages;
5292 assert_eq!(
5293 messages,
5294 [
5295 "/main.c:1:23: error: non-floating-point argument in call to function \
5296 '__builtin_isnan' [E0685]",
5297 "/main.c:2:30: error: non-floating-point arguments in call to function \
5298 '__builtin_isunordered' [E0685]",
5299 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5300 ]
5301 );
5302 }
5303
5304 #[test]
5313 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5314 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5315 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5319 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5320 assert!(text.contains("%3 = and %1, %2"), "{text}");
5321 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5322 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5323 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5324 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5325 assert!(text.contains("%8 = and %6, %7"), "{text}");
5326
5327 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5331 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5332 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5333
5334 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5335 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5336 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5337 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5338
5339 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5340 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5341 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5342 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5346 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5347 assert!(!text.contains("call"), "{text}");
5348
5349 let text = body(concat!(
5352 "double g(void);\n",
5353 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5354 ));
5355 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5356 }
5357
5358 #[test]
5365 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5366 let text = ir(concat!(
5367 "int a = __builtin_isnormal(1.0);\n",
5368 "int b = __builtin_isnormal(0.0);\n",
5369 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5370 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5371 "int e = __builtin_isinf_sign(1.0);\n",
5372 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5373 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5374 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5375 ));
5376 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5377 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5378 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5379 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5380 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5381 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5382 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5383 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5384 }
5385
5386 #[test]
5392 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5393 let mut opts = options();
5394 opts.emit = EmitKind::Ir;
5395 let source = concat!(
5396 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5397 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5398 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5399 );
5400 let messages = run(&opts, source).messages;
5401 assert_eq!(
5402 messages,
5403 [
5404 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5405 '__builtin_fpclassify' [E0687]",
5406 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5407 [E0511]",
5408 "/main.c:3:23: error: non-floating-point argument in call to function \
5409 '__builtin_fpclassify' [E0685]",
5410 ]
5411 );
5412 }
5413
5414 #[test]
5422 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5423 let text = ir(concat!(
5424 "double a = __builtin_inf();\n",
5425 "float b = __builtin_huge_valf();\n",
5426 "long double c = __builtin_infl();\n",
5427 "double d = __builtin_huge_val();\n",
5428 ));
5429 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5430 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5431 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5432 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5433 assert!(!text.contains("call"), "{text}");
5434 }
5435
5436 #[test]
5445 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5446 let text = ir(concat!(
5447 "double a = __builtin_nan(\"\");\n",
5448 "double b = __builtin_nan(\"0x1\");\n",
5449 "double c = __builtin_nan(\"010\");\n",
5451 "double d = __builtin_nans(\"\");\n",
5452 "double e = __builtin_nans(\"0x1\");\n",
5453 "float f = __builtin_nanf(\"0x1\");\n",
5454 "float g = __builtin_nansf(\"\");\n",
5455 "long double h = __builtin_nansl(\"\");\n",
5456 ));
5457 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5458 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5459 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5460 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5461 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5462 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5463 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5464 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5465
5466 let text = ir(concat!(
5469 "double f(const char *p) { return __builtin_nan(p); }\n",
5470 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5471 ));
5472 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5473 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5474 }
5475
5476 #[test]
5484 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5485 let text = ir(concat!(
5486 "unsigned long a = __builtin_strlen(\"hello\");\n",
5487 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5488 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5489 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5490 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5491 ));
5492 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5493 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5494 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5495 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5496 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5497 assert!(!text.contains("call"), "{text}");
5498
5499 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5501 assert!(text.contains("call @strlen("), "{text}");
5502 }
5503
5504 #[test]
5511 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5512 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5513 assert!(text.contains("bitcast.i64 %0"), "{text}");
5514 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5515 assert!(text.contains("and %1, %2"), "{text}");
5516 assert!(text.contains("bitcast.f64 %3"), "{text}");
5517 assert!(!text.contains("call"), "{text}");
5518
5519 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5520 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5521 assert!(text.contains("%8 = or %4, %7"), "{text}");
5522 assert!(!text.contains("call"), "{text}");
5523
5524 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5527 assert!(text.contains("bitcast.i80 %0"), "{text}");
5528 assert!(text.contains("bitcast.f80"), "{text}");
5529
5530 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5533 assert!(text.contains("fpext.f64 %0"), "{text}");
5534 assert!(text.contains("bitcast.i64 %1"), "{text}");
5535 }
5536
5537 #[test]
5546 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5547 let text =
5548 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5549 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5550 assert!(!text.contains("call"), "{text}");
5551
5552 let text =
5553 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5554 assert!(text.contains("bitcast.i32 %0"), "{text}");
5555 assert!(!text.contains("call"), "{text}");
5556
5557 let text = body(concat!(
5558 "double copysign(double x, double y);\n",
5559 "double f(double x, double y) { return copysign(x, y); }\n",
5560 ));
5561 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5562 assert!(!text.contains("call"), "{text}");
5563
5564 let text = body(concat!(
5565 "float copysignf(float x, float y);\n",
5566 "float f(float x, float y) { return copysignf(x, y); }\n",
5567 ));
5568 assert!(!text.contains("call"), "{text}");
5569
5570 let text = ir(concat!(
5574 "long double fabsl(long double x);\n",
5575 "long double f(long double x) { return fabsl(x); }\n",
5576 ));
5577 assert!(text.contains("call @fabsl"), "{text}");
5578 }
5579
5580 #[test]
5588 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5589 let taken = concat!(
5590 "static double fabs(double b) { return 7; }\n",
5591 "double f(double x) { return fabs(x); }\n",
5592 );
5593 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5594
5595 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5596 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5597
5598 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
5599 let mut opts = options();
5600 opts.emit = EmitKind::Ir;
5601 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
5602
5603 opts.builtins = false;
5604 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
5605
5606 opts.builtins = true;
5607 opts.no_builtin = vec!["fabs".to_owned()];
5608 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
5609 let one = concat!(
5610 "double copysign(double a, double b);\n",
5611 "double f(double x) { return copysign(x, 1.0); }\n",
5612 );
5613 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
5614
5615 opts.no_builtin = Vec::new();
5617 opts.builtins = false;
5618 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
5619 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
5620 }
5621
5622 #[test]
5631 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
5632 let text = ir(concat!(
5633 "double a = __builtin_fabs(-3.5);\n",
5634 "double b = __builtin_copysign(1.0, -0.0);\n",
5635 "double c = __builtin_copysign(0.0, -2.0);\n",
5636 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
5638 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
5639 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
5640 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
5641 "long double i = __builtin_fabsl(-__builtin_infl());\n",
5642 ));
5643 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
5644 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
5645 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
5646 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
5647 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
5648 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
5649 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
5650 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5651 }
5652
5653 #[test]
5661 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
5662 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
5663 assert!(!text.contains("call"), "{text}");
5664 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
5665 assert!(!text.contains("call"), "{text}");
5666
5667 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
5670 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5671 assert!(!text.contains("call"), "{text}");
5672 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
5673 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
5674
5675 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
5678 assert_eq!(written, text, "the name and the operator are the same thing");
5679
5680 let text = body(concat!(
5682 "double creal(_Complex double z);\n",
5683 "double f(_Complex double z) { return creal(z); }\n",
5684 ));
5685 assert!(!text.contains("call"), "{text}");
5686 let text = body(concat!(
5687 "_Complex float conjf(_Complex float z);\n",
5688 "_Complex float f(_Complex float z) { return conjf(z); }\n",
5689 ));
5690 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5691 assert!(!text.contains("call"), "{text}");
5692
5693 let taken = concat!(
5696 "static double creal(_Complex double z) { return 7; }\n",
5697 "double f(_Complex double z) { return creal(z); }\n",
5698 );
5699 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
5700 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
5701 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
5702 let plain = concat!(
5703 "double cimag(_Complex double z);\n",
5704 "double f(_Complex double z) { return cimag(z); }\n",
5705 );
5706 let mut opts = options();
5707 opts.emit = EmitKind::Ir;
5708 opts.builtins = false;
5709 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
5710 opts.builtins = true;
5711 opts.no_builtin = vec!["cimag".to_owned()];
5712 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
5713
5714 let text = ir(concat!(
5716 "double a = __builtin_creal(1.5 + 2.5i);\n",
5717 "double b = __builtin_cimag(1.5 + 2.5i);\n",
5718 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
5719 ));
5720 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
5721 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
5722 assert!(
5723 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
5724 "the conjugate of a constant is the constant with the second half negated: {text}"
5725 );
5726 assert!(!text.contains("call"), "{text}");
5727 }
5728
5729 #[test]
5737 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
5738 let text = ir(concat!(
5739 "double a = __builtin_ceil(1.5);\n",
5740 "double b = __builtin_floor(1.5);\n",
5741 "double c = __builtin_trunc(-1.5);\n",
5742 "double d = __builtin_round(2.5);\n",
5745 "double e = __builtin_ceil(-0.5);\n",
5747 "double f = __builtin_fmax(1.0, 2.0);\n",
5748 "double g = __builtin_fmin(1.0, 2.0);\n",
5749 "float h = __builtin_ceilf(1.25f);\n",
5750 "double ceil(double x);\n",
5753 "double i = ceil(2.25);\n",
5754 ));
5755 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
5756 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
5757 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
5758 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
5759 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
5760 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
5761 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
5762 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
5763 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
5764 assert!(!text.contains("call"), "{text}");
5765 }
5766
5767 #[test]
5775 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
5776 let text = ir(concat!(
5777 "double f(double x) { return __builtin_ceil(x); }\n",
5778 "float g(float x) { return __builtin_floorf(x); }\n",
5779 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
5780 ));
5781 assert!(text.contains("call @ceil("), "{text}");
5782 assert!(text.contains("call @floorf("), "{text}");
5783 assert!(text.contains("call @fmax("), "{text}");
5784
5785 let text = ir(concat!(
5789 "double f(void) { return __builtin_rint(2.5); }\n",
5790 "double g(void) { return __builtin_nearbyint(2.5); }\n",
5791 ));
5792 assert!(text.contains("call @rint("), "{text}");
5793 assert!(text.contains("call @nearbyint("), "{text}");
5794
5795 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
5798 assert!(text.contains("call @fmin("), "{text}");
5799
5800 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
5803 let mut opts = options();
5804 opts.emit = EmitKind::Ir;
5805 opts.no_builtin = vec!["ceil".to_owned()];
5806 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
5807 }
5808
5809 #[test]
5816 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
5817 let text = ir(concat!(
5818 "constexpr int side = 4;\n",
5819 "constexpr int wider = side + 1;\n",
5820 "constexpr double half = 1.5;\n",
5821 "struct point { int x; int y; };\n",
5822 "constexpr struct point origin = { 5, 6 };\n",
5823 "int square[side * side];\n",
5824 "int rectangle[wider];\n",
5825 "int rounded[(int)half * 2];\n",
5826 "int across[origin.y];\n",
5827 "enum named { four = side };\n",
5828 "int e = four;\n",
5829 ));
5830 assert!(text.contains("global @square : bytes 64 ="), "{text}");
5831 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
5832 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
5833 assert!(text.contains("global @across : bytes 24 ="), "{text}");
5834 assert!(text.contains("global @e : i32 = 4,"), "{text}");
5835
5836 let mut opts = options();
5839 opts.emit = EmitKind::Ir;
5840 let konst = "const int n = 1;\nint a[n];\n";
5841 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
5842 assert_eq!(run(&opts, konst).messages, [message]);
5843
5844 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
5846 assert_eq!(run(&opts, subscript).messages, [message]);
5847
5848 let address = "constexpr int c = 3;\nint *p = &c;\n";
5850 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
5851 pointer target type [E0514]";
5852 assert_eq!(run(&opts, address).messages, [warning]);
5853 }
5854
5855 #[test]
5864 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
5865 let mut opts = options();
5868 opts.std = Std::C17;
5869 let source = concat!(
5870 "int add(a, b)\n",
5871 "int a;\n",
5872 "int b;\n",
5873 "{ return a + b; }\n",
5874 "int promoted(c)\n",
5875 "char c;\n",
5876 "{ return c; }\n",
5877 "int narrow(char);\n",
5878 "int narrow(c)\n",
5879 "char c;\n",
5880 "{ return c; }\n",
5881 "int first(a)\n",
5882 "int a[4];\n",
5883 "{ return a[0]; }\n",
5884 );
5885 let result = run(&opts, source);
5886 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5887 let text = result.text();
5888 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
5889 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
5890 assert!(text.contains("c : char object automatic defined"), "{text}");
5892 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
5893 assert!(text.contains("first : int(int *) function external defined"), "{text}");
5895 }
5896
5897 #[test]
5904 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
5905 let mut opts = options();
5906 opts.std = Std::C17;
5907 for (source, message) in [
5908 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
5909 (
5910 "int f(a)\nint a;\nint b;\n{ return a; }\n",
5911 "3:5: error: declaration for parameter 'b' but no such parameter",
5912 ),
5913 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
5914 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
5915 (
5916 "int f(a)\nstatic int a;\n{ return a; }\n",
5917 "2:12: error: storage class specified for parameter 'a'",
5918 ),
5919 (
5920 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
5921 "2:7: error: argument 'a' doesn't match prototype",
5922 ),
5923 ] {
5924 let result = run(&opts, source);
5925 assert!(result.failed(), "expected this to fail:\n{source}");
5926 assert!(result.messages[0].contains(message), "{:?}", result.messages);
5927 }
5928
5929 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
5932 let mut older = options();
5933 older.std = Std::C89;
5934 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
5935 let result = run(&opts, implicit);
5936 assert!(
5937 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
5938 "{:?}",
5939 result.messages
5940 );
5941
5942 let mut newer = options();
5946 newer.std = Std::C23;
5947 let plain = "int f(a)\nint a;\n{ return a; }\n";
5948 let result = run(&newer, plain);
5949 assert!(!result.failed(), "{:?}", result.messages);
5950 assert_eq!(
5951 result.messages,
5952 ["/main.c:1:5: warning: old-style function definition [E0412]"]
5953 );
5954 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
5955 }
5956
5957 #[test]
5964 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
5965 let array = "int a[8] = { [3] 7 };\n";
5966 let member = "struct s { int x; } v = { x: 7 };\n";
5967 for source in [array, member] {
5968 let result = run(&options(), source);
5969 assert!(!result.failed(), "{:?}", result.messages);
5970 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
5971 }
5972
5973 let mut asked = options();
5974 asked.pedantic = true;
5975 assert_eq!(
5976 run(&asked, array).messages,
5977 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
5978 );
5979 assert_eq!(
5980 run(&asked, member).messages,
5981 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
5982 );
5983 }
5984
5985 #[test]
5992 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
5993 let text = ir(concat!(
5994 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
5995 "struct brim { char buf[9223372036854775807L]; };\n",
5996 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
5997 "unsigned long h = sizeof(struct huge_struct);\n",
5998 "unsigned long b = sizeof(struct brim);\n",
5999 "unsigned long y = sizeof(struct bitty);\n",
6000 ));
6001 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6002 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6003 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6004
6005 let mut opts = options();
6006 opts.emit = EmitKind::Ir;
6007 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6008 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6009 assert_eq!(run(&opts, over).messages, [message]);
6010 let array = "struct wide { short buf[1L << 62]; };\n";
6011 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6012 maximum object size '9223372036854775807' [E0537]";
6013 assert_eq!(run(&opts, array).messages[0], message);
6014 }
6015
6016 fn compile_bytes(source: &[u8]) -> Compiled {
6021 let mut opts = options();
6022 opts.emit = EmitKind::Ir;
6023 let mut fs = MemoryFileSystem::new();
6024 fs.insert("/main.c", source.to_vec());
6025 compile(&opts, "/main.c", &fs)
6026 }
6027
6028 #[test]
6035 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6036 let mut source = b"char s[] = \"a".to_vec();
6037 source.push(0xff);
6038 source.extend_from_slice(b"b\";\nchar c = '");
6039 source.push(0xff);
6040 source.extend_from_slice(b"';\n");
6041 let result = compile_bytes(&source);
6042 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6043 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6044 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6046
6047 let mut stray = b"int a".to_vec();
6048 stray.push(0xff);
6049 stray.extend_from_slice(b" = 1;\n");
6050 let result = compile_bytes(&stray);
6051 assert!(
6052 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6053 "{:?}",
6054 result.messages
6055 );
6056 }
6057
6058 #[test]
6059 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6060 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6061 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6062 let expected = "\
6063func @add(i32, i32) -> i32, linkage(external) {
6064block0(%0: i32, %1: i32):
6065 %2 = add.nsw %0, %1
6066 return %2
6067}
6068";
6069 assert!(text.contains(expected), "{text}");
6070 }
6071
6072 #[test]
6073 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6074 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6075 assert!(!text.contains("alloca"), "{text}");
6076 assert!(!text.contains("load"), "{text}");
6077 assert!(!text.contains("store"), "{text}");
6078 }
6079
6080 #[test]
6081 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6082 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6083 let expected = "\
6084block0:
6085 %0 = alloca, size 4, align 4
6086 %1 = iconst.i32 1
6087 store %1 -> %0, align 4, tbaa !1
6088 %2 = call @g(%0) : (ptr) -> i32
6089 return %2
6090";
6091 assert_eq!(text, expected);
6092 }
6093
6094 #[test]
6095 fn a_loop_carries_what_it_changes_as_block_parameters() {
6096 let text = body(
6099 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6100 return total;\n}\n",
6101 );
6102 assert!(!text.contains("alloca"), "{text}");
6103 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6104 assert!(text.contains("jump block1("), "{text}");
6105 }
6106
6107 #[test]
6108 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6109 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6110 assert!(text.contains("icmp slt %0, %1"), "{text}");
6111 assert!(!text.contains("zext"), "{text}");
6112 }
6113
6114 #[test]
6115 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6116 let text = body("int f(int a, int b) { return a && b; }\n");
6117 let expected = "\
6118block0(%0: i32, %1: i32):
6119 %2 = iconst.i32 0
6120 %3 = icmp ne %0, %2
6121 %4 = iconst.i1 0
6122 br_if %3, block1, block2(%4)
6123
6124block1:
6125 %5 = iconst.i32 0
6126 %6 = icmp ne %1, %5
6127 jump block2(%6)
6128
6129block2(%7: i1):
6130 %8 = zext.i32 %7
6131 return %8
6132";
6133 assert_eq!(text, expected);
6134 }
6135
6136 #[test]
6137 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6138 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6139 assert!(!text.contains("block3"), "{text}");
6142 assert!(!text.contains("iconst.i32 3"), "{text}");
6143 }
6144
6145 #[test]
6146 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6147 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6148 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6149 assert!(body("int f(void) { }\n").contains("unreachable"));
6150 }
6151
6152 #[test]
6153 fn a_structure_is_copied_rather_than_held_in_a_value() {
6154 let text = body(
6155 "struct point { int x, y; };\n\
6156 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6157 );
6158 assert!(text.contains("memcpy"), "{text}");
6159 }
6160
6161 #[test]
6162 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6163 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6164 assert!(text.contains("memset"), "{text}");
6165 }
6166
6167 #[test]
6168 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6169 let text = body(
6170 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6171 default: r = 4; } return r; }\n",
6172 );
6173 let expected = "\
6174block0(%0: i32):
6175 %1 = iconst.i32 0
6176 switch %0, block1, [1 => block2, 2 => block3(%1)]
6177
6178block1:
6179 %2 = iconst.i32 4
6180 jump block4(%2)
6181
6182block2:
6183 %3 = iconst.i32 1
6184 jump block3(%3)
6185
6186block3(%4: i32):
6187 %5 = iconst.i32 2
6188 %6 = add.nsw %4, %5
6189 jump block4(%6)
6190
6191block4(%7: i32):
6192 return %7
6193";
6194 assert_eq!(text, expected);
6195 }
6196
6197 #[test]
6198 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6199 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6202 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6203 assert!(text.contains("icmp ule"), "{text}");
6204 assert!(!text.contains("switch"), "{text}");
6205 }
6206
6207 #[test]
6208 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6209 let text = body(
6210 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6211 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6212 );
6213 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6216 assert!(text.contains("block5:\n jump block7("), "{text}");
6217 assert!(text.contains("block6:\n jump block8("), "{text}");
6218 }
6219
6220 #[test]
6221 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6222 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6223 }
6224
6225 #[test]
6226 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6227 let text = body(
6232 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6233 return n; }\n",
6234 );
6235 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6238 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6239 assert!(text.contains("block4:\n jump block3("), "{text}");
6240 }
6241
6242 #[test]
6243 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6244 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6247 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6248 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6249 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6250 }
6251
6252 #[test]
6253 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6254 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6255 assert!(!text.contains("alloca"), "{text}");
6259 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6260 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6261 }
6262
6263 #[test]
6264 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6265 let text =
6266 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6267 assert!(!text.contains("alloca"), "{text}");
6268 assert!(text.contains("block1(%2: i32):"), "{text}");
6269 assert!(text.contains("jump block1(%5)"), "{text}");
6270 }
6271
6272 #[test]
6273 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6274 assert_eq!(
6277 body("int f(int x) { return x; spare: return 0; }\n"),
6278 "block0(%0: i32):\n return %0\n"
6279 );
6280 }
6281
6282 #[test]
6283 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6284 let text = body(
6285 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6286 );
6287 assert_eq!(
6290 text,
6291 "\
6292block0(%0: ptr):
6293 %1 = load.i8 %0, align 1
6294 %2 = iconst.i8 3
6295 %3 = ashr %1, %2
6296 %4 = sext.i32 %3
6297 return %4
6298"
6299 );
6300 }
6301
6302 #[test]
6303 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6304 let text =
6308 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6309 assert_eq!(
6310 text,
6311 "\
6312block0(%0: ptr, %1: i32):
6313 %2 = iconst.i32 16777215
6314 %3 = and %1, %2
6315 %4 = trunc.i16 %3
6316 store %4 -> %0, align 2
6317 %5 = iconst.i32 16
6318 %6 = lshr %3, %5
6319 %7 = trunc.i8 %6
6320 %8 = iconst.i64 2
6321 %9 = ptr_add %0, %8
6322 store %7 -> %9, align 1
6323 return
6324"
6325 );
6326 }
6327
6328 #[test]
6329 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6330 let text =
6331 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6332 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6335 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6336 }
6337
6338 #[test]
6339 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6340 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6343 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6344 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6345 }
6346
6347 #[test]
6348 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6349 let text = body(
6353 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6354 );
6355 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6356 }
6357
6358 #[test]
6359 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6360 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6363 assert!(
6364 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6365 "{text}"
6366 );
6367 }
6368
6369 #[test]
6370 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6371 let text = ir(concat!(
6376 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6377 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6378 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6379 "char s[2] = \"hi\";\n",
6380 ));
6381 assert!(
6382 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6383 "{text}"
6384 );
6385 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6386 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6387 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6390 }
6391
6392 #[test]
6393 fn a_definition_takes_a_parameter_it_left_unnamed() {
6394 let text = ir("int f(int a, int) { return a; }\n");
6398 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6399 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6400
6401 let text = ir("int g(int, int n) { return n; }\n");
6404 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6405 }
6406
6407 #[test]
6408 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6409 let text = body(concat!(
6414 "struct s { int f; int g; };\n",
6415 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6416 "{ *d = *e = a[0] = *c; }\n",
6417 ));
6418 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6419 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6420 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6421 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6422 }
6423
6424 #[test]
6425 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6426 let mut opts = options();
6431 opts.emit = EmitKind::Ir;
6432 let result = run(
6433 &opts,
6434 concat!(
6435 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6436 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6437 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6438 "const union u c = { { \"1234\", \"567\" } };\n",
6439 ),
6440 );
6441 let text = result.text();
6442 assert_eq!(
6443 result.messages,
6444 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6445 (5 chars into 3 available) [E0637]"]
6446 );
6447 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6448 assert!(
6449 text.contains(
6450 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6451 bytes \"9\\00\", zero 3 }"
6452 ),
6453 "{text}"
6454 );
6455 assert!(
6458 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6459 "{text}"
6460 );
6461 }
6462
6463 #[test]
6464 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6465 let text = body(concat!(
6469 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6470 "void g(struct v *);\n",
6471 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6472 ));
6473 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6474 }
6475
6476 #[test]
6477 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6478 let text = ir(concat!(
6483 "struct s { int x; };\n",
6484 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6485 "int n = (int){ 7 };\n",
6486 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6487 ));
6488 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6489 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6490 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6493 }
6494
6495 #[test]
6496 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6497 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6501 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6502 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6503 }
6504
6505 #[test]
6506 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6507 let text = ir("unsigned char foo[1][0];\n");
6511 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6512 }
6513
6514 #[test]
6515 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
6516 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
6519 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
6520 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
6521 }
6522
6523 #[test]
6524 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
6525 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
6529 assert!(
6530 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
6531 "{text}"
6532 );
6533 }
6534
6535 #[test]
6536 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
6537 let text = body(
6542 "\
6543struct s { int a, b; };
6544struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
6545",
6546 );
6547 assert!(text.contains("block3(%7: ptr)"), "{text}");
6549 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
6550 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
6551 }
6552
6553 #[test]
6561 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
6562 let text = body("int f(int i) { return ++i ?: 10; }\n");
6563 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
6564 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6565
6566 let text = body("long f(int i) { return ++i ?: 10L; }\n");
6569 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
6570 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6571
6572 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
6574 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
6575
6576 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
6579 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
6580 }
6581
6582 #[test]
6583 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
6584 let text = ir("\
6588struct pair { int a, b; };
6589struct pair make(int a, int b);
6590struct pair twice(struct pair p) { return make(p.a, p.b); }
6591");
6592 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
6593 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
6594 }
6595
6596 #[test]
6597 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
6598 let text = ir("\
6602struct big { double v[8]; };
6603struct big grow(struct big b);
6604struct big twice(struct big b) { return grow(grow(b)); }
6605");
6606 assert!(
6607 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
6608 "{text}"
6609 );
6610 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
6611 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
6614 }
6615
6616 #[test]
6617 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
6618 let text = ir("\
6623struct big { double v[8]; };
6624struct pair { int a, b; };
6625int p(const char *, ...);
6626int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
6627");
6628 assert!(
6629 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
6630 "{text}"
6631 );
6632 }
6633
6634 #[test]
6635 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
6636 let body = body(
6639 "\
6640struct pair { int a, b; };
6641struct pair make(int a, int b);
6642int second(void) { return make(1, 2).b; }
6643",
6644 );
6645 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
6646 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
6647 }
6648
6649 #[test]
6650 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
6651 let source = "\
6655struct hfa { float x, y, z; };
6656int take(struct hfa h);
6657int give(struct hfa h) { return take(h); }
6658";
6659 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
6660 let mut opts = options();
6661 opts.emit = EmitKind::Ir;
6662 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
6663 let result = run(&opts, source);
6664 assert_eq!(result.messages, Vec::<String>::new());
6665 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
6666 }
6667
6668 #[test]
6669 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
6670 let source = "\
6673int use(int *);
6674void f(int n) {
6675 {
6676 int a[n];
6677 use(a);
6678 }
6679 use(0);
6680}
6681";
6682 let body = body(source);
6683 assert!(body.contains("mul.nsw"), "{body}");
6684 assert!(body.contains("stacksave"), "{body}");
6685 assert!(body.contains("alloca %"), "{body}");
6686 assert!(body.contains("stackrestore"), "{body}");
6687 }
6688
6689 #[test]
6690 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
6691 let source = "\
6696int use(int *);
6697int f(int n) {
6698 {
6699 int a[n];
6700 if (use(a)) goto out;
6701 use(0);
6702 }
6703out:
6704 return 0;
6705}
6706";
6707 let body = body(source);
6708 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
6710 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6711 assert!(after.starts_with(" %4\n jump block"), "{body}");
6712 }
6713
6714 #[test]
6715 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
6716 let source = "\
6720int use(int *);
6721int f(int n) {
6722 int a[n];
6723again:
6724 if (use(a)) goto again;
6725 return 0;
6726}
6727";
6728 let body = body(source);
6729 assert!(body.contains("stacksave"), "{body}");
6730 assert!(!body.contains("stackrestore"), "{body}");
6731 }
6732
6733 #[test]
6734 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
6735 let source = "\
6740int use(int *);
6741int f(int n) {
6742again:
6743 {
6744 int a[n];
6745 if (use(a)) goto again;
6746 }
6747 return 0;
6748}
6749";
6750 let body = body(source);
6751 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6752 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6753 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
6754 }
6755
6756 #[test]
6757 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
6758 let source = "\
6764int f(void);
6765void t(void) {
6766 int count = 10;
6767 for (; count--;) {
6768 int b[f()];
6769 int i;
6770 for (i = 0; i < f(); i++) {
6771 b[i] = count;
6772 }
6773 }
6774}
6775";
6776 let body = body(source);
6777 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6781 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6782 let next = after.split("\n\n").next().expect("the block the restore is in");
6785 assert!(next.contains("jump block1("), "{body}");
6786 }
6787
6788 #[test]
6789 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
6790 let source = "\
6793unsigned long f(int n) {
6794 int a[n];
6795 n = 0;
6796 return sizeof a;
6797}
6798";
6799 let body = body(source);
6800 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
6802 }
6803
6804 #[test]
6805 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
6806 let source = "\
6809int use(int);
6810int f(int x) {
6811 return ({
6812 int t = use(x);
6813 t * t;
6814 });
6815}
6816";
6817 let expected = "\
6818block0(%0: i32):
6819 %1 = call @use(%0) : (i32) -> i32
6820 %2 = mul.nsw %1, %1
6821 return %2
6822";
6823 assert_eq!(body(source), expected);
6824 }
6825
6826 #[test]
6827 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
6828 let source = "int f(int x) { return ({ return x; 0; }); }\n";
6832 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
6833 }
6834
6835 #[test]
6836 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
6837 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
6841 let expected = "\
6842block0(%0: ptr):
6843 %1 = va_arg.f64 %0
6844 %2 = va_arg.f64 %0
6845 %3 = fadd %1, %2
6846 return %3
6847";
6848 assert_eq!(body(source), expected);
6849 }
6850
6851 #[test]
6852 fn one_that_reads_a_structure_answers_where_the_object_is() {
6853 let source = "\
6867struct s { int a; long b; };
6868long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
6869";
6870 let expected = "\
6871block0(%0: ptr):
6872 %1 = alloca, size 16, align 16
6873 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
6874 memcpy %1, %2, size 16, align 8
6875 %3 = iconst.i64 8
6876 %4 = ptr_add %1, %3
6877 %5 = load.i64 %4, align 8, tbaa !1
6878 return %5
6879";
6880 assert_eq!(body(source), expected);
6881 }
6882
6883 #[test]
6887 fn the_classification_says_which_registers_the_object_arrived_in() {
6888 let source = "\
6889struct s { double a; double b; };
6890double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
6891";
6892 assert!(
6893 body(source)
6894 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
6895 "{}",
6896 body(source)
6897 );
6898
6899 let big = "\
6900struct s { long a[4]; };
6901long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
6902";
6903 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
6904 }
6905
6906 #[test]
6907 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
6908 let source = "\
6912int f(int c) {
6913 void *p = c ? &&one : &&two;
6914 goto *p;
6915one:
6916 return 1;
6917two:
6918 return 2;
6919}
6920";
6921 let expected = "\
6922block0(%0: i32):
6923 %1 = iconst.i32 0
6924 %2 = icmp ne %0, %1
6925 br_if %2, block1, block2
6926
6927block1:
6928 %3 = block_addr block3
6929 jump block4(%3)
6930
6931block2:
6932 %4 = block_addr block5
6933 jump block4(%4)
6934
6935block3:
6936 %5 = iconst.i32 1
6937 return %5
6938
6939block4(%6: ptr):
6940 indirect_br %6, block3, block5
6941
6942block5:
6943 %7 = iconst.i32 2
6944 return %7
6945";
6946 assert_eq!(body(source), expected);
6947 }
6948
6949 #[test]
6950 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
6951 let source = "void **next(void);
6954void f(void) { goto *next(); }
6955";
6956 let expected = "\
6957block0:
6958 %0 = call @next() : () -> ptr
6959 unreachable
6960";
6961 assert_eq!(body(source), expected);
6962 }
6963
6964 #[test]
6965 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
6966 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
6969 let expected = "\
6970block0:
6971 inline_asm.volatile \"mfence\", \"\", \"memory\"()
6972 return
6973";
6974 assert_eq!(body(source), expected);
6975 }
6976
6977 #[test]
6978 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
6979 let source = "\
6982int f(int x, int y) {
6983 int r;
6984 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
6985 return r + y;
6986}
6987";
6988 let expected = "\
6989block0(%0: i32, %1: i32):
6990 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
6991 %4 = add.nsw %2, %3
6992 return %4
6993";
6994 assert_eq!(body(source), expected);
6995 }
6996
6997 #[test]
6998 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
6999 let source = "\
7004struct pair { int a, b; };
7005int f(int x) {
7006 int slot = x;
7007 struct pair p = { x, x };
7008 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7009 return slot + p.a;
7010}
7011";
7012 let text = body(source);
7013 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7014 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7015 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7016 }
7017
7018 #[test]
7019 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7020 let source = "\
7025int f(int x) {
7026 int r = 7;
7027 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7028 return r;
7029away:
7030 return r;
7031}
7032";
7033 let expected = "\
7034block0(%0: i32):
7035 %1 = iconst.i32 7
7036 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7037
7038block1:
7039 return %2
7040
7041block2:
7042 return %1
7043";
7044 assert_eq!(body(source), expected);
7045 }
7046
7047 #[test]
7048 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7049 let mut opts = options();
7053 opts.emit = EmitKind::Ir;
7054 for (source, expected) in [
7055 (
7056 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7057 "output operand constraint lacks '='",
7058 ),
7059 (
7060 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7061 "lvalue required in 'asm' statement",
7062 ),
7063 (
7064 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7065 "read-only variable 'g' used as 'asm' output",
7066 ),
7067 (
7068 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7069 "input operand constraint contains '='",
7070 ),
7071 (
7072 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7073 "memory input 0 is not directly addressable",
7074 ),
7075 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7076 (
7077 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7078 "duplicate asm operand name 'a'",
7079 ),
7080 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7081 ] {
7082 let result = run(&opts, source);
7083 assert!(result.failed(), "expected this to be reported:\n{source}");
7084 assert!(
7085 result.messages.iter().any(|m| m.contains(expected)),
7086 "{expected}\n{:?}",
7087 result.messages
7088 );
7089 }
7090 }
7091
7092 #[test]
7097 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7098 let text = ir(concat!(
7099 "__asm__(\n",
7100 " \".section .rodata\\n\"\n",
7101 " \".globl first\\n\"\n",
7102 " \".balign 8\\n\"\n",
7103 " \"first:\\n\"\n",
7104 " \".long 1\\n\"\n",
7105 " \".long 2\\n\"\n",
7106 " \".globl last\\n\"\n",
7107 " \"last:\\n\"\n",
7108 " \".quad last - first\\n\");\n",
7109 "extern const int first[];\n",
7110 "extern const long last;\n",
7111 ));
7112 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7113 assert!(text.contains("global @last : i64 = 8"), "{text}");
7114 }
7115
7116 #[test]
7120 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7121 let text = ir(concat!(
7122 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7123 "extern int counter;\n",
7124 "int read(void) { return counter; }\n",
7125 ));
7126 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7127 }
7128
7129 #[test]
7132 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7133 let mut opts = options();
7134 opts.emit = EmitKind::Ir;
7135 let mut fs = MemoryFileSystem::new();
7136 fs.insert(
7137 "/main.c",
7138 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7139 );
7140 fs.insert("seed", b"hi".to_vec());
7141 let result = compile(&opts, "/main.c", &fs);
7142 assert_eq!(result.messages, Vec::<String>::new());
7143 let text = result.text();
7144 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7145 }
7146
7147 #[test]
7150 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7151 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7152 assert!(
7153 messages
7154 .iter()
7155 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7156 "{messages:?}"
7157 );
7158 }
7159
7160 #[test]
7163 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7164 for source in [
7165 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7166 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7167 ] {
7168 let messages = errors(source);
7169 assert!(
7170 messages
7171 .iter()
7172 .any(|m| m.contains("not supported yet")
7173 && m.contains("in an `asm` at file scope")),
7174 "{source}\n{messages:?}"
7175 );
7176 }
7177 }
7178
7179 #[test]
7180 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7181 let mut opts = options();
7182 opts.emit = EmitKind::Ir;
7183 for source in [
7184 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7185 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7186 ] {
7187 let result = run(&opts, source);
7188 assert!(result.failed(), "expected this to be reported:\n{source}");
7189 assert!(
7190 result.messages.iter().any(|m| m.contains("not supported yet")),
7191 "{:?}",
7192 result.messages
7193 );
7194 }
7195 }
7196
7197 fn round_trip(source: &str) -> (String, String) {
7199 let printed = ir(source);
7200 let mut opts = options();
7201 opts.emit = EmitKind::Ir;
7202 let mut fs = MemoryFileSystem::new();
7203 fs.insert("/main.ir", printed.clone().into_bytes());
7204 let result = compile_ir(&opts, "/main.ir", &fs);
7205 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7206 (printed, result.text().to_owned())
7207 }
7208
7209 #[test]
7210 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7211 let (printed, again) = round_trip(
7215 "struct point { int x, y; };\n static const char greeting[] = \"hi\";\n int puts(const char *);\n int f(int n) { struct point p = { n, 1 }; puts(greeting); return p.x; }\n",
7216 );
7217 assert_eq!(printed, again);
7218 }
7219
7220 #[test]
7221 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7222 let mut opts = options();
7223 opts.emit = EmitKind::Ir;
7224 let mut fs = MemoryFileSystem::new();
7225 let text = "\
7226; ModuleID = 'a.c'
7227; format 0
7228target triple = \"x86_64-unknown-linux-gnu\"
7229target datalayout = \"e-p:64:64-i64:64-S128\"
7230
7231func @f(), linkage(external) {
7232block0:
7233 frobnicate
7234}
7235";
7236 fs.insert("/main.ir", text.as_bytes().to_vec());
7237 let result = compile_ir(&opts, "/main.ir", &fs);
7238 assert!(result.failed());
7239 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7240 }
7241
7242 #[test]
7243 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7244 let mut opts = options();
7247 opts.emit = EmitKind::Ir;
7248 let mut fs = MemoryFileSystem::new();
7249 let text = "\
7250; ModuleID = 'a.c'
7251; format 0
7252target triple = \"x86_64-unknown-linux-gnu\"
7253target datalayout = \"e-p:64:64-i64:64-S128\"
7254
7255func @f(), linkage(external) {
7256block0:
7257 %0 = iconst.i32 1
7258 return %0
7259}
7260";
7261 fs.insert("/main.ir", text.as_bytes().to_vec());
7262 let result = compile_ir(&opts, "/main.ir", &fs);
7263 assert!(result.failed());
7264 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7265 }
7266
7267 #[test]
7268 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7269 let mut fs = MemoryFileSystem::new();
7271 fs.insert("/main.ir", Vec::new());
7272 let result = compile_ir(&options(), "/main.ir", &fs);
7273 assert!(result.failed());
7274 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7275 }
7276
7277 #[test]
7278 fn the_printed_ir_reads_back_as_the_same_module() {
7279 let text = ir("\
7282struct point { int x, y; };
7283static const char greeting[] = \"hi\";
7284int table[4] = { 1, 2, 3 };
7285int puts(const char *);
7286double half(double x) { return x / 2.0; }
7287int f(int n) {
7288 int total = 0;
7289 for (int i = 0; i < n; i++) {
7290 if (i == 3) continue;
7291 total += table[i];
7292 }
7293 switch (n) {
7294 case 0: total = 1;
7295 case 1: total++; break;
7296 default: total = -total;
7297 }
7298 struct point p = { total, 1 };
7299 int *q = &p.y;
7300 puts(greeting);
7301 return p.x + *q;
7302}
7303int dispatch(int c) {
7304 void *p = c ? &&one : &&two;
7305 goto *p;
7306one:
7307 return 1;
7308two:
7309 return 2;
7310}
7311int assembly(int x, int *p) {
7312 int r;
7313 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7314 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7315 return r;
7316away:
7317 return 0;
7318}
7319");
7320 let mut names = Interner::new();
7321 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7322 assert_eq!(rucc_ir::print(&module, &names), text);
7323 }
7324
7325 #[test]
7326 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7327 let mut opts = options();
7331 opts.emit = EmitKind::Object;
7332 opts.save_temps = rucc_session::SaveTemps::Object;
7333 let result = run(&opts, "#define N 2\nint a[N];\n");
7334 assert_eq!(result.messages, Vec::<String>::new());
7335 let text = result.temps.preprocessed.expect("the preprocessed text");
7336 assert!(text.contains("int a[2];"), "{text}");
7337 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7338 let asm = result.temps.assembly.expect("the assembly");
7339 assert!(asm.contains("a:"), "{asm}");
7340 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7341 }
7342
7343 #[test]
7344 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7345 let mut opts = options();
7348 opts.emit = EmitKind::Object;
7349 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7350 }
7351
7352 #[test]
7353 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7354 let mut opts = options();
7357 opts.emit = EmitKind::Ir;
7358 opts.save_temps = rucc_session::SaveTemps::Cwd;
7359 let result = run(&opts, "int a;\n");
7360 assert!(result.temps.preprocessed.is_some());
7361 assert_eq!(result.temps.assembly, None);
7362 }
7363}