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_cost::Goal;
22use rucc_diag::{Diagnostic, Severity, Span};
23use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
24use rucc_lex::{Convert, Keywords, PpToken, convert};
25use rucc_lower::Protector as LowerProtector;
26use rucc_sema::{Checker, Context as CheckContext};
27use rucc_session::{
28 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
29};
30use rucc_target::TargetInfo;
31use rucc_tuple::{Arch, ObjectFormat};
32
33use crate::preprocess::render;
34
35#[derive(Debug, Clone, PartialEq, Eq, Default)]
42pub enum Artifact {
43 #[default]
46 Nothing,
47 Text(String),
49 Object {
56 bytes: Vec<u8>,
58 defines: Vec<String>,
62 },
63}
64
65impl Artifact {
66 #[must_use]
68 pub fn bytes(&self) -> &[u8] {
69 match self {
70 Artifact::Nothing => &[],
71 Artifact::Text(text) => text.as_bytes(),
72 Artifact::Object { bytes, .. } => bytes,
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct Compiled {
80 pub artifact: Artifact,
82 pub messages: Vec<String>,
84 pub errors: u32,
86 pub fired: Fired,
92 pub pressure: Pressure,
97 pub lowerings: Lowerings,
102 pub dumps: Vec<rucc_opt::Dump>,
108 pub remarks: String,
114 pub deps: Vec<rucc_pp::Dependency>,
119 pub temps: Temps,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Default)]
135pub struct Temps {
136 pub preprocessed: Option<String>,
138 pub assembly: Option<String>,
140}
141
142impl Compiled {
143 #[must_use]
145 pub fn failed(&self) -> bool {
146 self.errors > 0
147 }
148
149 #[must_use]
154 pub fn text(&self) -> &str {
155 match &self.artifact {
156 Artifact::Text(text) => text,
157 _ => "",
158 }
159 }
160}
161
162#[must_use]
175pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
176 let mut sess = Session::new(opts.clone());
177 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
181 let mut diagnostics: Vec<Diagnostic> = Vec::new();
182 let mut fired = Fired::new();
184 let mut pressure = Pressure::new();
186 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
187 let mut dumps = Vec::new();
189 let mut remarks = String::new();
190 let mut temps = Temps::default();
192
193 let bytes = match fs.read(Path::new(name)) {
194 Ok(bytes) => bytes,
195 Err(e) => return failure(format!("{name}: {e}")),
196 };
197 let Ok(file) = sess.sources.add_shared(crate::phase::source_name(name), bytes, None) else {
198 return failure(format!("{name}: the source map has no room left for this file"));
199 };
200
201 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
205 let predef = rucc_pp::Predef::for_options(opts);
206 let expanded: Vec<PpToken> = {
207 let mut tokens = Vec::new();
208 {
213 let mut cx =
214 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
215 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
216 cx.pedantic = opts.pedantic;
217 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
218 return failure(format!(
219 "{name}: the source map has no room for the built in macros"
220 ));
221 }
222 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
223 return failure(format!("{name}: the source map has no room for the command line"));
224 }
225 tokens.append(&mut pp.run(file, &mut cx));
226 }
227 if opts.save_temps.wanted() {
228 temps.preprocessed = Some(rucc_pp::print(
229 file,
230 &tokens,
231 pp.line_directives(),
232 &sess.sources,
233 &sess.interner,
234 rucc_pp::PrintOptions { line_markers: opts.line_markers },
235 ));
236 }
237 tokens.iter().map(|token| token.to_pp()).collect()
238 };
239 diagnostics.extend(pp.take_diagnostics());
240 let deps = pp.dependencies().to_vec();
243
244 let cx = Convert {
247 keywords: &keywords,
248 interner: &sess.interner,
249 target: &sess.target,
250 std: opts.std,
251 gnu: opts.gnu_extensions,
252 pedantic: opts.pedantic,
253 };
254 let (tokens, complaints) = convert(&expanded, &cx);
255 diagnostics.extend(complaints);
256
257 let parsed = rucc_parse::parse(
258 &tokens,
259 rucc_parse::Context {
260 interner: &sess.interner,
261 std: opts.std,
262 gnu: opts.gnu_extensions,
263 pedantic: opts.pedantic,
264 error_limit: opts.error_limit as usize,
265 },
266 );
267 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
268 diagnostics.extend(parsed.diagnostics);
269
270 let mut artifact = Artifact::Nothing;
271 let mut instrumented = Instrumented::default();
274 if !parse_failed {
275 let mut checker = Checker::new(
276 &parsed.ast,
277 CheckContext {
278 names: &sess.interner,
279 target: &sess.target,
280 std: opts.std,
281 gnu: opts.gnu_extensions,
282 pedantic: opts.pedantic,
283 permissive: opts.permissive,
284 gnu89_inline: opts.gnu89_inline,
285 error_limit: opts.error_limit as usize,
286 builtins: opts.builtins && opts.hosted,
289 no_builtin: &opts.no_builtin,
290 short_enums: opts.short_enums,
291 ms_extensions: sess.ms_extensions(),
292 trapping_math: opts.trapping_math,
293 },
294 );
295 checker.check_unit();
296 let checked = checker.finish();
297 if !checked.failed() {
298 match opts.emit {
299 EmitKind::Tast => {
300 artifact = Artifact::Text(rucc_sema::print(
301 &checked.tast,
302 &checked.types,
303 &sess.interner,
304 ));
305 }
306 EmitKind::TypeGranules => {
310 artifact = Artifact::Text(rucc_types::granule_report(
311 &checked.types,
312 &sess.interner,
313 &sess.target,
314 ));
315 }
316 EmitKind::Ir
317 | EmitKind::MirFinal
318 | EmitKind::Asm
319 | EmitKind::Object
320 | EmitKind::Archive
321 | EmitKind::Executable
322 | EmitKind::SafetySummary => {
323 let mut read = |named: &str| {
328 fs.read(Path::new(named))
329 .map(|bytes| bytes.as_slice().to_vec())
330 .map_err(|why| why.to_string())
331 };
332 let mut lowered = rucc_lower::lower(
333 crate::phase::source_name(name),
334 rucc_lower::Context {
335 tast: &checked.tast,
336 types: &checked.types,
337 target: &sess.target,
338 names: &mut sess.interner,
339 visibility: match opts.visibility {
340 Visibility::Default => IrVisibility::Default,
341 Visibility::Hidden => IrVisibility::Hidden,
342 Visibility::Protected => IrVisibility::Protected,
343 },
344 protector: match opts.protector {
345 Protector::None => LowerProtector::None,
346 Protector::Buffers => LowerProtector::Buffers,
347 Protector::Strong => LowerProtector::Strong,
348 Protector::All => LowerProtector::All,
349 },
350 wrapping: rucc_lower::Wrapping {
351 signed: opts.wrapping.signed,
352 pointer: opts.wrapping.pointer,
353 trap: opts.wrapping.trap,
354 },
355 aliasing: opts.strict_aliasing,
356 padding: opts.padding == Padding::Ignored,
357 contract: match opts.fp_contract {
358 Contract::Off => FpContract::Off,
359 Contract::On => FpContract::On,
360 Contract::Fast => FpContract::Fast,
361 },
362 align: opts.align_functions,
363 read: &mut read,
364 },
365 );
366 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
370 if !failed {
371 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
376 for error in errors {
377 diagnostics.push(internal(&format!("invalid IR, {error}")));
378 }
379 } else if let Err(complaints) =
380 instrument(&mut lowered.module, &mut sess.interner, opts)
381 .map(|done| instrumented = done)
382 {
383 diagnostics.extend(complaints);
384 } else if let Err(complaints) = optimize(
385 &mut lowered.module,
386 &sess.interner,
387 &sess.target,
388 opts,
389 name,
390 &mut dumps,
391 &mut remarks,
392 ) {
393 diagnostics.extend(complaints);
394 } else if opts.emit == EmitKind::SafetySummary {
395 artifact = Artifact::Text(
400 rucc_safety::summarize(
401 &lowered.module,
402 &sess.interner,
403 name,
404 opts.safety.as_str(),
405 instrumented.checks,
406 instrumented.interposed,
407 instrumented.crossings,
408 )
409 .render(),
410 );
411 } else if opts.emit == EmitKind::Ir {
412 artifact =
417 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
418 } else {
419 match generate(
422 &mut lowered.module,
423 &mut sess.interner,
424 &sess.target,
425 opts,
426 &mut Recording {
427 fired: &mut fired,
428 pressure: &mut pressure,
429 lowerings: &mut lowerings,
430 },
431 &mut temps.assembly,
432 ) {
433 Ok(made) => artifact = made,
434 Err(complaints) => diagnostics.extend(complaints),
435 }
436 }
437 }
438 diagnostics.extend(lowered.diagnostics);
439 }
440 _ => {}
441 }
442 }
443 diagnostics.extend(checked.diagnostics);
444 }
445
446 let mut messages = Vec::with_capacity(diagnostics.len());
447 let mut errors = 0;
448 for diag in &diagnostics {
449 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
455 continue;
456 }
457 if diag.severity.is_fatal()
458 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
459 {
460 errors += 1;
461 }
462 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
463 }
464 if errors > 0 {
465 artifact = Artifact::Nothing;
467 }
468 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
471}
472
473#[must_use]
483pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
484 let mut sess = Session::new(opts.clone());
485 if opts.emit != EmitKind::Ir {
486 return failure(format!(
487 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
488 the C in front of it became",
489 opts.emit.as_str()
490 ));
491 }
492 let bytes = match fs.read(Path::new(name)) {
493 Ok(bytes) => bytes,
494 Err(e) => return failure(format!("{name}: {e}")),
495 };
496 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
497 return failure(format!("{name}: this is not text, so it is not IR"));
498 };
499
500 let module = match rucc_ir::parse(text, &mut sess.interner) {
501 Ok(module) => module,
502 Err(error) => {
503 return failure(format!("{name}:{}: {}", error.line, error.message));
504 }
505 };
506 let mut diagnostics: Vec<Diagnostic> = Vec::new();
507 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
508 for error in errors {
509 diagnostics.push(invalid(&format!("invalid IR, {error}")));
510 }
511 }
512 let mut messages = Vec::with_capacity(diagnostics.len());
513 for diag in &diagnostics {
514 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
515 }
516 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
517 let artifact = if errors > 0 {
518 Artifact::Nothing
519 } else {
520 Artifact::Text(rucc_ir::print(&module, &sess.interner))
521 };
522 Compiled {
524 artifact,
525 messages,
526 errors,
527 fired: Fired::new(),
528 pressure: Pressure::new(),
529 lowerings: Lowerings::new(),
530 dumps: Vec::new(),
531 remarks: String::new(),
532 deps: Vec::new(),
533 temps: Temps::default(),
534 }
535}
536
537fn instrument(
560 module: &mut rucc_ir::Module,
561 names: &mut Interner,
562 opts: &Options,
563) -> Result<Instrumented, Vec<Diagnostic>> {
564 if !opts.safety.instruments() {
565 return Ok(Instrumented::default());
566 }
567 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
568 checks.freed = rucc_safety::ending::checks(module, names);
576 let interposed = rucc_safety::redirect(module, names);
581 let crossings = rucc_safety::witness(module, names);
584 match rucc_ir::verify(module, names) {
585 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
586 Err(errors) => Err(errors
587 .iter()
588 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
589 .collect()),
590 }
591}
592
593#[derive(Clone, Copy, Debug, Default)]
599struct Instrumented {
600 checks: rucc_safety::Counts,
602 interposed: usize,
604 crossings: rucc_safety::Sites,
606}
607
608fn optimize(
620 module: &mut rucc_ir::Module,
621 names: &Interner,
622 target: &TargetInfo,
623 opts: &Options,
624 file: &str,
625 dumps: &mut Vec<rucc_opt::Dump>,
626 remarks: &mut String,
627) -> Result<(), Vec<Diagnostic>> {
628 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
629 settings.interposition = match opts.interposition {
635 true => replaceable(target, opts),
636 false => IrPic::Executable,
637 };
638 settings.toggles.clone_from(&opts.passes);
639 settings.fuel = opts.pass_fuel.iter().cloned().collect();
640 settings.global_fuel = opts.pass_fuel_global;
641 settings.verify |= opts.verify_each;
642 for (on, spec) in &opts.pass_gates {
643 if let Err(why) = settings.gates.add(*on, spec) {
646 return Err(vec![internal(&why)]);
647 }
648 }
649 for spec in &opts.dump_ir {
650 if let Err(why) = settings.dumps.add(spec) {
653 return Err(vec![internal(&why)]);
654 }
655 }
656 let mut wants = rucc_opt::Wants::none();
657 for spec in &opts.opt_info {
658 if let Err(why) = wants.add(spec) {
661 return Err(vec![internal(&why)]);
662 }
663 }
664 let report = rucc_opt::run(module, names, &settings);
665 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
666 dumps.extend(report.dumps);
667 match report.broke.is_empty() {
668 true => Ok(()),
669 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
670 }
671}
672
673fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
707 match (target.tuple.os().object_format(), opts.pic) {
708 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
709 _ => IrPic::Executable,
710 }
711}
712
713fn generate(
714 module: &mut rucc_ir::Module,
715 names: &mut Interner,
716 target: &TargetInfo,
717 opts: &Options,
718 recording: &mut Recording<'_>,
719 assembly: &mut Option<String>,
720) -> Result<Artifact, Vec<Diagnostic>> {
721 let Some(machine) = Machine::for_target(target) else {
722 return Err(vec![unsupported(&format!(
723 "there is no back end for {} in this compiler yet, so there is nothing to generate",
724 target.tuple
725 ))]);
726 };
727 if opts.protector != Protector::None && machine.conv.guard.is_none() {
732 return Err(vec![unsupported(&format!(
733 "{} is not supported for {} yet, because the stack protector on that target is not \
734 the one this compiler writes",
735 opts.protector, target.tuple
736 ))]);
737 }
738 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
744 return Err(vec![unsupported(&format!(
745 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
746 for it there is not the note this compiler writes",
747 opts.control, target.tuple
748 ))]);
749 }
750 let profile = match machine.conv.trace {
756 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
757 None if opts.profile => {
758 return Err(vec![unsupported(&format!(
759 "-pg is not supported for {} yet, because the profiler's hook on that target is \
760 not the one this compiler calls",
761 target.tuple
762 ))]);
763 }
764 None => None,
765 };
766 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
771 return Err(vec![unsupported(&format!(
772 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
773 the room is there is not the section this compiler writes",
774 target.tuple
775 ))]);
776 }
777 let flags = pipeline::Flags {
778 frame_pointer: opts.frame_pointer,
779 red_zone: opts.red_zone,
780 stack_clash: opts.stack_clash,
781 landing: opts.control.branch(),
782 profile: match profile {
783 None => pipeline::Profile::No,
784 Some(true) => pipeline::Profile::Early,
785 Some(false) => pipeline::Profile::Late,
786 },
787 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
788 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
795 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
800 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
805 accurate: opts.cycle_accurate_model,
807 verify: opts.verify_each,
810 goal: Goal::for_size(opts.opt_level.is_size()),
815 };
816
817 if opts.safety.instruments() {
826 rucc_opt::heap::annotate(module, names);
836 rucc_safety::handover::arrange(module);
843 rucc_safety::lower(module, names);
844 if let Err(errors) = rucc_ir::verify(module, names) {
845 return Err(errors
846 .iter()
847 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
848 .collect());
849 }
850 }
851
852 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
861
862 let mut funcs = Vec::new();
863 let mut complaints = Vec::new();
864 for id in module.funcs() {
865 if module[id].is_declaration() {
866 continue;
867 }
868 match pipeline::compile_recording(
869 &mut module[id],
870 names,
871 &machine,
872 &elsewhere,
873 flags,
874 recording,
875 ) {
876 Ok(func) => funcs.push(func),
877 Err(why) => {
878 let name = names.resolve(module[id].name).to_owned();
879 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
882 let said = format!("cannot generate code for '{name}': {why}");
883 complaints.push(unsupported_at(&said, span));
884 }
885 }
886 }
887 if !complaints.is_empty() {
888 return Err(complaints);
889 }
890 let (globals, aliases) = match opts.emit {
896 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
897 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
898 rucc_asm::aliases(module, names).map_err(refused)?,
899 ),
900 _ => (rucc_asm::Globals::default(), Vec::new()),
901 };
902 let unwind = opts.unwinds();
906 match opts.emit {
907 EmitKind::Asm => {
908 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
909 .map(Artifact::Text)
910 .map_err(refused)
911 }
912 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
916 if opts.save_temps.wanted() {
917 let listing = rucc_asm::print(
918 &funcs,
919 &globals,
920 &aliases,
921 names,
922 target,
923 unwind,
924 output(opts, target),
925 );
926 *assembly = Some(listing.map_err(refused)?);
927 }
928 let text = rucc_asm::assemble(&funcs, names, target, unwind).map_err(refused)?;
929 let data = globals.image();
930 let bytes = rucc_object::write(&text, &data, &aliases, target, output(opts, target))
933 .map_err(wrote)?;
934 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
939 Ok(Artifact::Object { bytes, defines })
940 }
941 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
942 }
943}
944
945fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
957 let mut features = 0;
958 if target.tuple.arch() == Arch::X86_64 {
959 if opts.control.branch() {
960 features |= rucc_object::Property::IBT;
961 }
962 if opts.control.ret() {
963 features |= rucc_object::Property::SHSTK;
964 }
965 }
966 rucc_object::Output {
967 sections: rucc_object::Sections {
968 functions: opts.function_sections,
969 data: opts.data_sections,
970 },
971 property: rucc_object::Property { features },
972 }
973}
974
975fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
981 match why {
982 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
983 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
984 }
985}
986
987fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
994 match why {
995 rucc_asm::Error::Thread { .. }
996 | rucc_asm::Error::IFunc { .. }
997 | rucc_asm::Error::Frame { .. } => {
998 vec![unsupported(&why.to_string())]
999 }
1000 _ => vec![internal(&why.to_string())],
1001 }
1002}
1003
1004fn unsupported(message: &str) -> Diagnostic {
1010 unsupported_at(message, Span::DUMMY)
1011}
1012
1013fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1019 Diagnostic::error(message.to_owned(), span)
1020 .with_code("E0653")
1021 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1022}
1023
1024fn invalid(message: &str) -> Diagnostic {
1026 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1027}
1028
1029fn internal(message: &str) -> Diagnostic {
1031 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1032 .with_code("E0652")
1033 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1034}
1035
1036fn failure(message: String) -> Compiled {
1039 Compiled {
1040 artifact: Artifact::Nothing,
1041 messages: vec![format!("rucc: error: {message}")],
1042 errors: 1,
1043 fired: Fired::new(),
1044 pressure: Pressure::new(),
1045 lowerings: Lowerings::new(),
1046 dumps: Vec::new(),
1047 remarks: String::new(),
1048 deps: Vec::new(),
1049 temps: Temps::default(),
1050 }
1051}
1052
1053#[cfg(test)]
1054mod tests {
1055 use rucc_session::{MemoryFileSystem, Std};
1056 use rucc_target::Triple;
1057
1058 use super::*;
1059
1060 fn options() -> Options {
1061 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1062 opts.emit = EmitKind::Tast;
1063 opts
1064 }
1065
1066 fn run(opts: &Options, source: &str) -> Compiled {
1067 let mut fs = MemoryFileSystem::new();
1068 fs.insert("/main.c", source.to_owned().into_bytes());
1069 compile(opts, "/main.c", &fs)
1070 }
1071
1072 fn freestanding() -> Options {
1076 let mut opts = options();
1077 opts.hosted = false;
1078 opts.search.push_system(rucc_session::runtime::DIR);
1079 opts
1080 }
1081
1082 fn shipped(source: &str) -> String {
1084 let result = run(&freestanding(), source);
1085 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1086 result.text().to_owned()
1087 }
1088
1089 fn tast(source: &str) -> String {
1091 let result = run(&options(), source);
1092 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1093 result.text().to_owned()
1094 }
1095
1096 #[test]
1097 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1098 let text = shipped(concat!(
1099 "#include <stdarg.h>\n",
1100 "int sum(int n, ...) {\n",
1101 " va_list ap, copy;\n",
1102 " va_start(ap, n);\n",
1103 " va_copy(copy, ap);\n",
1104 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1105 " va_end(ap);\n",
1106 " va_end(copy);\n",
1107 " return total;\n",
1108 "}\n",
1109 ));
1110 assert!(text.contains("va-start"), "{text}");
1111 assert!(text.contains("va-copy"), "{text}");
1112 assert!(text.contains("va-arg"), "{text}");
1113 assert!(text.contains("va-end"), "{text}");
1114 }
1115
1116 #[test]
1120 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1121 let text = shipped(concat!(
1122 "#define __need___va_list\n",
1123 "#include <stdarg.h>\n",
1124 "int vprint(const char *f, __gnuc_va_list ap);\n",
1125 "#ifdef va_start\n",
1126 "#error va_start should not be defined\n",
1127 "#endif\n",
1128 "#ifdef _VA_LIST_DEFINED\n",
1129 "#error va_list should not have been made\n",
1130 "#endif\n",
1131 ));
1132 assert!(text.contains("vprint"), "{text}");
1133 }
1134
1135 #[test]
1138 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1139 let text = shipped(concat!(
1140 "#define __need_size_t\n",
1141 "#include <stddef.h>\n",
1142 "#ifdef offsetof\n",
1143 "#error offsetof should not be defined yet\n",
1144 "#endif\n",
1145 "#define __need_ptrdiff_t\n",
1146 "#include <stddef.h>\n",
1147 "#include <stddef.h>\n",
1148 "size_t a;\n",
1149 "ptrdiff_t b;\n",
1150 "wchar_t c;\n",
1151 "max_align_t d;\n",
1152 "void *e = NULL;\n",
1153 "struct P { int x; long y; };\n",
1154 "size_t f = offsetof(struct P, y);\n",
1155 ));
1156 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1157 assert!(text.contains("decl #1 b : long"), "{text}");
1158 }
1159
1160 #[test]
1161 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1162 let text = shipped(concat!(
1163 "#include <limits.h>\n",
1164 "#include <float.h>\n",
1165 "int bits = CHAR_BIT;\n",
1166 "long big = LONG_MAX;\n",
1167 "int low = INT_MIN;\n",
1168 "int radix = FLT_RADIX;\n",
1169 "int digits = DBL_MANT_DIG;\n",
1170 ));
1171 assert!(text.contains("const 8 : int"), "{text}");
1172 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1173 assert!(text.contains("const 2 : int"), "{text}");
1174 assert!(text.contains("const 53 : int"), "{text}");
1175 }
1176
1177 #[test]
1181 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1182 let text = shipped(concat!(
1183 "#include <stdint.h>\n",
1184 "int64_t a = INT64_C(1);\n",
1185 "uint_least16_t b;\n",
1186 "intptr_t c;\n",
1187 "uintmax_t d = UINTMAX_MAX;\n",
1188 "int wide = sizeof(int_fast64_t);\n",
1189 ));
1190 assert!(text.contains("decl #0 a : long"), "{text}");
1191 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1192 assert!(text.contains("decl #2 c : long"), "{text}");
1193 }
1194
1195 #[test]
1206 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1207 let text = shipped(concat!(
1208 "#include <mmintrin.h>\n",
1209 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1210 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1211 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1212 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1213 "void done(void) { _mm_empty(); }\n",
1214 ));
1215 assert!(text.contains("add"), "{text}");
1216 assert!(text.contains("pack"), "{text}");
1217 assert!(text.contains("shift"), "{text}");
1218 }
1219
1220 #[test]
1225 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1226 let text = shipped(concat!(
1227 "#include <mm_malloc.h>\n",
1228 "void *get(void) { return _mm_malloc(64, 16); }\n",
1229 "void put(void *p) { _mm_free(p); }\n",
1230 ));
1231 assert!(text.contains("get"), "{text}");
1232 assert!(text.contains("put"), "{text}");
1233 }
1234
1235 #[test]
1247 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1248 let text = shipped(concat!(
1249 "#include <xmmintrin.h>\n",
1250 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1251 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1252 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1253 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1254 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1255 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1256 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1257 "void *room(void) { return _mm_malloc(64, 16); }\n",
1258 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1259 ));
1260 assert!(text.contains("add"), "{text}");
1261 assert!(text.contains("mask"), "{text}");
1262 assert!(text.contains("pick"), "{text}");
1263 assert!(text.contains("wide"), "{text}");
1264 }
1265
1266 #[test]
1273 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1274 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1275 for absent in [
1276 "_mm_sqrt_ps",
1277 "_mm_sqrt_ss",
1278 "_mm_rsqrt_ps",
1279 "_mm_rsqrt_ss",
1280 "_mm_getcsr",
1281 "_mm_setcsr",
1282 ] {
1283 let defined = text.contains(&format!("{absent}("));
1284 assert!(!defined, "{absent} is defined and the header says it is not");
1285 assert!(text.contains(absent), "{absent} is absent and unexplained");
1286 }
1287 }
1288
1289 #[test]
1290 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1291 let text = shipped(concat!(
1292 "#include <emmintrin.h>\n",
1293 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1294 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1295 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1296 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1297 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1298 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1299 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1300 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1301 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1302 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1303 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1304 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1305 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1306 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1307 ));
1308 assert!(text.contains("wide"), "{text}");
1309 assert!(text.contains("pack"), "{text}");
1310 assert!(text.contains("near"), "{text}");
1311 assert!(text.contains("half"), "{text}");
1312 }
1313
1314 #[test]
1318 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1319 let text = shipped(concat!(
1320 "#include <immintrin.h>\n",
1321 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1322 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1323 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1324 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1325 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1326 "}\n",
1327 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1328 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1329 ));
1330 assert!(text.contains("matching"), "{text}");
1331 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1332 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1333 }
1334
1335 #[test]
1339 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1340 let text = shipped(concat!(
1341 "#include <x86intrin.h>\n",
1342 "void barriers(void *p) {\n",
1343 " _mm_lfence();\n",
1344 " _mm_sfence();\n",
1345 " _mm_mfence();\n",
1346 " _mm_pause();\n",
1347 " _mm_clflush(p);\n",
1348 "}\n",
1349 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1350 ));
1351 assert!(text.contains("barriers"), "{text}");
1352 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1353 }
1354
1355 #[test]
1359 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1360 let text = shipped(concat!(
1361 "#include <immintrin.h>\n",
1362 "#include <emmintrin.h>\n",
1363 "#include <immintrin.h>\n",
1364 "#include <x86intrin.h>\n",
1365 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1366 ));
1367 assert!(text.contains("twice"), "{text}");
1368 }
1369
1370 #[test]
1374 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1375 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1376 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1377 let defined = text.contains(&format!("{absent}("));
1378 assert!(!defined, "{absent} is defined and the header says it is not");
1379 assert!(text.contains(absent), "{absent} is absent and unexplained");
1380 }
1381 }
1382
1383 #[test]
1384 fn the_three_formality_headers_still_have_to_work() {
1385 let text = shipped(concat!(
1386 "#include <stdbool.h>\n",
1387 "#include <stdalign.h>\n",
1388 "#include <iso646.h>\n",
1389 "#include <stdnoreturn.h>\n",
1390 "int t = true and not false;\n",
1391 "_Alignas(16) char buf[16];\n",
1392 "int a = alignof(long);\n",
1393 ));
1394 assert!(text.contains("decl #0 t : int"), "{text}");
1395 assert!(text.contains("const 8 : unsigned long"), "{text}");
1396 }
1397
1398 #[test]
1406 fn every_shipped_header_can_be_included_twice() {
1407 let once: String = rucc_session::runtime::names()
1408 .iter()
1409 .map(|name| format!("#include <{name}>\n"))
1410 .collect();
1411 let twice = once.repeat(2);
1412 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1413 }
1414
1415 #[test]
1416 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1417 let fs = MemoryFileSystem::new();
1418 let result = compile(&options(), "/nope.c", &fs);
1419 assert!(result.failed());
1420 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1421 assert!(result.text().is_empty());
1422 }
1423
1424 #[test]
1425 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1426 let text = tast("int x = 1;\n");
1427 let expected = "\
1428decl #0 x : int object external static defined
1429 init
1430 +0
1431 const 1 : int
1432";
1433 assert_eq!(text, expected);
1434 }
1435
1436 #[test]
1437 fn the_macros_are_expanded_before_anything_is_parsed() {
1438 let text = tast("#define N 2\nint a[N];\n");
1442 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1443 }
1444
1445 #[test]
1451 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1452 let text = tast(concat!(
1453 "#pragma pack(4)\n",
1454 "struct s { int a; };\n",
1455 "#pragma pack()\n",
1456 "int b;\n",
1457 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1458 ));
1459 assert!(text.contains("decl #0 b : int"), "{text}");
1460 assert!(text.contains("decl #1 c : int"), "{text}");
1461 }
1462
1463 #[test]
1471 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1472 tast(concat!(
1473 "struct A { char c; int i; } __attribute__((packed));\n",
1474 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1475 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1476 "struct B { char c; int i; } __attribute__((aligned));\n",
1479 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1480 "struct C { char c; int i __attribute__((packed)); };\n",
1481 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1482 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1483 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1484 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1485 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1486 "struct E { char c; _Alignas(8) int i; };\n",
1487 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1488 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1489 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1490 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1491 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1494 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1495 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1496 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1497 "struct I { [[gnu::packed]] char c; int i; };\n",
1500 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1501 "struct J { char c; [[gnu::packed]] int i; };\n",
1502 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1503 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1504 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1505 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1506 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1507 "union L { char c; int i; } __attribute__((packed));\n",
1508 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1509 "struct O { char c; int i; } __attribute__((__packed__));\n",
1513 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
1514 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
1515 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
1516 ));
1517 }
1518
1519 #[test]
1532 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
1533 let text = tast(concat!(
1534 "struct one { int x; };\n",
1535 "struct two { long y; };\n",
1536 "typedef union { struct one *a; struct two *b; void *any; }\n",
1537 " __attribute__((__transparent_union__)) arg;\n",
1538 "int takes(arg v);\n",
1539 "int f(struct one *p, struct two *q, char *c) {\n",
1540 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
1541 "}\n",
1542 "int takes(struct one *p);\n",
1544 "int (*as_a_member)(struct one *) = takes;\n",
1545 "int (*as_the_union)(arg) = takes;\n",
1546 ));
1547 assert!(text.contains("compound-literal"), "{text}");
1548 }
1549
1550 #[test]
1556 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
1557 let text = tast(concat!(
1558 "struct sockaddr { int family; };\n",
1559 "struct sockaddr_in { int family; int addr; };\n",
1560 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
1561 " addr_arg __attribute__((__transparent_union__));\n",
1562 "int bind_to(int fd, addr_arg where);\n",
1563 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
1564 ));
1565 assert!(text.contains("compound-literal"), "{text}");
1566 }
1567
1568 #[test]
1576 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
1577 let result = run(
1578 &options(),
1579 concat!(
1580 "union wider { int small; double large; } __attribute__((transparent_union));\n",
1581 "struct plain { int x; } __attribute__((transparent_union));\n",
1582 ),
1583 );
1584 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
1585 assert!(!result.failed(), "{:?}", result.messages);
1586 for message in &result.messages {
1587 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
1588 }
1589 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
1590 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
1591 }
1592
1593 #[test]
1602 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
1603 let packed = body(concat!(
1604 "struct P { char c; int v; } __attribute__((packed));\n",
1605 "int f(struct P *p) { return p->v; }\n",
1606 ));
1607 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
1608 let plain = body(concat!(
1610 "struct P { char c; int v; };\n",
1611 "int f(struct P *p) { return p->v; }\n",
1612 ));
1613 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
1614 }
1615
1616 #[test]
1623 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
1624 let stepped = body(concat!(
1625 "struct P { char c; int v[4]; } __attribute__((packed));\n",
1626 "int f(struct P *p, int i) { return p->v[i]; }\n",
1627 ));
1628 assert!(stepped.contains(", align 1,"), "{stepped}");
1629 assert!(!stepped.contains(", align 4,"), "{stepped}");
1630 let nested = body(concat!(
1631 "struct Inner { int v; };\n",
1632 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
1633 "int f(struct P *p) { return p->in.v; }\n",
1634 ));
1635 assert!(nested.contains(", align 1,"), "{nested}");
1636 assert!(!nested.contains(", align 4,"), "{nested}");
1637 }
1638
1639 #[test]
1655 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
1656 let through = body(concat!(
1657 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1658 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
1659 ));
1660 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
1661 let stepped = body(concat!(
1664 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1665 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
1666 ));
1667 assert!(stepped.contains(", align 1,"), "{stepped}");
1668 assert!(!stepped.contains(", align 4,"), "{stepped}");
1669 let plain = body(concat!(
1672 "typedef unsigned int word;\n",
1673 "unsigned int f(const void *p) { return *(const word *)p; }\n",
1674 ));
1675 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
1676 }
1677
1678 #[test]
1689 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
1690 let prefix = concat!(
1691 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
1692 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
1693 );
1694 let loaded =
1695 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
1696 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
1697 assert!(!loaded.contains("align 16"), "{loaded}");
1698 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
1701 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
1702 let aligned =
1704 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
1705 assert!(aligned.contains("align 16"), "{aligned}");
1706 }
1707
1708 #[test]
1717 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
1718 tast(concat!(
1719 "int v __attribute__((aligned(64)));\n",
1720 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
1721 "__attribute__((aligned(32))) int w;\n",
1724 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
1725 "[[gnu::aligned(16)]] int x;\n",
1726 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
1727 "int y __attribute__((aligned(2)));\n",
1730 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
1731 "void f(void) { int a __attribute__((aligned(128)));\n",
1733 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
1734 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1737 "void g(void) __attribute__((aligned(256)));\n",
1740 "void g(void) {}\n",
1741 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
1742 ));
1743 }
1744
1745 #[test]
1749 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
1750 let text = asm(concat!(
1751 "int v __attribute__((aligned(64)));\n",
1752 "void g(void) __attribute__((aligned(256)));\n",
1753 "void g(void) {}\n",
1754 "void plain(void) {}\n",
1755 ));
1756 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
1757 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1758 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
1759 }
1760
1761 #[test]
1767 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
1768 let source = concat!(
1769 "void g(void) __attribute__((aligned(256)));\n",
1770 "void g(void) {}\n",
1771 "void small(void) __attribute__((aligned(4)));\n",
1772 "void small(void) {}\n",
1773 "void plain(void) {}\n",
1774 );
1775 let listing = |align: Option<u32>| {
1776 let mut opts = options();
1777 opts.emit = EmitKind::Asm;
1778 opts.align_functions = align;
1779 let result = run(&opts, source);
1780 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
1781 result.text().to_owned()
1782 };
1783
1784 let text = listing(Some(32));
1785 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
1786 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
1787 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
1788
1789 let text = listing(Some(8));
1792 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
1793 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1794 }
1795
1796 #[test]
1805 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
1806 tast(concat!(
1807 "typedef int L __attribute__((aligned(2)));\n",
1808 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
1809 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
1810 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
1812 "struct T { char c; L x; };\n",
1813 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
1814 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
1815 "typedef int H __attribute__((aligned(16)));\n",
1817 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
1818 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
1819 "struct U { char c; H x; };\n",
1820 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
1821 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
1822 "typedef L M __attribute__((aligned(8)));\n",
1825 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
1826 "typedef L N;\n",
1829 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
1830 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1832 ));
1833 let text = asm(concat!(
1834 "typedef int L __attribute__((aligned(2)));\n",
1835 "typedef int H __attribute__((aligned(16)));\n",
1836 "L low;\n",
1837 "H high;\n",
1838 ));
1839 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
1840 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
1841 }
1842
1843 #[test]
1851 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
1852 tast(concat!(
1853 "typedef int __attribute__((vector_size(16))) v4si;\n",
1854 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
1855 "typedef char __attribute__((vector_size(16))) v16qi;\n",
1856 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
1857 "typedef int __attribute__((vector_size(4))) v1si;\n",
1860 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
1861 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
1863 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
1864 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
1865 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
1866 "v4si g;\n",
1869 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
1870 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
1871 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
1874 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
1876 ));
1877 }
1878
1879 #[test]
1889 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
1890 tast(concat!(
1891 "typedef int __attribute__((vector_size(8))) v2si;\n",
1892 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
1893 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
1894 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
1896 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
1897 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
1900 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
1901 ));
1902 }
1903
1904 #[test]
1912 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
1913 let result = run(
1914 &options(),
1915 concat!(
1916 "typedef int __attribute__((vector_size(16))) v4si;\n",
1917 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
1918 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
1919 " v4si v = { 1, 2, 3, 4 };\n",
1920 " v[0] = n;\n",
1921 " v[1] += n;\n",
1922 " v[2]++;\n",
1923 " *&v[3] = n;\n",
1924 " v4ui shifted = a >> b;\n",
1926 " shifted <<= b;\n",
1927 " *out = v + (v4si)shifted + (1 << b);\n",
1930 "}\n",
1931 "void refused(const v4si c) {\n",
1934 " c[0] = 1;\n",
1935 "}\n",
1936 ),
1937 );
1938 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
1939 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
1940 }
1941
1942 #[test]
1949 fn a_record_that_asks_for_the_other_byte_order_is_refused_rather_than_laid_out_in_this_one() {
1950 let opts = options();
1951 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
1952 assert_eq!(
1953 run(&opts, big).messages,
1954 ["/main.c:1:36: error: 'scalar_storage_order' is not implemented yet [E0688]\n\
1955 /main.c:1:36: note: every scalar in this record would be read in the wrong byte \
1956 order"]
1957 );
1958
1959 let armoured =
1960 "struct s { int i; } __attribute__((__scalar_storage_order__(\"little-endian\")));\n";
1961 let messages = run(&opts, armoured).messages;
1962 assert!(messages[0].contains("[E0688]"), "{messages:?}");
1963
1964 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
1967 assert!(run(&opts, front).messages[0].contains("[E0688]"), "{front}");
1968 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
1969 assert!(run(&opts, standard).messages[0].contains("[E0688]"), "{standard}");
1970 }
1971
1972 #[test]
1982 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
1983 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
1985 assert_eq!(
1986 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
1987 1
1988 );
1989 assert_eq!(
1990 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
1991 1
1992 );
1993 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
1994 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
1996 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
1997 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
1999 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2000 }
2001
2002 fn bit_field_byte(record: &str) -> u64 {
2004 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2005 let body = body(&source);
2006 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2007 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2008 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2009 }
2010
2011 #[test]
2017 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2018 tast(concat!(
2019 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2020 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2021 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2022 "struct b { char c; __attribute__((packed)) int i; };\n",
2023 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2024 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2025 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2026 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2027 ));
2028 }
2029
2030 #[test]
2036 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2037 tast(concat!(
2038 "#pragma pack(1)\n",
2039 "struct A { char c; int i; };\n",
2040 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2041 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2042 "#pragma pack()\n",
2043 "struct B { char c; int i; };\n",
2044 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2045 "#pragma pack(2)\n",
2046 "struct C { char c; int i; double d; };\n",
2047 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2048 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2049 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2051 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2052 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2053 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2055 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2056 "#pragma pack()\n",
2057 "#pragma pack(push, 1)\n",
2058 "struct D { char c; short s; };\n",
2059 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2060 "#pragma pack(pop)\n",
2061 "struct E { char c; short s; };\n",
2062 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2063 "struct H { char c;\n",
2065 "#pragma pack(1)\n",
2066 " int i; };\n",
2067 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2068 "#pragma pack(1)\n",
2069 "struct I { char c;\n",
2070 "#pragma pack()\n",
2071 " int i; };\n",
2072 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2073 "#pragma pack()\n",
2074 "#pragma pack(push, 8)\n",
2076 "#pragma pack(push, 1)\n",
2077 "struct P { char c; int i; };\n",
2078 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2079 "#pragma pack(pop)\n",
2080 "struct Q { char c; int i; };\n",
2081 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2082 "#pragma pack(pop)\n",
2083 "#pragma pack(16)\n",
2085 "struct R { char c; int i; };\n",
2086 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2087 "#pragma pack()\n",
2088 "#pragma pack(1)\n",
2089 "struct S { char c; int i : 5; int j : 20; };\n",
2090 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2091 "union T { char c; int i; };\n",
2092 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2093 "#pragma pack()\n",
2094 ));
2095 }
2096
2097 #[test]
2101 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2102 let result = run(
2103 &options(),
2104 concat!(
2105 "#pragma pack 4\n",
2106 "#pragma pack(pop)\n",
2107 "#pragma pack(3)\n",
2108 "#pragma pack(1) junk\n",
2109 "#pragma pack(push, 1\n",
2110 "#pragma pack(x)\n",
2111 "#pragma pack(0)\n",
2114 "#pragma pack(push)\n",
2115 "struct s { char c; int i; };\n",
2116 "#pragma pack(pop)\n",
2117 "#pragma pack(pop, foo)\n",
2118 ),
2119 );
2120 let expected = [
2121 "missing `(` after `#pragma pack` - ignored",
2122 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2123 "alignment must be a small power of two, not 3",
2124 "junk at end of `#pragma pack`",
2125 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2126 "unknown action `x` for `#pragma pack` - ignored",
2127 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2128 ];
2129 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2130 for (message, want) in result.messages.iter().zip(expected) {
2131 assert!(message.contains(want), "expected {want:?} in {message:?}");
2132 }
2133 }
2134
2135 #[test]
2142 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2143 let result = run(
2144 &options(),
2145 concat!(
2146 "#pragma pack(push, 1)\n",
2147 "#pragma pack(pop)\n",
2148 "#define API\n",
2149 "API const char version[] = \"3.53.4\";\n",
2150 "const char *get(void) { return version; }\n",
2151 ),
2152 );
2153 assert!(result.messages.is_empty(), "{:?}", result.messages);
2154 }
2155
2156 #[test]
2160 fn the_wide_integer_answers_to_all_three_of_its_names() {
2161 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2162 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2163 assert!(text.contains("decl #1 b : __int128"), "{text}");
2164 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2165 }
2166
2167 #[test]
2168 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2169 let text = tast("long f(int a, long b) { return a + b; }\n");
2173 assert!(text.contains("convert arithmetic"), "{text}");
2174 }
2175
2176 #[test]
2177 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2178 for source in [
2179 "#error stop\n",
2180 "int f(void) { return 1 + ; }\n",
2181 "int f(void) { return undeclared; }\n",
2182 ] {
2183 let result = run(&options(), source);
2184 assert!(result.failed(), "expected this to fail:\n{source}");
2185 assert!(
2186 result.text().is_empty(),
2187 "a file that did not compile wrote a tree:\n{source}"
2188 );
2189 }
2190 }
2191
2192 #[test]
2193 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2194 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2198 assert_eq!(result.errors, 1, "{:?}", result.messages);
2199 }
2200
2201 #[test]
2202 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2203 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2207 assert_eq!(result.errors, 1, "{:?}", result.messages);
2208 }
2209
2210 #[test]
2211 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2212 let source = "int f(void) { char c = 300; return c; }\n";
2213 let plain = run(&options(), source);
2214 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2215 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2216 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2217
2218 let mut opts = options();
2219 opts.warnings_are_errors = true;
2220 let strict = run(&opts, source);
2221 assert!(strict.failed());
2222 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2223 for message in &strict.messages {
2224 assert!(!message.contains("warning:"), "{message}");
2225 }
2226 }
2227
2228 #[test]
2229 fn w_drops_the_warning_before_werror_can_promote_it() {
2230 let source = "int f(void) { char c = 300; return c; }\n";
2231 let mut opts = options();
2232 opts.warnings = false;
2233 let quiet = run(&opts, source);
2234 assert_eq!(quiet.messages, Vec::<String>::new());
2235 assert_eq!(quiet.errors, 0);
2236 assert!(!quiet.text().is_empty(), "and the file still compiles");
2237
2238 opts.warnings_are_errors = true;
2241 let both = run(&opts, source);
2242 assert_eq!(both.messages, Vec::<String>::new());
2243 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2244 }
2245
2246 #[test]
2247 fn the_dialect_reaches_the_keywords_and_the_checking() {
2248 let source = "typeof(1) x;\n";
2251 let mut opts = options();
2252 opts.std = Std::C23;
2253 opts.gnu_extensions = false;
2254 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2255
2256 opts.std = Std::C17;
2257 assert!(run(&opts, source).failed());
2258 }
2259
2260 #[test]
2261 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2262 let mut opts = options();
2263 opts.emit = EmitKind::Object;
2264 let result = run(&opts, "int x = 1;\n");
2265 assert!(!result.failed(), "{:?}", result.messages);
2266 assert!(result.text().is_empty());
2267 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2270 }
2271
2272 fn mir(source: &str) -> String {
2274 let mut opts = options();
2275 opts.emit = EmitKind::MirFinal;
2276 let result = run(&opts, source);
2277 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2278 result.text().to_owned()
2279 }
2280
2281 #[test]
2287 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2288 let text = mir("int add(int a, int b) { return a + b; }\n");
2289 assert!(text.starts_with("mfunc @add {"), "{text}");
2290 assert!(text.contains("x64.add_rr_32"), "{text}");
2291 assert!(text.contains("x64.ret"), "{text}");
2292 assert!(!text.contains('%'), "{text}");
2295 }
2296
2297 #[test]
2299 fn a_function_with_no_body_produces_no_machine_function() {
2300 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2301 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2302 assert!(text.contains("mfunc @f {"), "{text}");
2303 assert!(text.contains("x64.call"), "{text}");
2304 }
2305
2306 #[test]
2308 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2309 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2310 let first = text.find("mfunc @a").expect("the first function");
2311 let second = text.find("mfunc @b").expect("the second function");
2312 assert!(first < second, "{text}");
2313 }
2314
2315 #[test]
2317 fn the_target_decides_which_convention_the_generated_code_follows() {
2318 let mut opts = options();
2319 opts.emit = EmitKind::MirFinal;
2320 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2321 assert!(linux.contains("$rdi"), "{linux}");
2322
2323 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2324 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2325 assert!(windows.contains("$rcx"), "{windows}");
2326 assert!(!windows.contains("$rdi"), "{windows}");
2327 }
2328
2329 #[test]
2337 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2338 let source = concat!(
2339 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2340 "int size(void) { return sizeof(struct S); }\n",
2341 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2342 );
2343
2344 let mut opts = options();
2345 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2346 let windows = run(&opts, source);
2347 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2348
2349 let linux = run(&options(), source);
2350 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2351 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2352
2353 let mut opts = options();
2356 opts.ms_extensions = Some(true);
2357 let asked = run(&opts, source);
2358 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2359 }
2360
2361 #[test]
2363 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2364 let mut opts = options();
2365 opts.emit = EmitKind::MirFinal;
2366 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2367 let result = run(&opts, "int f(int a) { return a; }\n");
2368 assert!(result.failed());
2369 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2370 assert!(result.text().is_empty());
2371 }
2372
2373 #[test]
2385 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2386 let mut opts = options();
2387 opts.emit = EmitKind::MirFinal;
2388 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
2389 s; s.x = 1; v[0] = s.x; }\n\
2390 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
2391 s; s.x = 1; v[0] = s.x; }\n";
2392 let result = run(&opts, source);
2393 assert!(result.failed());
2394 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2395 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2396 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2397 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2398 assert!(result.text().is_empty());
2399 }
2400
2401 #[test]
2409 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2410 let mut opts = options();
2411 opts.emit = EmitKind::MirFinal;
2412 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2413 let plain = run(&opts, source);
2414 assert!(!plain.failed(), "{:?}", plain.messages);
2415 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2416
2417 opts.stack_clash = true;
2418 let result = run(&opts, source);
2419 assert!(!result.failed(), "{:?}", result.messages);
2420 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2421 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2422 }
2423
2424 #[test]
2434 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
2435 let mut opts = options();
2436 opts.emit = EmitKind::Object;
2437 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2438 let source = concat!(
2439 "void use(void *p);\n",
2440 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
2441 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
2442 );
2443 let result = run(&opts, source);
2444 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2445 let bytes = match result.artifact {
2446 Artifact::Object { bytes, .. } => bytes,
2447 other => panic!("expected an object, got {other:?}"),
2448 };
2449 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2450
2451 let mut opts = options();
2454 opts.emit = EmitKind::Object;
2455 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2456 }
2457
2458 #[test]
2469 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
2470 let source = concat!(
2471 "void other(void *p);\n",
2472 "void takes(void (*f)(void *));\n",
2473 "void (*held)(void *);\n",
2474 "void pass(void) { takes(other); }\n",
2475 "void keep(void) { held = other; }\n",
2476 "void call(void) { other(0); }\n",
2477 );
2478 let mut opts = options();
2479 opts.emit = EmitKind::Object;
2480 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2481 let result = run(&opts, source);
2482 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2483 let bytes = match result.artifact {
2484 Artifact::Object { bytes, .. } => bytes,
2485 other => panic!("expected an object, got {other:?}"),
2486 };
2487 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2488
2489 let mut opts = options();
2492 opts.emit = EmitKind::Object;
2493 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2494 }
2495
2496 #[test]
2510 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
2511 let mut opts = options();
2512 opts.emit = EmitKind::MirFinal;
2513 let source =
2514 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
2515 let result = run(&opts, source);
2516 assert!(result.failed());
2517 assert!(
2518 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
2519 "{result:?}"
2520 );
2521 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
2522 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
2523 }
2524
2525 #[test]
2527 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
2528 let mut opts = options();
2529 opts.emit = EmitKind::MirFinal;
2530 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
2531 let result = run(&opts, source);
2532 assert!(result.failed());
2533 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
2534 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
2535 assert!(!note.contains("spec/17-milestones.md"), "{note}");
2536 }
2537
2538 #[test]
2540 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
2541 let source = "int f(int a) { return a; }\n";
2542 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
2543
2544 let mut opts = options();
2545 opts.emit = EmitKind::MirFinal;
2546 opts.frame_pointer = true;
2547 let kept = run(&opts, source).text().to_owned();
2548 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
2549 }
2550
2551 fn asm(source: &str) -> String {
2553 let mut opts = options();
2554 opts.emit = EmitKind::Asm;
2555 let result = run(&opts, source);
2556 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2557 result.text().to_owned()
2558 }
2559
2560 #[test]
2567 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
2568 let text = asm("int add(int a, int b) { return a + b; }\n");
2569 assert!(text.contains("\t.globl\tadd\n"), "{text}");
2570 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
2571 assert!(text.contains("\nadd:\n"), "{text}");
2572 assert!(text.contains("\taddl\t"), "{text}");
2573 assert!(text.contains("\tret\n"), "{text}");
2574 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
2575 assert!(text.contains(".note.GNU-stack"), "{text}");
2578 }
2579
2580 #[test]
2586 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
2587 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
2588 assert!(text.contains("\tcall\t*%"), "{text}");
2589 assert!(text.contains("\tcall\tg\n"), "{text}");
2590 assert!(text.contains("%rdi"), "{text}");
2594 }
2595
2596 #[test]
2600 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
2601 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
2602 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
2603 }
2604
2605 #[test]
2614 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
2615 let arms = "return 1; return 2;";
2616 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
2617 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
2618 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
2619 assert!(
2620 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2621 "{operator}: {text}"
2622 );
2623 assert!(!text.contains("\tset"), "{operator}: {text}");
2624 assert!(!text.contains("\ttest"), "{operator}: {text}");
2625 }
2626 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
2627 for (operator, jump) in unsigned {
2628 let source =
2629 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
2630 let text = asm(&source);
2631 assert!(
2632 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2633 "{operator}: {text}"
2634 );
2635 }
2636
2637 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
2640 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
2641 }
2642
2643 #[test]
2649 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
2650 let text = asm("int f(int a, int b) { return a < b; }\n");
2651 assert!(text.contains("\tsetl\t"), "{text}");
2652 }
2653
2654 fn optimized(source: &str) -> String {
2656 let mut opts = options();
2657 opts.emit = EmitKind::Asm;
2658 opts.opt_level = rucc_session::OptLevel::O2;
2659 let result = run(&opts, source);
2660 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2661 result.text().to_owned()
2662 }
2663
2664 #[test]
2674 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
2675 let arms: String =
2676 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
2677 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2678 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
2679 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
2680 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
2681 }
2682
2683 #[test]
2690 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
2691 let arms: String = (0..16)
2692 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
2693 .collect::<Vec<_>>()
2694 .join(" ");
2695 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2696 assert!(text.matches("\tcmp").count() > 1, "{text}");
2697 }
2698
2699 #[test]
2707 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
2708 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
2709 assert!(text.contains("movl\t$2, %eax"), "{text}");
2710 assert!(!text.contains("cvttsd2si"), "{text}");
2711 }
2712
2713 #[test]
2721 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
2722 let text =
2723 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
2724 assert!(text.contains("movl\t$30, %eax"), "{text}");
2725 assert!(!text.contains("t(%rip)"), "{text}");
2726 }
2727
2728 #[test]
2731 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
2732 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
2733 assert!(text.contains("movl\t$98, %eax"), "{text}");
2734 }
2735
2736 #[test]
2740 fn a_table_that_is_not_read_only_keeps_its_load() {
2741 let text = optimized(
2742 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
2743 );
2744 assert!(!text.contains("movl\t$30, %eax"), "{text}");
2745 }
2746
2747 #[test]
2754 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
2755 let text = optimized(
2756 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
2757 );
2758 assert!(!text.contains("call\tlink_error"), "{text}");
2759 }
2760
2761 #[test]
2763 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2764 let text = asm("long f(void *p) { return (long)p; }\n");
2765 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2770 let mnemonic = line.split_whitespace().next().unwrap_or("");
2771 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2772 }
2773 }
2774
2775 #[test]
2779 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2780 let six = "long a, long b, long c, long d, long e, long f";
2781 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2782
2783 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2790 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2791
2792 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2796 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2797 let eight =
2798 "double a, double b, double c, double d, double e, double f, double g, double h";
2799 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2800 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2801 }
2802
2803 #[test]
2806 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2807 let six = "1, 2, 3, 4, 5, 6";
2808 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2809 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2810
2811 assert!(text.contains("\tmovq\t%"), "{text}");
2812 assert!(text.contains(", (%rsp)\n"), "{text}");
2813 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2814 assert!(text.contains("\tsubq\t$"), "{text}");
2816
2817 let narrow = "int g(int, int, int, int, int, int, int);\n";
2819 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2820 assert!(text.contains("\tmovl\t%"), "{text}");
2821 assert!(text.contains(", (%rsp)\n"), "{text}");
2822 }
2823
2824 #[test]
2827 fn a_variadic_call_counts_registers_and_not_arguments() {
2828 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2829 let decl = "int g(int, ...);\n";
2830 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2831
2832 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2833 assert!(text.contains("\tmovsd\t%"), "{text}");
2834 assert!(text.contains(", (%rsp)\n"), "{text}");
2835 }
2836
2837 #[test]
2842 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2843 let body =
2844 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2845 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2846
2847 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2850 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2851 assert!(!text.contains(", 0(%r"), "{text}");
2852 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2855 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2856
2857 assert!(text.contains("\tsubq\t$"), "{text}");
2859 }
2860
2861 #[test]
2864 fn va_start_writes_the_four_fields_the_psabi_describes() {
2865 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2866 let params = "int a, int b, int c, double d";
2867 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2868
2869 assert!(text.contains(" movl $24, "), "{text}");
2873 assert!(text.contains(" movl $64, "), "{text}");
2874 assert!(text.contains(", 8(%r"), "{text}");
2878 assert!(text.contains(", 16(%r"), "{text}");
2879 let frame: u32 = text
2880 .lines()
2881 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
2882 .expect("a variadic function takes a frame for the save area");
2883 let above = |line: &str| {
2884 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
2885 Some(at > frame)
2886 };
2887 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
2888 }
2889
2890 #[test]
2893 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
2894 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
2895 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
2896 let text = asm(&ints);
2897
2898 assert!(text.contains("$40, "), "{text}");
2901 assert!(text.contains(" cmpl "), "{text}");
2902 assert!(text.contains(" ja "), "{text}");
2906
2907 let arg = "__builtin_va_arg(ap, double)";
2908 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
2909 assert!(text.contains("$160, "), "the last vector slot: {text}");
2910 }
2911
2912 #[test]
2915 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
2916 let decl = "struct pair { long a, b; };\n";
2917 let body = "struct pair p = *q; return p.a + p.b;";
2918 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
2919
2920 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
2921 assert!(!text.contains("\tcall"), "{text}");
2922 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
2924 }
2925
2926 #[test]
2929 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
2930 let decl = "struct bytes { char a[8]; };\n";
2931 let body = "struct bytes p = *q; return p.a[0];";
2932 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
2933
2934 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
2936 }
2937
2938 #[test]
2941 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
2942 let decl = "struct wide { long a, b, c; };\n";
2943 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
2944
2945 assert!(!text.contains("memset"), "nothing calls the library: {text}");
2946 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
2951 }
2952
2953 #[test]
2956 fn a_copy_too_large_to_unroll_calls_the_runtime() {
2957 let decl = "struct huge { char a[4096]; };\n";
2958 let mut opts = options();
2959 opts.emit = EmitKind::Asm;
2960 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
2961 let result = run(&opts, &source);
2962 assert!(!result.failed(), "{:?}", result.messages);
2963 let text = result.text();
2964 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
2965 assert!(text.contains("4096"), "the size travels: {text}");
2968 }
2969
2970 #[test]
2977 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
2978 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
2979 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
2980
2981 let copy = text.find("call\tmemcpy").expect("the copy");
2982 let call = text.find("call\ttake").expect("the call");
2983 assert!(copy < call, "the copy comes first: {text}");
2984 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
2989 assert!(text.contains("$4096, %edx"), "the size: {text}");
2990 }
2991
2992 #[test]
2995 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
2996 let six = "long a, long b, long c, long d, long e, long f";
2997 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
2998 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
2999
3000 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3004 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3005 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3006 }
3007
3008 #[test]
3010 fn the_target_decides_how_the_assembly_is_spelled() {
3011 let mut opts = options();
3012 opts.emit = EmitKind::Asm;
3013 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3014 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3015 assert!(text.contains("__TEXT,__text"), "{text}");
3016 assert!(text.contains("\n_f:\n"), "{text}");
3017 assert!(!text.contains(".note.GNU-stack"), "{text}");
3018 }
3019
3020 fn obj(source: &str) -> Vec<u8> {
3022 let mut opts = options();
3023 opts.emit = EmitKind::Object;
3024 let result = run(&opts, source);
3025 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3026 match result.artifact {
3027 Artifact::Object { bytes, .. } => bytes,
3028 other => panic!("expected an object, got {other:?}"),
3029 }
3030 }
3031
3032 #[test]
3038 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3039 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3040 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3041 let text = asm("int add(int a, int b) { return a + b; }\n");
3042 assert!(
3043 text.contains("\taddl\t"),
3044 "and the listing of it is the same instructions:\n{text}"
3045 );
3046 }
3047
3048 #[test]
3050 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3051 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3052 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3053 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3054 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3055 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3058 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3059 assert!(!text.contains(".globl\thidden"), "{text}");
3060 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3063 }
3064
3065 #[test]
3072 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3073 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3074 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3075 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3076
3077 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3080 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3081
3082 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3085 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3086
3087 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3089 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3090 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3091 }
3092
3093 #[test]
3095 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3096 let text = asm("const char *f(void) { return \"hi\"; }\n");
3097 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3098 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3099 let label = text
3100 .lines()
3101 .find(|line| line.starts_with(".Lstr"))
3102 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3103 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3104 }
3105
3106 #[test]
3108 fn an_address_in_an_initializer_is_left_to_the_linker() {
3109 let source = "int counter;\nint *p = &counter;\n";
3110 let text = asm(source);
3111 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3112 let bytes = obj(source);
3115 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3116 }
3117
3118 #[test]
3127 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3128 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3131 struct m { void (*x)(void); void (*y)(void); };\n\
3132 const struct m t = { a, b };\n");
3133 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3134 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3135
3136 let text =
3139 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3140 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3141
3142 let text = asm("const int fixed = 7;\n");
3144 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3145 }
3146
3147 #[test]
3154 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3155 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3156 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3159 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3160 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3163 assert!(text.contains("%fs:0"), "{text}");
3164 }
3165
3166 #[test]
3172 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3173 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3174 assert!(text.contains("movq\t%fs:0, "), "{text}");
3175 assert!(!text.contains("GOTTPOFF"), "{text}");
3177 }
3178
3179 #[test]
3190 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3191 for (locality, wanted) in
3192 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3193 {
3194 let source =
3195 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3196 let text = asm(&source);
3197 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3198 }
3199 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3201 assert!(text.contains("\tprefetcht0\t"), "{text}");
3202 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3205 assert!(text.contains("\tprefetcht0\t"), "{text}");
3206 assert!(!text.contains("prefetchw"), "{text}");
3207 }
3208
3209 #[test]
3220 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3221 let text = asm("void stop(void) { __builtin_trap(); }\n");
3222 assert!(text.contains("\tud2\n"), "{text}");
3223 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3224
3225 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3226 assert!(text.contains("\tud2\n"), "{text}");
3227 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3228 }
3229
3230 #[test]
3242 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3243 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3244 assert!(!text.contains("assume_aligned"), "{text}");
3245 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3246
3247 let source = "unsigned long width(void);\n\
3248 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3249 let text = asm(source);
3250 assert!(!text.contains("assume_aligned"), "{text}");
3251 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3252 }
3253
3254 #[test]
3264 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3265 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3266 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3267 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3268 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3269
3270 let walk = |depth: u32| {
3271 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3272 asm(&source).matches("movq\t(%r").count()
3273 };
3274 assert_eq!(walk(1), 1, "one link is one load");
3275 assert_eq!(walk(3), 3, "three links are three loads");
3276 }
3277
3278 #[test]
3288 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3289 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3290 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3291 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3292 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3293
3294 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3295 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3296 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3297 }
3298
3299 #[test]
3310 fn a_depth_that_is_not_a_small_constant_is_refused() {
3311 let mut opts = options();
3312 opts.emit = EmitKind::Ir;
3313 for source in [
3314 "void *up(int n) { return __builtin_return_address(n); }\n",
3315 "void *up(void) { return __builtin_frame_address(1000); }\n",
3316 ] {
3317 let messages = run(&opts, source).messages;
3318 let named = messages.iter().any(|m| m.contains("E0705"));
3319 assert!(named, "expected a refusal in {messages:?}");
3320 }
3321 }
3322
3323 #[test]
3335 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3336 let text =
3337 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3338 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
3339 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
3340 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3341
3342 let plain = concat!(
3345 "extern void *alloca(__SIZE_TYPE__);\n",
3346 "void use(void *p);\n",
3347 "void f(unsigned long n) { use(alloca(n)); }\n",
3348 );
3349 let text = asm(plain);
3350 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
3351 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
3352
3353 let own = concat!(
3356 "static void *alloca(unsigned long n) { return 0; }\n",
3357 "void *f(unsigned long n) { return alloca(n); }\n",
3358 );
3359 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3360 }
3361
3362 #[test]
3372 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3373 let inner = "{ use(__builtin_alloca(n)); }";
3374 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3375 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3376 let text = asm(&source);
3377 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3381 let taking = line.contains("subq");
3382 let leaving = line.contains("%rbp");
3383 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3384 }
3385 }
3386 }
3387
3388 #[test]
3390 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3391 let source = "int callee(void); int g(void) { return callee(); }\n";
3395 let bytes = obj(source);
3396 assert!(
3397 bytes.windows(7).any(|w| w == b"callee\0"),
3398 "the object has to name the callee for the linker to find it"
3399 );
3400 let text = asm(source);
3401 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3402 }
3403
3404 #[test]
3410 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3411 let mut opts = options();
3412 opts.emit = EmitKind::Executable;
3414 let result = run(&opts, "int main(void) { return 0; }\n");
3415 assert_eq!(result.messages, Vec::<String>::new());
3416 match result.artifact {
3417 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3418 other => panic!("expected an object, got {other:?}"),
3419 }
3420 }
3421
3422 #[test]
3424 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3425 let mut opts = options();
3426 opts.emit = EmitKind::Object;
3427 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3428 let result = run(&opts, "int f(void) { return 0; }\n");
3429 assert!(result.failed(), "an object nobody can read is worse than a message");
3430 assert!(
3431 result.messages.iter().any(|m| m.contains("no object writer")),
3432 "{:?}",
3433 result.messages
3434 );
3435 }
3436
3437 fn ir(source: &str) -> String {
3439 let mut opts = options();
3440 opts.emit = EmitKind::Ir;
3441 let result = run(&opts, source);
3442 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3443 result.text().to_owned()
3444 }
3445
3446 fn errors(source: &str) -> Vec<String> {
3448 let mut opts = options();
3449 opts.emit = EmitKind::Ir;
3450 let result = run(&opts, source);
3451 assert!(result.failed(), "expected this to be refused:\n{source}");
3452 result.messages
3453 }
3454
3455 fn body(source: &str) -> String {
3457 let text = ir(source);
3458 let (_, rest) = text.split_once("{\n").expect("a function definition");
3459 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3460 body.to_owned()
3461 }
3462
3463 #[test]
3471 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3472 let source = "inline int f(int x) { return x + 1; }\n";
3473 let with = |flag: bool| {
3474 let mut opts = options();
3475 opts.emit = EmitKind::Ir;
3476 opts.gnu89_inline = flag;
3477 let result = run(&opts, source);
3478 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3479 result.text().to_owned()
3480 };
3481
3482 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3485
3486 assert!(with(true).contains("block0"), "a body: {}", with(true));
3489 }
3490
3491 #[test]
3498 fn an_access_through_a_type_names_the_type_it_went_through() {
3499 let source = "\
3500struct s { int a; float b; };\n\
3501union u { int i; float f; };\n\
3502int scalar(int *p) { return *p; }\n\
3503float member(struct s *p) { p->a = 1; return p->b; }\n\
3504int element(int *a, long i) { return a[i]; }\n\
3505float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3506 let text = ir(source);
3507 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3508 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3509 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3510 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3513 assert_eq!(named, 6, "six accesses: {text}");
3514 }
3515
3516 #[test]
3523 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3524 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3525 let mut opts = options();
3526 opts.emit = EmitKind::Ir;
3527 opts.strict_aliasing = false;
3528 let result = run(&opts, source);
3529 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3530 let text = result.text().to_owned();
3531 assert!(!text.contains("tbaa"), "not even the root: {text}");
3532 }
3533
3534 #[test]
3542 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3543 let mut opts = options();
3544 opts.emit = EmitKind::Ir;
3545 opts.std = Std::C89;
3546 let compiled = |source: &str| {
3547 let result = run(&opts, source);
3548 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3549 result.text().to_owned()
3550 };
3551
3552 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3553 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3554 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3555
3556 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3558 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3559 }
3560
3561 #[test]
3569 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3570 let mut opts = options();
3571 opts.emit = EmitKind::Ir;
3572 opts.std = Std::C89;
3573 let compiled = |source: &str| {
3574 let result = run(&opts, source);
3575 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3576 result.text().to_owned()
3577 };
3578
3579 let text = compiled("int f(void) { return g(); }\n");
3581 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3582 assert!(text.contains("i32"), "and it gives back an int: {text}");
3583
3584 let text = compiled("int f(char c) { return g(c); }\n");
3587 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3588
3589 let mut opts = options();
3592 opts.std = Std::C89;
3593 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3594 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3595 }
3596
3597 #[test]
3607 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3608 let mut opts = options();
3609 opts.emit = EmitKind::Ir;
3610 opts.std = Std::C89;
3611 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3612 .text()
3613 .to_owned();
3614 assert!(text.contains("func @f()"), "the caller is there: {text}");
3615 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3616 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3617 }
3618
3619 #[test]
3627 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3628 let mut opts = options();
3629 opts.emit = EmitKind::Ir;
3630 opts.std = Std::C89;
3631 let compiled = |source: &str| run(&opts, source).text().to_owned();
3632
3633 let text = compiled("f (c) unsigned char c; { return c; }\n");
3634 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3635 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3636 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3637
3638 let text = compiled("f (s) short s; { return s; }\n");
3640 assert!(text.contains("trunc.i16"), "cut down: {text}");
3641 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3642
3643 let text = compiled("f (x) float x; { return x * 2; }\n");
3646 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3647 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3648
3649 let text = compiled("int f(unsigned char c) { return c; }\n");
3652 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3653 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3654 }
3655
3656 #[test]
3665 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3666 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3668 let cases = [
3669 ("static counted;\n", ["", "error", "warning", "error"]),
3670 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3671 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3672 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3673 (
3674 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3675 ["warning", "error", "warning", "error"],
3676 ),
3677 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3678 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3679 ];
3680
3681 for (source, wanted) in cases {
3682 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3683 let mut opts = options();
3684 opts.std = std;
3685 opts.permissive = permissive;
3686 let said = run(&opts, source).messages.join("\n");
3687 let severity = if said.contains(": error: ") {
3688 "error"
3689 } else if said.contains(": warning: ") {
3690 "warning"
3691 } else {
3692 ""
3693 };
3694 let how = if permissive { " -fpermissive" } else { "" };
3695 assert_eq!(
3696 severity,
3697 wanted,
3698 "under -std={}{how}, {source} was answered with `{said}`",
3699 std.as_str()
3700 );
3701 if wanted.is_empty() {
3702 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3703 }
3704 }
3705 }
3706 }
3707
3708 #[test]
3717 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3718 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3719 let cases = [
3720 (
3721 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3722 "first argument to 'va_arg' not of type 'va_list'",
3723 ["error", "error", "error", "error"],
3724 ),
3725 (
3726 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3727 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3728 ["warning", "error", "warning", "error"],
3729 ),
3730 (
3731 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3732 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3733 cast",
3734 ["warning", "error", "warning", "error"],
3735 ),
3736 (
3737 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3738 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3739 ["warning", "error", "warning", "error"],
3740 ),
3741 ];
3742
3743 for (source, message, wanted) in cases {
3744 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3745 let mut opts = options();
3746 opts.std = std;
3747 opts.permissive = permissive;
3748 let said = run(&opts, source).messages.join("\n");
3749 let how = if permissive { " -fpermissive" } else { "" };
3750 assert!(
3751 said.contains(&format!(": {wanted}: {message}")),
3752 "under -std={}{how}, {source} was answered with `{said}`",
3753 std.as_str()
3754 );
3755 }
3756 }
3757 }
3758
3759 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3761 let mut opts = options();
3762 opts.emit = EmitKind::Ir;
3763 opts.safety = tier;
3764 let result = run(&opts, source);
3765 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3766 result.text().to_owned()
3767 }
3768
3769 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3770
3771 fn padded_ir(padding: Padding, source: &str) -> String {
3773 let mut opts = options();
3774 opts.emit = EmitKind::Ir;
3775 opts.safety = rucc_session::Safety::Detect;
3776 opts.padding = padding;
3777 let result = run(&opts, source);
3778 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3779 result.text().to_owned()
3780 }
3781
3782 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3783 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3784
3785 #[test]
3786 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3787 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3791 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3792 }
3793
3794 #[test]
3795 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3796 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3799 assert!(!text.contains("owns"), "{text}");
3800 }
3801
3802 #[test]
3803 fn a_member_of_a_union_owns_nothing_after_it() {
3804 let text = padded_ir(
3808 Padding::Ignored,
3809 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3810 );
3811 assert!(!text.contains("owns"), "{text}");
3812 }
3813
3814 #[test]
3815 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3816 let text = padded_ir(
3821 Padding::Ignored,
3822 "struct inner { char c; };\n\
3823 struct outer { struct inner in; int x; };\n\
3824 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3825 );
3826 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3827 }
3828
3829 #[test]
3830 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3831 let text = ir(READS_THROUGH_A_POINTER);
3835 assert!(!text.contains("check_"), "{text}");
3836 assert!(!text.contains("cap_of"), "{text}");
3837 }
3838
3839 #[test]
3840 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3841 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3842 assert!(text.contains("cap_of"), "{text}");
3843 assert!(text.contains("check_bounds"), "{text}");
3844 assert!(text.contains("check_live"), "{text}");
3845 assert!(text.contains("check_deriv"), "{text}");
3847 assert!(text.contains("check_type"), "{text}");
3849 }
3850
3851 #[test]
3852 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3853 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3857 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3858 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3859 }
3860 }
3861
3862 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3864 let mut opts = options();
3865 opts.emit = EmitKind::SafetySummary;
3866 opts.safety = tier;
3867 let result = run(&opts, source);
3868 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3869 result.text().to_owned()
3870 }
3871
3872 #[test]
3873 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3874 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3875 assert!(text.contains("\"tier\": \"detect\""), "{text}");
3876 assert!(
3878 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
3879 "{text}"
3880 );
3881 assert!(
3882 text.contains(
3883 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
3884 ),
3885 "{text}"
3886 );
3887 }
3888
3889 #[test]
3890 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
3891 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
3895 assert!(text.contains("\"tier\": \"off\""), "{text}");
3896 assert!(
3897 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
3898 "{text}"
3899 );
3900 }
3901
3902 #[test]
3903 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
3904 let text = summary(
3905 rucc_session::Safety::Detect,
3906 "void *memcpy(void *, const void *, unsigned long);\n\
3907 int puts(const char *);\n\
3908 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
3909 );
3910 assert!(text.contains("\"interposed\": 1"), "{text}");
3911 assert!(text.contains("\"puts\""), "{text}");
3912 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
3916 }
3917
3918 #[test]
3919 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
3920 let text = summary(
3925 rucc_session::Safety::Detect,
3926 "void *memcpy(void *, const void *, unsigned long);\n\
3927 int puts(const char *);\n\
3928 void *table[2] = { (void *)memcpy, (void *)puts };\n\
3929 void *f(int i) { return table[i]; }\n",
3930 );
3931 assert!(text.contains("\"interposed\": 1"), "{text}");
3932 assert!(text.contains("\"puts\""), "{text}");
3933 assert!(!text.contains("\"memcpy\""), "{text}");
3934 }
3935
3936 #[test]
3937 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3938 let text = summary(
3942 rucc_session::Safety::Detect,
3943 "void *notes_open(void);\n\
3944 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3945 );
3946 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3947 assert!(text.contains("\"notes_open\""), "{text}");
3948 }
3949
3950 #[test]
3951 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3952 let text = summary(
3955 rucc_session::Safety::Detect,
3956 "static int len(const char *p) { return p ? 1 : 0; }\n\
3957 int f(void) { return len(\"x\"); }\n",
3958 );
3959 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3960 }
3961
3962 fn granules(source: &str) -> String {
3964 let mut opts = options();
3965 opts.emit = EmitKind::TypeGranules;
3966 let result = run(&opts, source);
3967 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3968 result.text().to_owned()
3969 }
3970
3971 #[test]
3972 fn the_granule_report_names_every_record_and_both_keyings() {
3973 let text = granules(
3974 "struct hot { char *p; int a; int b; };\n\
3975 int f(struct hot *h) { return h->a; }\n",
3976 );
3977 assert!(text.contains("struct hot"), "{text}");
3978 assert!(text.contains("every type distinct"), "{text}");
3981 assert!(text.contains("every pointer one type"), "{text}");
3982 assert!(text.contains("budget"), "{text}");
3983 }
3984
3985 #[test]
3986 fn a_record_nothing_uses_is_still_measured() {
3987 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3990 assert!(text.contains("struct unused"), "{text}");
3991 }
3992
3993 #[test]
3994 fn the_granule_report_stops_before_anything_is_lowered() {
3995 let text = granules(
3999 "struct wide { long double d; };\n\
4000 long double f(long double x) { return x * x; }\n",
4001 );
4002 assert!(text.contains("struct wide"), "{text}");
4003 }
4004
4005 #[test]
4006 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
4007 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
4010 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
4011 }
4012
4013 #[test]
4014 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
4015 let text = summary(
4016 rucc_session::Safety::Detect,
4017 "unsigned long f(int *p) { return (unsigned long) p; }\n",
4018 );
4019 assert!(text.contains("\"exposed\": 1"), "{text}");
4020 }
4021
4022 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
4024 let mut opts = options();
4025 opts.emit = EmitKind::Asm;
4026 opts.safety = tier;
4027 let result = run(&opts, source);
4028 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4029 result.text().to_owned()
4030 }
4031
4032 #[test]
4033 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
4034 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4035 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
4036 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
4037 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
4038 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
4039 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
4040 }
4041
4042 #[test]
4043 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
4044 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4048 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4049 assert_eq!(text.matches(§ion).count(), 5, "{text}");
4050 for index in 0..5 {
4051 let name = format!("__rucc_safety_desc_{index}");
4052 assert!(text.contains(&format!("{name}:\n")), "{text}");
4055 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4056 }
4057 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
4058 }
4059
4060 #[test]
4068 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4069 let text = ir(concat!(
4070 "int g;\n",
4071 "int a = __builtin_constant_p(1);\n",
4072 "int b = __builtin_constant_p(g);\n",
4073 "int c = __builtin_constant_p(\"abc\");\n",
4074 "int d = __builtin_constant_p(&g);\n",
4075 "int e = __builtin_constant_p(1.5);\n",
4076 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4077 ));
4078 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4079 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4080 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4081 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4082 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4083 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4084 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4085
4086 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4090 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4091 }
4092
4093 #[test]
4102 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4103 let text = body("void f(void) { __builtin_abort(); }\n");
4104 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4105
4106 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4109 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4110 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4111 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4112 }
4113
4114 #[test]
4127 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4128 let text = ir(concat!(
4129 "char d[8];\n",
4130 "void f(const char *s, unsigned long n) {\n",
4131 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4132 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4133 " __builtin___memset_chk(d, 0, n, 8);\n",
4134 "}\n",
4135 ));
4136 assert!(text.contains("call @__memcpy_chk("), "{text}");
4137 assert!(text.contains("call @__strcpy_chk("), "{text}");
4138 assert!(text.contains("call @__memset_chk("), "{text}");
4139 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4140 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4141 }
4142
4143 #[test]
4151 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4152 let text = ir(concat!(
4153 "extern char *p;\n",
4154 "char d[8];\n",
4155 "void f(const char *s, unsigned long n) {\n",
4156 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4157 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4158 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4159 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4160 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4161 "}\n",
4162 ));
4163
4164 assert!(
4166 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4167 "{text}"
4168 );
4169
4170 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4173 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4174 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4175
4176 assert!(text.contains("call @__sprintf_chk("), "{text}");
4179
4180 let asm = asm(concat!(
4183 "void f(char *p, const char *s, unsigned long n) {\n",
4184 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4185 "}\n",
4186 ));
4187 assert!(asm.contains("call\tmemcpy"), "{asm}");
4188 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4189 }
4190
4191 #[test]
4199 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4200 let text = ir(concat!(
4201 "char d[64];\n",
4202 "int f(const char *fmt, ...) {\n",
4203 " __builtin_va_list ap;\n",
4204 " __builtin_va_start(ap, fmt);\n",
4205 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4206 " __builtin_va_end(ap);\n",
4207 " return n;\n",
4208 "}\n",
4209 ));
4210 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4211 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4212 }
4213
4214 #[test]
4225 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4226 let text = body(concat!(
4227 "long long llabs(long long);\n",
4228 "long long f(long long x) { return llabs(x); }\n",
4229 ));
4230 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4231 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4232 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4233 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4234 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4235
4236 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4239 assert!(text.contains("iconst.i32 31"), "{text}");
4240 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4241 assert!(text.contains("iconst.i64 63"), "{text}");
4242
4243 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4246 assert!(!text.contains("call"), "{text}");
4247
4248 let text = ir(concat!(
4250 "long long llabs(long long b);\n",
4251 "long long g(long long x) { return llabs(x); }\n",
4252 "long long llabs(long long b) { return 7; }\n",
4253 ));
4254 assert!(!text.contains("call @llabs"), "{text}");
4255 }
4256
4257 #[test]
4264 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4265 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4266 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4267
4268 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4271 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4272 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4273 }
4274
4275 #[test]
4281 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4282 for (name, ty, width) in [
4283 ("__builtin_bswap16", "unsigned short", "i16"),
4284 ("__builtin_bswap32", "unsigned", "i32"),
4285 ("__builtin_bswap64", "unsigned long long", "i64"),
4286 ] {
4287 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4288 let text = body(&source);
4289 assert_eq!(
4290 text,
4291 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4292 "{name}"
4293 );
4294 }
4295 }
4296
4297 #[test]
4304 fn the_bit_counts_are_instructions_and_not_calls() {
4305 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4306 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4307
4308 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4309 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4310
4311 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4312 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4313 }
4314
4315 #[test]
4324 fn the_bit_counts_ask_about_the_width_their_name_says() {
4325 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4326 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4327 assert!(text.contains("%1 = ctlz %0"), "{text}");
4328 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4329
4330 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4333 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4334 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4335
4336 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4337 assert!(text.contains("%1 = ctpop %0"), "{text}");
4338 assert!(!text.contains("call"), "{text}");
4339 }
4340
4341 #[test]
4346 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4347 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4348 assert!(text.contains("%1 = ctpop %0"), "{text}");
4349 assert!(text.contains("iconst.i32 1"), "{text}");
4350 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4351 }
4352
4353 #[test]
4359 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4360 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4361 assert!(text.contains("%1 = cttz %0"), "{text}");
4362 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4363 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4364 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4365 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4366 assert!(!text.contains("br_if"), "no branch: {text}");
4367 }
4368
4369 #[test]
4379 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4380 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4381 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4382 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4383 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4384 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4385 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4386 assert!(text.contains("%7 = ctlz %6"), "{text}");
4387 assert!(!text.contains("call"), "{text}");
4388 assert!(!text.contains("br_if"), "no branch: {text}");
4389 }
4390
4391 #[test]
4397 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4398 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4399 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4400 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4401 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4402
4403 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4404 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4405
4406 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4409 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4410 }
4411
4412 #[test]
4420 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4421 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4422 assert!(text.contains("iconst.i64 63"), "{text}");
4423 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4424 assert!(!text.contains("call"), "{text}");
4425
4426 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4427 assert!(text.contains("iconst.i64 63"), "{text}");
4428 assert!(!text.contains("call"), "{text}");
4429 }
4430
4431 #[test]
4439 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4440 let text =
4441 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4442 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4443 assert!(!text.contains("store"), "nothing is written: {text}");
4444 assert!(!text.contains("call"), "{text}");
4445
4446 let text =
4449 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4450 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4451 assert!(!text.contains("store"), "{text}");
4452
4453 let text = body(concat!(
4456 "int g(void);\n",
4457 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4458 ));
4459 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4460 }
4461
4462 #[test]
4472 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4473 let text =
4474 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4475 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4476 assert!(text.contains("store %3 -> %2"), "{text}");
4477 assert!(!text.contains("call"), "{text}");
4478
4479 let text =
4480 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4481 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4482
4483 let text =
4484 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4485 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4486
4487 let text = body(
4490 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4491 );
4492 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4493 }
4494
4495 #[test]
4503 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4504 let text = body(
4505 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4506 );
4507 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4508 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4509 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4510
4511 let text = body(
4514 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4515 );
4516 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4517 assert!(!text.contains("sext."), "{text}");
4518 assert!(!text.contains("zext.i64"), "{text}");
4520 }
4521
4522 #[test]
4530 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4531 let text =
4532 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4533 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4534 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4535 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4536 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4537 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4538 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4539 }
4540
4541 #[test]
4548 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4549 for name in ["add", "sub", "mul"] {
4550 let source = format!(
4551 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4552 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4553 );
4554 let mut opts = options();
4555 opts.emit = EmitKind::MirFinal;
4556 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4557 }
4558 }
4559
4560 #[test]
4563 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4564 let messages =
4565 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4566 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4567
4568 let messages =
4569 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4570 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4571 }
4572
4573 #[test]
4584 fn an_ordered_access_is_ordered_in_the_ir() {
4585 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4586 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4587
4588 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4589 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4590
4591 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4592 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4593
4594 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4595 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4596
4597 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4600 assert!(text.contains("trunc.i8 %1"), "{text}");
4601 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4602 }
4603
4604 #[test]
4613 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4614 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4615 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4616 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4617
4618 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4619 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4620 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4621
4622 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4623 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4624 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4625 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4626 }
4627
4628 #[test]
4638 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4639 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4640 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4641
4642 for weaker in ["1", "2", "3", "4"] {
4643 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4644 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4645 }
4646 }
4647
4648 #[test]
4658 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
4659 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
4660 let source = format!("void f(void) {{ {name}(); }}\n");
4661 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
4662 let text = body(&source);
4663 assert!(text.contains("fence seq_cst"), "{name}: {text}");
4664 }
4665
4666 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
4667 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4668 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
4669 }
4670
4671 #[test]
4677 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4678 let text =
4681 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4682 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4683 assert!(text.contains("return %3"), "the value it found: {text}");
4684
4685 let text =
4686 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4687 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4688 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4689
4690 let text = body(
4693 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4694 );
4695 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4696 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4697 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4698 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4699
4700 let text = body(
4703 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4704 );
4705 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4706 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4707 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4708 }
4709
4710 #[test]
4717 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4718 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4719 for (ty, suffix, reg) in widths {
4720 let source = format!(
4721 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4722 );
4723 let text = asm(&source);
4724 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4725 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4726 assert!(text.contains("sete\t"), "{ty}: {text}");
4727 }
4728 let source =
4729 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4730 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4731
4732 for order in ["0", "2", "3", "4", "5"] {
4736 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4737 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4738 let text = asm(&source);
4739 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4740 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4741 }
4742 }
4743
4744 #[test]
4756 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4757 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4758 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4759 assert!(text.contains("return %2"), "the value that was there: {text}");
4760
4761 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4762 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4763 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4764
4765 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4766 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4767 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4768
4769 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4771 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4772
4773 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4776 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4777
4778 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4779 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4780
4781 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4784 assert!(text.contains("release"), "{text}");
4785 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4786
4787 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4791 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4792 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4793
4794 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4797 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4798
4799 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4800 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4801 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4802
4803 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4806 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4807 assert!(text.contains("%3 = and %2, %1"), "{text}");
4808 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4809 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4810 }
4811
4812 #[test]
4823 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4824 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4825 for (ty, suffix, reg) in widths {
4826 for (name, call, insn) in [
4827 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4828 ("or", "__sync_fetch_and_or(p, v)", "or"),
4829 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4830 ] {
4831 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4832 let text = asm(&source);
4833 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4834 assert!(
4835 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4836 "{ty} {name}: {text}"
4837 );
4838 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4839 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4841 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4842 }
4843 }
4844 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4845 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4846
4847 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4851 assert!(text.contains("cmpxchgl\t"), "{text}");
4852 assert!(text.contains("andl\t"), "{text}");
4853 assert!(text.contains("notl\t"), "{text}");
4854 }
4855
4856 #[test]
4865 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4866 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4867 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4868 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4869
4870 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4871 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4872 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4873
4874 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4877 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4878 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4879 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4880 }
4881
4882 #[test]
4893 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4894 for pointer in ["char", "int", "void"] {
4895 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4896 let text = body(&source);
4897 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4898 assert!(
4899 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4900 "{pointer}: {text}"
4901 );
4902 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4903
4904 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4905 let text = body(&source);
4906 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4907 }
4908
4909 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4912 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4913 assert!(text.contains("setne\t"), "{text}");
4914 }
4915
4916 #[test]
4924 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4925 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4926 for (ty, suffix, reg) in widths {
4927 let source =
4928 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4929 let text = asm(&source);
4930 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4931 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4932
4933 let source =
4934 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4935 let text = asm(&source);
4936 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4937 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4938 }
4939 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4940 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4941
4942 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4945 let text = asm(source);
4946 assert!(text.contains("negl\t"), "{text}");
4947 assert!(text.contains("xaddl\t"), "{text}");
4948
4949 for order in ["0", "2", "3", "4", "5"] {
4952 let source =
4953 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4954 let text = asm(&source);
4955 assert!(text.contains("xaddl\t"), "{order}: {text}");
4956 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4957 }
4958
4959 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4963 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4964 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4971 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
4972 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4973 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4974 }
4975
4976 #[test]
4988 fn the_lock_free_questions_are_answered_as_constants() {
4989 for size in ["1", "2", "4", "8"] {
4990 let source =
4991 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4992 let text = asm(&source);
4993 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4994 assert!(!text.contains("call"), "and is not a call: {text}");
4995 }
4996 for size in ["3", "16", "sizeof(long double)"] {
4997 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4998 let text = asm(&source);
4999 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
5000 assert!(!text.contains("call"), "and is not a call either: {text}");
5001 }
5002
5003 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
5007 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
5008 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
5009 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
5010 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
5011 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
5012 }
5013
5014 #[test]
5026 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
5027 let mut opts = options();
5028 opts.emit = EmitKind::Ir;
5029
5030 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
5031 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
5032 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
5033
5034 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
5035 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
5036 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
5037
5038 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
5039 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
5040 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
5041 }
5042
5043 #[test]
5055 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5056 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5057 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5058 assert!(text.contains("shrq"), "with the value halved first: {text}");
5059 assert!(text.contains("addsd"), "and doubled after: {text}");
5060 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5061
5062 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5063 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5064 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5065 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5066 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5067 }
5068
5069 #[test]
5080 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5081 let taken = concat!(
5082 "static long long llabs(long long b) { return 7; }\n",
5083 "long long f(long long x) { return llabs(x); }\n",
5084 );
5085 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5086
5087 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5088 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5089
5090 let plain = concat!(
5091 "long long llabs(long long b);\n",
5092 "long long f(long long x) { return llabs(x); }\n",
5093 );
5094 let mut opts = options();
5095 opts.emit = EmitKind::Ir;
5096 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5097
5098 opts.builtins = false;
5099 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5100
5101 opts.builtins = true;
5102 opts.no_builtin = vec!["llabs".to_owned()];
5103 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5104 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5105 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5106
5107 opts.no_builtin = Vec::new();
5110 opts.builtins = false;
5111 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5112 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5113 }
5114
5115 #[test]
5128 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5129 let text = ir(concat!(
5130 "long a = __builtin_expect(7, 1);\n",
5131 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5132 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5133 ));
5134 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5135 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5136 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5137 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5138
5139 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5142 assert!(text.contains("sext"), "{text}");
5143
5144 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5148 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5149 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5150 assert_eq!(body(source), one);
5151
5152 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5157 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5158 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5159 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5160 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5161 }
5162
5163 #[test]
5175 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5176 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5177 let text = ir(promised);
5178 assert!(text.contains(" unreachable_hint\n"), "{text}");
5179 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5180
5181 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5185 assert!(after.contains("return"), "{after}");
5186
5187 let text = asm(promised);
5190 let mine = text.split_once("\nf:\n").expect("a definition").1;
5191 let mine = mine.split_once("\t.size").expect("a definition").0;
5192 let plain = asm("int f(int x) { if (x) return 1; }\n");
5193 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5194 let plain = plain.split_once("\t.size").expect("a definition").0;
5195 assert_eq!(mine, plain);
5196 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5199 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5200 assert!(!mine.contains("ud2"), "{mine}");
5201 }
5202
5203 #[test]
5210 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5211 let mut opts = options();
5212 opts.emit = EmitKind::Ir;
5213 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5214 assert!(
5215 messages.iter().any(|m| m.contains("__builtin_abort")),
5216 "expected the written name in {messages:?}"
5217 );
5218 }
5219
5220 #[test]
5228 fn a_builtin_nothing_lowers_is_refused_by_name() {
5229 let mut opts = options();
5230 opts.emit = EmitKind::Ir;
5231 let builtin = "__atomic_signal_fence";
5232 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5233 let messages = run(&opts, &source).messages;
5234 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5235 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5236 }
5237
5238 #[test]
5247 fn what_is_refused_is_the_call_and_not_the_name() {
5248 let text = ir(concat!(
5249 "void __atomic_signal_fence(int order) { (void)order; }\n",
5250 "void f(void) { __atomic_signal_fence(5); }\n",
5251 ));
5252 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5253 }
5254
5255 #[test]
5264 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5265 let text = ir(concat!(
5266 "struct S { char a[8]; int n; char b[12]; };\n",
5267 "char g[32];\n",
5268 "struct S gs;\n",
5269 "unsigned long whole = __builtin_object_size(g, 0);\n",
5270 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5271 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5272 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5273 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5274 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5275 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5276 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5277 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5278 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5279 ));
5280 for (name, size) in [
5281 ("whole", 32),
5282 ("moved", 28),
5283 ("back", 4),
5284 ("outer", 24),
5285 ("inner", 8),
5286 ("scalar", 4),
5287 ("after", 16),
5288 ("into", 10),
5289 ("text", 6),
5290 ("dyn", 12),
5291 ] {
5292 let said = format!("global @{name} : i64 = {size},");
5293 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5294 }
5295 }
5296
5297 #[test]
5305 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5306 let text = body(concat!(
5307 "struct S { char a[8]; int n; char b[12]; };\n",
5308 "unsigned long f(void) {\n",
5309 " char loc[20];\n",
5310 " struct S ls;\n",
5311 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5312 "}\n",
5313 ));
5314 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5315 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5316 }
5317
5318 #[test]
5328 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5329 let text = ir(concat!(
5330 "struct T { int n; char f[]; };\n",
5331 "extern char *p;\n",
5332 "extern struct T *t;\n",
5333 "unsigned long largest = __builtin_object_size(p, 0);\n",
5334 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5335 "unsigned long least = __builtin_object_size(p, 2);\n",
5336 "unsigned long tight = __builtin_object_size(p, 3);\n",
5337 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5338 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5339 ));
5340 for name in ["largest", "nearest", "flex"] {
5341 let said = format!("global @{name} : i64 = -1,");
5345 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5346 }
5347 for name in ["least", "tight"] {
5348 let said = format!("global @{name} : i64 = 0,");
5349 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5350 }
5351 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5352 }
5353
5354 #[test]
5361 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5362 let text = body(concat!(
5363 "extern char *side(void);\n",
5364 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5365 ));
5366 assert!(!text.contains("call"), "nothing is called: {text}");
5367 }
5368
5369 #[test]
5374 fn a_kind_that_is_not_one_of_the_four_is_refused() {
5375 for source in [
5376 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
5377 + "{ return __builtin_object_size(p, k); }\n",
5378 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
5379 .to_owned(),
5380 "extern char *p;\nunsigned long f(void) ".to_owned()
5381 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
5382 ] {
5383 let messages = errors(&source);
5384 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
5385 assert!(named, "expected a complaint about the kind in {messages:?}");
5386 }
5387 }
5388
5389 #[test]
5395 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
5396 let text = ir(concat!(
5397 "void *buf[5];\n",
5398 "int f(void) {\n",
5399 " if (__builtin_setjmp(buf)) return 2;\n",
5400 " return 1;\n",
5401 "}\n",
5402 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5403 ));
5404 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5405 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5406 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5407 }
5408
5409 #[test]
5417 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5418 let text = ir(concat!(
5419 "void *buf[5];\n",
5420 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5421 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5422 ));
5423 let (saves, plain) = text.split_once("func @g").expect("both functions");
5424 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5425 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5426 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5427 }
5428
5429 #[test]
5438 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5439 let text =
5440 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5441 let body = text.split_once("\nf:\n").expect("the function").1;
5442 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5443 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5444 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5445 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5446 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5447 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5448 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5449 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5450 }
5451
5452 #[test]
5460 fn a_save_destroys_every_register_the_allocator_hands_out() {
5461 let text =
5462 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5463 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5464 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5465 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5466 }
5467 }
5468
5469 #[test]
5476 fn the_restore_puts_the_frame_back_before_it_jumps() {
5477 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5478 let mut opts = options();
5479 opts.emit = EmitKind::Asm;
5480 opts.opt_level = level;
5481 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5482 let result = run(&opts, source);
5483 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5484 let text = result.text().to_owned();
5485 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5486 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5487 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5488 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5489 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5490 }
5491 }
5492
5493 #[test]
5499 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5500 for source in [
5501 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5502 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5503 ] {
5504 let messages = errors(source);
5505 let named = messages.iter().any(|m| m.contains("E0710"));
5506 assert!(named, "expected a complaint about the value in {messages:?}");
5507 }
5508 }
5509
5510 #[test]
5515 fn a_static_function_nothing_refers_to_is_not_emitted() {
5516 let text = ir("static int dropped(void) { return 1; }\n\
5517 static int kept(void) { return 2; }\n\
5518 int main(void) { return kept(); }\n");
5519 assert!(text.contains("func @kept"), "{text}");
5520 assert!(!text.contains("dropped"), "{text}");
5521 }
5522
5523 #[test]
5529 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5530 let text = ir("static int ping(void);\n\
5531 static int pong(void) { return ping(); }\n\
5532 static int ping(void) { return pong(); }\n\
5533 int main(void) { return 0; }\n");
5534 assert!(!text.contains("ping"), "{text}");
5535 assert!(!text.contains("pong"), "{text}");
5536 }
5537
5538 #[test]
5544 fn naming_a_static_function_anywhere_keeps_it() {
5545 let text = ir("static int by_address(void) { return 1; }\n\
5546 static int in_an_image(void) { return 2; }\n\
5547 static int deeper(void) { return 3; }\n\
5548 static int reaches_deeper(void) { return deeper(); }\n\
5549 static int (*table[1])(void) = {in_an_image};\n\
5550 int main(void) {\n\
5551 int (*p)(void) = by_address;\n\
5552 return p() + table[0]() + reaches_deeper();\n\
5553 }\n");
5554 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5555 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5556 }
5557 }
5558
5559 #[test]
5565 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5566 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5567 let source = format!(
5568 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5569 int main(void) {{ return 0; }}\n"
5570 );
5571 let text = ir(&source);
5572 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5573 }
5574 }
5575
5576 #[test]
5579 fn a_function_anything_could_call_is_emitted_without_being_called() {
5580 let text =
5581 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5582 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5583 }
5584
5585 #[test]
5592 fn a_classification_c_has_an_operator_for_is_that_operator() {
5593 for (builtin, operator) in [
5594 ("__builtin_isgreater", "binary >"),
5595 ("__builtin_isgreaterequal", "binary >="),
5596 ("__builtin_isless", "binary <"),
5597 ("__builtin_islessequal", "binary <="),
5598 ] {
5599 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5600 let text = tast(&source);
5601 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5602 }
5603 }
5604
5605 #[test]
5614 fn the_classification_builtins_are_comparisons_and_not_calls() {
5615 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5616 assert_eq!(
5617 text,
5618 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5619 %2\n return %3\n"
5620 );
5621
5622 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5624 assert!(text.contains("fcmp one %0, %1"), "{text}");
5625
5626 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5627 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5628
5629 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5630 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5631 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5632 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5633 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5634 assert!(text.contains("%5 = or %3, %4"), "{text}");
5635
5636 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5639 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5640 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5641 assert!(text.contains("%5 = and %3, %4"), "{text}");
5642
5643 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5644 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5645 assert!(text.contains("icmp slt %1, %2"), "{text}");
5646
5647 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5650 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5651
5652 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5655 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5656 }
5657
5658 #[test]
5665 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5666 let text = ir(concat!(
5667 "int a = __builtin_isinff(1e300);\n",
5668 "int b = __builtin_isinf(1e300);\n",
5669 "int c = __builtin_isnan(0.0);\n",
5673 "int d = __builtin_signbit(-0.0);\n",
5674 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5675 ));
5676 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5677 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5678 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5679 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5680 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5681 }
5682
5683 #[test]
5685 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5686 let mut opts = options();
5687 opts.emit = EmitKind::Ir;
5688 let source = concat!(
5689 "int a(int x) { return __builtin_isnan(x); }\n",
5690 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5691 "int c(double x) { return __builtin_isnan(x, x); }\n",
5692 );
5693 let messages = run(&opts, source).messages;
5694 assert_eq!(
5695 messages,
5696 [
5697 "/main.c:1:23: error: non-floating-point argument in call to function \
5698 '__builtin_isnan' [E0685]",
5699 "/main.c:2:30: error: non-floating-point arguments in call to function \
5700 '__builtin_isunordered' [E0685]",
5701 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5702 ]
5703 );
5704 }
5705
5706 #[test]
5715 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5716 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5717 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5721 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5722 assert!(text.contains("%3 = and %1, %2"), "{text}");
5723 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5724 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5725 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5726 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5727 assert!(text.contains("%8 = and %6, %7"), "{text}");
5728
5729 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5733 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5734 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5735
5736 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5737 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5738 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5739 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5740
5741 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5742 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5743 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5744 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5748 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5749 assert!(!text.contains("call"), "{text}");
5750
5751 let text = body(concat!(
5754 "double g(void);\n",
5755 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5756 ));
5757 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5758 }
5759
5760 #[test]
5767 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5768 let text = ir(concat!(
5769 "int a = __builtin_isnormal(1.0);\n",
5770 "int b = __builtin_isnormal(0.0);\n",
5771 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5772 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5773 "int e = __builtin_isinf_sign(1.0);\n",
5774 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5775 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5776 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5777 ));
5778 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5779 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5780 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5781 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5782 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5783 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5784 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5785 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5786 }
5787
5788 #[test]
5794 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5795 let mut opts = options();
5796 opts.emit = EmitKind::Ir;
5797 let source = concat!(
5798 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5799 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5800 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5801 );
5802 let messages = run(&opts, source).messages;
5803 assert_eq!(
5804 messages,
5805 [
5806 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5807 '__builtin_fpclassify' [E0687]",
5808 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5809 [E0511]",
5810 "/main.c:3:23: error: non-floating-point argument in call to function \
5811 '__builtin_fpclassify' [E0685]",
5812 ]
5813 );
5814 }
5815
5816 #[test]
5824 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5825 let text = ir(concat!(
5826 "double a = __builtin_inf();\n",
5827 "float b = __builtin_huge_valf();\n",
5828 "long double c = __builtin_infl();\n",
5829 "double d = __builtin_huge_val();\n",
5830 ));
5831 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5832 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5833 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5834 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5835 assert!(!text.contains("call"), "{text}");
5836 }
5837
5838 #[test]
5847 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5848 let text = ir(concat!(
5849 "double a = __builtin_nan(\"\");\n",
5850 "double b = __builtin_nan(\"0x1\");\n",
5851 "double c = __builtin_nan(\"010\");\n",
5853 "double d = __builtin_nans(\"\");\n",
5854 "double e = __builtin_nans(\"0x1\");\n",
5855 "float f = __builtin_nanf(\"0x1\");\n",
5856 "float g = __builtin_nansf(\"\");\n",
5857 "long double h = __builtin_nansl(\"\");\n",
5858 ));
5859 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5860 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5861 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5862 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5863 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5864 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5865 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5866 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5867
5868 let text = ir(concat!(
5871 "double f(const char *p) { return __builtin_nan(p); }\n",
5872 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5873 ));
5874 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5875 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5876 }
5877
5878 #[test]
5886 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5887 let text = ir(concat!(
5888 "unsigned long a = __builtin_strlen(\"hello\");\n",
5889 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5890 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5891 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5892 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5893 ));
5894 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5895 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5896 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5897 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5898 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5899 assert!(!text.contains("call"), "{text}");
5900
5901 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5903 assert!(text.contains("call @strlen("), "{text}");
5904 }
5905
5906 #[test]
5913 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5914 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5915 assert!(text.contains("bitcast.i64 %0"), "{text}");
5916 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5917 assert!(text.contains("and %1, %2"), "{text}");
5918 assert!(text.contains("bitcast.f64 %3"), "{text}");
5919 assert!(!text.contains("call"), "{text}");
5920
5921 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5922 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5923 assert!(text.contains("%8 = or %4, %7"), "{text}");
5924 assert!(!text.contains("call"), "{text}");
5925
5926 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5929 assert!(text.contains("bitcast.i80 %0"), "{text}");
5930 assert!(text.contains("bitcast.f80"), "{text}");
5931
5932 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5935 assert!(text.contains("fpext.f64 %0"), "{text}");
5936 assert!(text.contains("bitcast.i64 %1"), "{text}");
5937 }
5938
5939 #[test]
5948 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5949 let text =
5950 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5951 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5952 assert!(!text.contains("call"), "{text}");
5953
5954 let text =
5955 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5956 assert!(text.contains("bitcast.i32 %0"), "{text}");
5957 assert!(!text.contains("call"), "{text}");
5958
5959 let text = body(concat!(
5960 "double copysign(double x, double y);\n",
5961 "double f(double x, double y) { return copysign(x, y); }\n",
5962 ));
5963 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5964 assert!(!text.contains("call"), "{text}");
5965
5966 let text = body(concat!(
5967 "float copysignf(float x, float y);\n",
5968 "float f(float x, float y) { return copysignf(x, y); }\n",
5969 ));
5970 assert!(!text.contains("call"), "{text}");
5971
5972 let text = ir(concat!(
5976 "long double fabsl(long double x);\n",
5977 "long double f(long double x) { return fabsl(x); }\n",
5978 ));
5979 assert!(text.contains("call @fabsl"), "{text}");
5980 }
5981
5982 #[test]
5990 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5991 let taken = concat!(
5992 "static double fabs(double b) { return 7; }\n",
5993 "double f(double x) { return fabs(x); }\n",
5994 );
5995 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5996
5997 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5998 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5999
6000 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
6001 let mut opts = options();
6002 opts.emit = EmitKind::Ir;
6003 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
6004
6005 opts.builtins = false;
6006 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
6007
6008 opts.builtins = true;
6009 opts.no_builtin = vec!["fabs".to_owned()];
6010 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
6011 let one = concat!(
6012 "double copysign(double a, double b);\n",
6013 "double f(double x) { return copysign(x, 1.0); }\n",
6014 );
6015 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
6016
6017 opts.no_builtin = Vec::new();
6019 opts.builtins = false;
6020 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
6021 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
6022 }
6023
6024 #[test]
6033 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
6034 let text = ir(concat!(
6035 "double a = __builtin_fabs(-3.5);\n",
6036 "double b = __builtin_copysign(1.0, -0.0);\n",
6037 "double c = __builtin_copysign(0.0, -2.0);\n",
6038 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
6040 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
6041 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
6042 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
6043 "long double i = __builtin_fabsl(-__builtin_infl());\n",
6044 ));
6045 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6046 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6047 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6048 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6049 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6050 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6051 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6052 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6053 }
6054
6055 #[test]
6063 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6064 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6065 assert!(!text.contains("call"), "{text}");
6066 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6067 assert!(!text.contains("call"), "{text}");
6068
6069 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6072 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6073 assert!(!text.contains("call"), "{text}");
6074 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6075 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6076
6077 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6080 assert_eq!(written, text, "the name and the operator are the same thing");
6081
6082 let text = body(concat!(
6084 "double creal(_Complex double z);\n",
6085 "double f(_Complex double z) { return creal(z); }\n",
6086 ));
6087 assert!(!text.contains("call"), "{text}");
6088 let text = body(concat!(
6089 "_Complex float conjf(_Complex float z);\n",
6090 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6091 ));
6092 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6093 assert!(!text.contains("call"), "{text}");
6094
6095 let taken = concat!(
6098 "static double creal(_Complex double z) { return 7; }\n",
6099 "double f(_Complex double z) { return creal(z); }\n",
6100 );
6101 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6102 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6103 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6104 let plain = concat!(
6105 "double cimag(_Complex double z);\n",
6106 "double f(_Complex double z) { return cimag(z); }\n",
6107 );
6108 let mut opts = options();
6109 opts.emit = EmitKind::Ir;
6110 opts.builtins = false;
6111 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6112 opts.builtins = true;
6113 opts.no_builtin = vec!["cimag".to_owned()];
6114 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6115
6116 let text = ir(concat!(
6118 "double a = __builtin_creal(1.5 + 2.5i);\n",
6119 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6120 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6121 ));
6122 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6123 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6124 assert!(
6125 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6126 "the conjugate of a constant is the constant with the second half negated: {text}"
6127 );
6128 assert!(!text.contains("call"), "{text}");
6129 }
6130
6131 #[test]
6139 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6140 let text = ir(concat!(
6141 "double a = __builtin_ceil(1.5);\n",
6142 "double b = __builtin_floor(1.5);\n",
6143 "double c = __builtin_trunc(-1.5);\n",
6144 "double d = __builtin_round(2.5);\n",
6147 "double e = __builtin_ceil(-0.5);\n",
6149 "double f = __builtin_fmax(1.0, 2.0);\n",
6150 "double g = __builtin_fmin(1.0, 2.0);\n",
6151 "float h = __builtin_ceilf(1.25f);\n",
6152 "double ceil(double x);\n",
6155 "double i = ceil(2.25);\n",
6156 ));
6157 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6158 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6159 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6160 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6161 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6162 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6163 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6164 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6165 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6166 assert!(!text.contains("call"), "{text}");
6167 }
6168
6169 #[test]
6177 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6178 let text = ir(concat!(
6179 "double f(double x) { return __builtin_ceil(x); }\n",
6180 "float g(float x) { return __builtin_floorf(x); }\n",
6181 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6182 ));
6183 assert!(text.contains("call @ceil("), "{text}");
6184 assert!(text.contains("call @floorf("), "{text}");
6185 assert!(text.contains("call @fmax("), "{text}");
6186
6187 let text = ir(concat!(
6191 "double f(void) { return __builtin_rint(2.5); }\n",
6192 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6193 ));
6194 assert!(text.contains("call @rint("), "{text}");
6195 assert!(text.contains("call @nearbyint("), "{text}");
6196
6197 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6200 assert!(text.contains("call @fmin("), "{text}");
6201
6202 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6205 let mut opts = options();
6206 opts.emit = EmitKind::Ir;
6207 opts.no_builtin = vec!["ceil".to_owned()];
6208 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6209 }
6210
6211 #[test]
6218 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6219 let text = ir(concat!(
6220 "constexpr int side = 4;\n",
6221 "constexpr int wider = side + 1;\n",
6222 "constexpr double half = 1.5;\n",
6223 "struct point { int x; int y; };\n",
6224 "constexpr struct point origin = { 5, 6 };\n",
6225 "int square[side * side];\n",
6226 "int rectangle[wider];\n",
6227 "int rounded[(int)half * 2];\n",
6228 "int across[origin.y];\n",
6229 "enum named { four = side };\n",
6230 "int e = four;\n",
6231 ));
6232 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6233 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6234 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6235 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6236 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6237
6238 let mut opts = options();
6241 opts.emit = EmitKind::Ir;
6242 let konst = "const int n = 1;\nint a[n];\n";
6243 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6244 assert_eq!(run(&opts, konst).messages, [message]);
6245
6246 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6248 assert_eq!(run(&opts, subscript).messages, [message]);
6249
6250 let address = "constexpr int c = 3;\nint *p = &c;\n";
6252 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6253 pointer target type [E0514]";
6254 assert_eq!(run(&opts, address).messages, [warning]);
6255 }
6256
6257 #[test]
6266 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
6267 let mut opts = options();
6268 opts.emit = EmitKind::Ir;
6269
6270 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
6271 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
6272 assert_eq!(run(&opts, alone).messages, [message]);
6273
6274 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
6276 assert_eq!(run(&opts, first).messages, [message]);
6277
6278 let negative = "struct F { int a[-1]; };\n";
6281 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
6282 assert_eq!(run(&opts, negative).messages, [refused]);
6283
6284 let flexible = "struct G { int a[]; };\n";
6287 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
6288 members [E0554]";
6289 assert_eq!(run(&opts, flexible).messages, [named]);
6290 }
6291
6292 #[test]
6307 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
6308 let mut opts = options();
6309 opts.emit = EmitKind::Ir;
6310 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
6311
6312 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
6314 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
6315
6316 let plain = concat!(
6319 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
6320 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
6321 );
6322 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
6323
6324 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
6327 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
6328 [E0514]";
6329 assert_eq!(run(&opts, &dropping).messages, [warning]);
6330
6331 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
6334 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
6335 incompatible return type 'const unsigned int (*)[4]' [E0512]";
6336 assert_eq!(run(&opts, wrong).messages, [error]);
6337 }
6338
6339 #[test]
6348 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
6349 let mut opts = options();
6352 opts.std = Std::C17;
6353 let source = concat!(
6354 "int add(a, b)\n",
6355 "int a;\n",
6356 "int b;\n",
6357 "{ return a + b; }\n",
6358 "int promoted(c)\n",
6359 "char c;\n",
6360 "{ return c; }\n",
6361 "int narrow(char);\n",
6362 "int narrow(c)\n",
6363 "char c;\n",
6364 "{ return c; }\n",
6365 "int first(a)\n",
6366 "int a[4];\n",
6367 "{ return a[0]; }\n",
6368 );
6369 let result = run(&opts, source);
6370 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6371 let text = result.text();
6372 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6373 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6374 assert!(text.contains("c : char object automatic defined"), "{text}");
6376 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
6377 assert!(text.contains("first : int(int *) function external defined"), "{text}");
6379 }
6380
6381 #[test]
6388 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
6389 let mut opts = options();
6390 opts.std = Std::C17;
6391 for (source, message) in [
6392 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
6393 (
6394 "int f(a)\nint a;\nint b;\n{ return a; }\n",
6395 "3:5: error: declaration for parameter 'b' but no such parameter",
6396 ),
6397 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
6398 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
6399 (
6400 "int f(a)\nstatic int a;\n{ return a; }\n",
6401 "2:12: error: storage class specified for parameter 'a'",
6402 ),
6403 (
6404 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
6405 "2:7: error: argument 'a' doesn't match prototype",
6406 ),
6407 ] {
6408 let result = run(&opts, source);
6409 assert!(result.failed(), "expected this to fail:\n{source}");
6410 assert!(result.messages[0].contains(message), "{:?}", result.messages);
6411 }
6412
6413 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
6416 let mut older = options();
6417 older.std = Std::C89;
6418 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
6419 let result = run(&opts, implicit);
6420 assert!(
6421 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
6422 "{:?}",
6423 result.messages
6424 );
6425
6426 let mut newer = options();
6430 newer.std = Std::C23;
6431 let plain = "int f(a)\nint a;\n{ return a; }\n";
6432 let result = run(&newer, plain);
6433 assert!(!result.failed(), "{:?}", result.messages);
6434 assert_eq!(
6435 result.messages,
6436 ["/main.c:1:5: warning: old-style function definition [E0412]"]
6437 );
6438 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
6439 }
6440
6441 #[test]
6448 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
6449 let array = "int a[8] = { [3] 7 };\n";
6450 let member = "struct s { int x; } v = { x: 7 };\n";
6451 for source in [array, member] {
6452 let result = run(&options(), source);
6453 assert!(!result.failed(), "{:?}", result.messages);
6454 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
6455 }
6456
6457 let mut asked = options();
6458 asked.pedantic = true;
6459 assert_eq!(
6460 run(&asked, array).messages,
6461 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
6462 );
6463 assert_eq!(
6464 run(&asked, member).messages,
6465 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
6466 );
6467 }
6468
6469 #[test]
6476 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
6477 let text = ir(concat!(
6478 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
6479 "struct brim { char buf[9223372036854775807L]; };\n",
6480 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
6481 "unsigned long h = sizeof(struct huge_struct);\n",
6482 "unsigned long b = sizeof(struct brim);\n",
6483 "unsigned long y = sizeof(struct bitty);\n",
6484 ));
6485 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6486 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6487 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6488
6489 let mut opts = options();
6490 opts.emit = EmitKind::Ir;
6491 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6492 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6493 assert_eq!(run(&opts, over).messages, [message]);
6494 let array = "struct wide { short buf[1L << 62]; };\n";
6495 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6496 maximum object size '9223372036854775807' [E0537]";
6497 assert_eq!(run(&opts, array).messages[0], message);
6498 }
6499
6500 fn compile_bytes(source: &[u8]) -> Compiled {
6505 let mut opts = options();
6506 opts.emit = EmitKind::Ir;
6507 let mut fs = MemoryFileSystem::new();
6508 fs.insert("/main.c", source.to_vec());
6509 compile(&opts, "/main.c", &fs)
6510 }
6511
6512 #[test]
6519 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6520 let mut source = b"char s[] = \"a".to_vec();
6521 source.push(0xff);
6522 source.extend_from_slice(b"b\";\nchar c = '");
6523 source.push(0xff);
6524 source.extend_from_slice(b"';\n");
6525 let result = compile_bytes(&source);
6526 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6527 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6528 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6530
6531 let mut stray = b"int a".to_vec();
6532 stray.push(0xff);
6533 stray.extend_from_slice(b" = 1;\n");
6534 let result = compile_bytes(&stray);
6535 assert!(
6536 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6537 "{:?}",
6538 result.messages
6539 );
6540 }
6541
6542 #[test]
6543 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6544 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6545 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6546 let expected = "\
6547func @add(i32, i32) -> i32, linkage(external) {
6548block0(%0: i32, %1: i32):
6549 %2 = add.nsw %0, %1
6550 return %2
6551}
6552";
6553 assert!(text.contains(expected), "{text}");
6554 }
6555
6556 #[test]
6557 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6558 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6559 assert!(!text.contains("alloca"), "{text}");
6560 assert!(!text.contains("load"), "{text}");
6561 assert!(!text.contains("store"), "{text}");
6562 }
6563
6564 #[test]
6565 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6566 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6567 let expected = "\
6568block0:
6569 %0 = alloca, size 4, align 4
6570 %1 = iconst.i32 1
6571 store %1 -> %0, align 4, tbaa !1
6572 %2 = call @g(%0) : (ptr) -> i32
6573 return %2
6574";
6575 assert_eq!(text, expected);
6576 }
6577
6578 #[test]
6579 fn a_loop_carries_what_it_changes_as_block_parameters() {
6580 let text = body(
6583 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6584 return total;\n}\n",
6585 );
6586 assert!(!text.contains("alloca"), "{text}");
6587 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6588 assert!(text.contains("jump block1("), "{text}");
6589 }
6590
6591 #[test]
6592 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6593 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6594 assert!(text.contains("icmp slt %0, %1"), "{text}");
6595 assert!(!text.contains("zext"), "{text}");
6596 }
6597
6598 #[test]
6599 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6600 let text = body("int f(int a, int b) { return a && b; }\n");
6601 let expected = "\
6602block0(%0: i32, %1: i32):
6603 %2 = iconst.i32 0
6604 %3 = icmp ne %0, %2
6605 %4 = iconst.i1 0
6606 br_if %3, block1, block2(%4)
6607
6608block1:
6609 %5 = iconst.i32 0
6610 %6 = icmp ne %1, %5
6611 jump block2(%6)
6612
6613block2(%7: i1):
6614 %8 = zext.i32 %7
6615 return %8
6616";
6617 assert_eq!(text, expected);
6618 }
6619
6620 #[test]
6621 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6622 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6623 assert!(!text.contains("block3"), "{text}");
6626 assert!(!text.contains("iconst.i32 3"), "{text}");
6627 }
6628
6629 #[test]
6630 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6631 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6632 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6633 assert!(body("int f(void) { }\n").contains("unreachable"));
6634 }
6635
6636 #[test]
6637 fn a_structure_is_copied_rather_than_held_in_a_value() {
6638 let text = body(
6639 "struct point { int x, y; };\n\
6640 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6641 );
6642 assert!(text.contains("memcpy"), "{text}");
6643 }
6644
6645 #[test]
6646 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6647 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6648 assert!(text.contains("memset"), "{text}");
6649 }
6650
6651 #[test]
6652 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6653 let text = body(
6654 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6655 default: r = 4; } return r; }\n",
6656 );
6657 let expected = "\
6658block0(%0: i32):
6659 %1 = iconst.i32 0
6660 switch %0, block1, [1 => block2, 2 => block3(%1)]
6661
6662block1:
6663 %2 = iconst.i32 4
6664 jump block4(%2)
6665
6666block2:
6667 %3 = iconst.i32 1
6668 jump block3(%3)
6669
6670block3(%4: i32):
6671 %5 = iconst.i32 2
6672 %6 = add.nsw %4, %5
6673 jump block4(%6)
6674
6675block4(%7: i32):
6676 return %7
6677";
6678 assert_eq!(text, expected);
6679 }
6680
6681 #[test]
6682 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6683 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6686 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6687 assert!(text.contains("icmp ule"), "{text}");
6688 assert!(!text.contains("switch"), "{text}");
6689 }
6690
6691 #[test]
6692 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6693 let text = body(
6694 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6695 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6696 );
6697 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6700 assert!(text.contains("block5:\n jump block7("), "{text}");
6701 assert!(text.contains("block6:\n jump block8("), "{text}");
6702 }
6703
6704 #[test]
6705 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6706 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6707 }
6708
6709 #[test]
6710 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6711 let text = body(
6716 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6717 return n; }\n",
6718 );
6719 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6722 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6723 assert!(text.contains("block4:\n jump block3("), "{text}");
6724 }
6725
6726 #[test]
6727 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6728 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6731 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6732 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6733 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6734 }
6735
6736 #[test]
6737 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6738 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6739 assert!(!text.contains("alloca"), "{text}");
6743 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6744 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6745 }
6746
6747 #[test]
6748 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6749 let text =
6750 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6751 assert!(!text.contains("alloca"), "{text}");
6752 assert!(text.contains("block1(%2: i32):"), "{text}");
6753 assert!(text.contains("jump block1(%5)"), "{text}");
6754 }
6755
6756 #[test]
6757 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6758 assert_eq!(
6761 body("int f(int x) { return x; spare: return 0; }\n"),
6762 "block0(%0: i32):\n return %0\n"
6763 );
6764 }
6765
6766 #[test]
6767 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6768 let text = body(
6769 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6770 );
6771 assert_eq!(
6774 text,
6775 "\
6776block0(%0: ptr):
6777 %1 = load.i8 %0, align 1
6778 %2 = iconst.i8 3
6779 %3 = ashr %1, %2
6780 %4 = sext.i32 %3
6781 return %4
6782"
6783 );
6784 }
6785
6786 #[test]
6787 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6788 let text =
6792 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6793 assert_eq!(
6794 text,
6795 "\
6796block0(%0: ptr, %1: i32):
6797 %2 = iconst.i32 16777215
6798 %3 = and %1, %2
6799 %4 = trunc.i16 %3
6800 store %4 -> %0, align 2
6801 %5 = iconst.i32 16
6802 %6 = lshr %3, %5
6803 %7 = trunc.i8 %6
6804 %8 = iconst.i64 2
6805 %9 = ptr_add %0, %8
6806 store %7 -> %9, align 1
6807 return
6808"
6809 );
6810 }
6811
6812 #[test]
6813 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6814 let text =
6815 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6816 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6819 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6820 }
6821
6822 #[test]
6823 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6824 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6827 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6828 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6829 }
6830
6831 #[test]
6832 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6833 let text = body(
6837 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6838 );
6839 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6840 }
6841
6842 #[test]
6843 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6844 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6847 assert!(
6848 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6849 "{text}"
6850 );
6851 }
6852
6853 #[test]
6854 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6855 let text = ir(concat!(
6860 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6861 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6862 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6863 "char s[2] = \"hi\";\n",
6864 ));
6865 assert!(
6866 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6867 "{text}"
6868 );
6869 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6870 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6871 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6874 }
6875
6876 #[test]
6877 fn a_definition_takes_a_parameter_it_left_unnamed() {
6878 let text = ir("int f(int a, int) { return a; }\n");
6882 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6883 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6884
6885 let text = ir("int g(int, int n) { return n; }\n");
6888 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6889 }
6890
6891 #[test]
6892 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6893 let text = body(concat!(
6898 "struct s { int f; int g; };\n",
6899 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6900 "{ *d = *e = a[0] = *c; }\n",
6901 ));
6902 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6903 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6904 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6905 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6906 }
6907
6908 #[test]
6909 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6910 let mut opts = options();
6915 opts.emit = EmitKind::Ir;
6916 let result = run(
6917 &opts,
6918 concat!(
6919 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6920 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6921 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6922 "const union u c = { { \"1234\", \"567\" } };\n",
6923 ),
6924 );
6925 let text = result.text();
6926 assert_eq!(
6927 result.messages,
6928 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6929 (5 chars into 3 available) [E0637]"]
6930 );
6931 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6932 assert!(
6933 text.contains(
6934 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6935 bytes \"9\\00\", zero 3 }"
6936 ),
6937 "{text}"
6938 );
6939 assert!(
6942 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6943 "{text}"
6944 );
6945 }
6946
6947 #[test]
6948 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6949 let text = body(concat!(
6953 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6954 "void g(struct v *);\n",
6955 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6956 ));
6957 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6958 }
6959
6960 #[test]
6961 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6962 let text = ir(concat!(
6967 "struct s { int x; };\n",
6968 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6969 "int n = (int){ 7 };\n",
6970 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6971 ));
6972 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6973 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6974 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6977 }
6978
6979 #[test]
6980 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6981 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6985 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6986 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6987 }
6988
6989 #[test]
6990 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6991 let text = ir("unsigned char foo[1][0];\n");
6995 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6996 }
6997
6998 #[test]
6999 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
7000 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
7003 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
7004 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
7005 }
7006
7007 #[test]
7008 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
7009 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
7013 assert!(
7014 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
7015 "{text}"
7016 );
7017 }
7018
7019 #[test]
7020 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
7021 let text = body(
7026 "\
7027struct s { int a, b; };
7028struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
7029",
7030 );
7031 assert!(text.contains("block3(%7: ptr)"), "{text}");
7033 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
7034 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
7035 }
7036
7037 #[test]
7045 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
7046 let text = body("int f(int i) { return ++i ?: 10; }\n");
7047 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
7048 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7049
7050 let text = body("long f(int i) { return ++i ?: 10L; }\n");
7053 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
7054 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7055
7056 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
7058 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
7059
7060 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
7063 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
7064 }
7065
7066 #[test]
7067 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
7068 let text = ir("\
7072struct pair { int a, b; };
7073struct pair make(int a, int b);
7074struct pair twice(struct pair p) { return make(p.a, p.b); }
7075");
7076 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
7077 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
7078 }
7079
7080 #[test]
7081 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7082 let text = ir("\
7086struct big { double v[8]; };
7087struct big grow(struct big b);
7088struct big twice(struct big b) { return grow(grow(b)); }
7089");
7090 assert!(
7091 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7092 "{text}"
7093 );
7094 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7095 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7098 }
7099
7100 #[test]
7101 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7102 let text = ir("\
7107struct big { double v[8]; };
7108struct pair { int a, b; };
7109int p(const char *, ...);
7110int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7111");
7112 assert!(
7113 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7114 "{text}"
7115 );
7116 }
7117
7118 #[test]
7119 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7120 let body = body(
7123 "\
7124struct pair { int a, b; };
7125struct pair make(int a, int b);
7126int second(void) { return make(1, 2).b; }
7127",
7128 );
7129 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7130 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7131 }
7132
7133 #[test]
7134 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7135 let source = "\
7139struct hfa { float x, y, z; };
7140int take(struct hfa h);
7141int give(struct hfa h) { return take(h); }
7142";
7143 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7144 let mut opts = options();
7145 opts.emit = EmitKind::Ir;
7146 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7147 let result = run(&opts, source);
7148 assert_eq!(result.messages, Vec::<String>::new());
7149 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7150 }
7151
7152 #[test]
7153 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7154 let source = "\
7157int use(int *);
7158void f(int n) {
7159 {
7160 int a[n];
7161 use(a);
7162 }
7163 use(0);
7164}
7165";
7166 let body = body(source);
7167 assert!(body.contains("mul.nsw"), "{body}");
7168 assert!(body.contains("stacksave"), "{body}");
7169 assert!(body.contains("alloca %"), "{body}");
7170 assert!(body.contains("stackrestore"), "{body}");
7171 }
7172
7173 #[test]
7174 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7175 let source = "\
7180int use(int *);
7181int f(int n) {
7182 {
7183 int a[n];
7184 if (use(a)) goto out;
7185 use(0);
7186 }
7187out:
7188 return 0;
7189}
7190";
7191 let body = body(source);
7192 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7194 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7195 assert!(after.starts_with(" %4\n jump block"), "{body}");
7196 }
7197
7198 #[test]
7199 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7200 let source = "\
7204int use(int *);
7205int f(int n) {
7206 int a[n];
7207again:
7208 if (use(a)) goto again;
7209 return 0;
7210}
7211";
7212 let body = body(source);
7213 assert!(body.contains("stacksave"), "{body}");
7214 assert!(!body.contains("stackrestore"), "{body}");
7215 }
7216
7217 #[test]
7218 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7219 let source = "\
7224int use(int *);
7225int f(int n) {
7226again:
7227 {
7228 int a[n];
7229 if (use(a)) goto again;
7230 }
7231 return 0;
7232}
7233";
7234 let body = body(source);
7235 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7236 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7237 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7238 }
7239
7240 #[test]
7241 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
7242 let source = "\
7248int f(void);
7249void t(void) {
7250 int count = 10;
7251 for (; count--;) {
7252 int b[f()];
7253 int i;
7254 for (i = 0; i < f(); i++) {
7255 b[i] = count;
7256 }
7257 }
7258}
7259";
7260 let body = body(source);
7261 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7265 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7266 let next = after.split("\n\n").next().expect("the block the restore is in");
7269 assert!(next.contains("jump block1("), "{body}");
7270 }
7271
7272 #[test]
7273 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
7274 let source = "\
7277unsigned long f(int n) {
7278 int a[n];
7279 n = 0;
7280 return sizeof a;
7281}
7282";
7283 let body = body(source);
7284 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
7286 }
7287
7288 #[test]
7289 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
7290 let source = "\
7293int use(int);
7294int f(int x) {
7295 return ({
7296 int t = use(x);
7297 t * t;
7298 });
7299}
7300";
7301 let expected = "\
7302block0(%0: i32):
7303 %1 = call @use(%0) : (i32) -> i32
7304 %2 = mul.nsw %1, %1
7305 return %2
7306";
7307 assert_eq!(body(source), expected);
7308 }
7309
7310 #[test]
7311 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
7312 let source = "int f(int x) { return ({ return x; 0; }); }\n";
7316 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
7317 }
7318
7319 #[test]
7320 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
7321 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
7325 let expected = "\
7326block0(%0: ptr):
7327 %1 = va_arg.f64 %0
7328 %2 = va_arg.f64 %0
7329 %3 = fadd %1, %2
7330 return %3
7331";
7332 assert_eq!(body(source), expected);
7333 }
7334
7335 #[test]
7336 fn one_that_reads_a_structure_answers_where_the_object_is() {
7337 let source = "\
7351struct s { int a; long b; };
7352long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
7353";
7354 let expected = "\
7355block0(%0: ptr):
7356 %1 = alloca, size 16, align 16
7357 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
7358 memcpy %1, %2, size 16, align 8
7359 %3 = iconst.i64 8
7360 %4 = ptr_add %1, %3
7361 %5 = load.i64 %4, align 8, tbaa !1
7362 return %5
7363";
7364 assert_eq!(body(source), expected);
7365 }
7366
7367 #[test]
7371 fn the_classification_says_which_registers_the_object_arrived_in() {
7372 let source = "\
7373struct s { double a; double b; };
7374double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
7375";
7376 assert!(
7377 body(source)
7378 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
7379 "{}",
7380 body(source)
7381 );
7382
7383 let big = "\
7384struct s { long a[4]; };
7385long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
7386";
7387 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
7388 }
7389
7390 #[test]
7391 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
7392 let source = "\
7396int f(int c) {
7397 void *p = c ? &&one : &&two;
7398 goto *p;
7399one:
7400 return 1;
7401two:
7402 return 2;
7403}
7404";
7405 let expected = "\
7406block0(%0: i32):
7407 %1 = iconst.i32 0
7408 %2 = icmp ne %0, %1
7409 br_if %2, block1, block2
7410
7411block1:
7412 %3 = block_addr block3
7413 jump block4(%3)
7414
7415block2:
7416 %4 = block_addr block5
7417 jump block4(%4)
7418
7419block3:
7420 %5 = iconst.i32 1
7421 return %5
7422
7423block4(%6: ptr):
7424 indirect_br %6, block3, block5
7425
7426block5:
7427 %7 = iconst.i32 2
7428 return %7
7429";
7430 assert_eq!(body(source), expected);
7431 }
7432
7433 #[test]
7434 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
7435 let source = "void **next(void);
7438void f(void) { goto *next(); }
7439";
7440 let expected = "\
7441block0:
7442 %0 = call @next() : () -> ptr
7443 unreachable
7444";
7445 assert_eq!(body(source), expected);
7446 }
7447
7448 #[test]
7449 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
7450 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
7453 let expected = "\
7454block0:
7455 inline_asm.volatile \"mfence\", \"\", \"memory\"()
7456 return
7457";
7458 assert_eq!(body(source), expected);
7459 }
7460
7461 #[test]
7462 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
7463 let source = "\
7466int f(int x, int y) {
7467 int r;
7468 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
7469 return r + y;
7470}
7471";
7472 let expected = "\
7473block0(%0: i32, %1: i32):
7474 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
7475 %4 = add.nsw %2, %3
7476 return %4
7477";
7478 assert_eq!(body(source), expected);
7479 }
7480
7481 #[test]
7482 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
7483 let source = "\
7488struct pair { int a, b; };
7489int f(int x) {
7490 int slot = x;
7491 struct pair p = { x, x };
7492 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7493 return slot + p.a;
7494}
7495";
7496 let text = body(source);
7497 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7498 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7499 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7500 }
7501
7502 #[test]
7503 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7504 let source = "\
7509int f(int x) {
7510 int r = 7;
7511 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7512 return r;
7513away:
7514 return r;
7515}
7516";
7517 let expected = "\
7518block0(%0: i32):
7519 %1 = iconst.i32 7
7520 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7521
7522block1:
7523 return %2
7524
7525block2:
7526 return %1
7527";
7528 assert_eq!(body(source), expected);
7529 }
7530
7531 #[test]
7532 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7533 let mut opts = options();
7537 opts.emit = EmitKind::Ir;
7538 for (source, expected) in [
7539 (
7540 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7541 "output operand constraint lacks '='",
7542 ),
7543 (
7544 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7545 "lvalue required in 'asm' statement",
7546 ),
7547 (
7548 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7549 "read-only variable 'g' used as 'asm' output",
7550 ),
7551 (
7552 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7553 "input operand constraint contains '='",
7554 ),
7555 (
7556 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7557 "memory input 0 is not directly addressable",
7558 ),
7559 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7560 (
7561 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7562 "duplicate asm operand name 'a'",
7563 ),
7564 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7565 ] {
7566 let result = run(&opts, source);
7567 assert!(result.failed(), "expected this to be reported:\n{source}");
7568 assert!(
7569 result.messages.iter().any(|m| m.contains(expected)),
7570 "{expected}\n{:?}",
7571 result.messages
7572 );
7573 }
7574 }
7575
7576 #[test]
7581 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7582 let text = ir(concat!(
7583 "__asm__(\n",
7584 " \".section .rodata\\n\"\n",
7585 " \".globl first\\n\"\n",
7586 " \".balign 8\\n\"\n",
7587 " \"first:\\n\"\n",
7588 " \".long 1\\n\"\n",
7589 " \".long 2\\n\"\n",
7590 " \".globl last\\n\"\n",
7591 " \"last:\\n\"\n",
7592 " \".quad last - first\\n\");\n",
7593 "extern const int first[];\n",
7594 "extern const long last;\n",
7595 ));
7596 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7597 assert!(text.contains("global @last : i64 = 8"), "{text}");
7598 }
7599
7600 #[test]
7604 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7605 let text = ir(concat!(
7606 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7607 "extern int counter;\n",
7608 "int read(void) { return counter; }\n",
7609 ));
7610 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7611 }
7612
7613 #[test]
7616 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7617 let mut opts = options();
7618 opts.emit = EmitKind::Ir;
7619 let mut fs = MemoryFileSystem::new();
7620 fs.insert(
7621 "/main.c",
7622 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7623 );
7624 fs.insert("seed", b"hi".to_vec());
7625 let result = compile(&opts, "/main.c", &fs);
7626 assert_eq!(result.messages, Vec::<String>::new());
7627 let text = result.text();
7628 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7629 }
7630
7631 #[test]
7634 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7635 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7636 assert!(
7637 messages
7638 .iter()
7639 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7640 "{messages:?}"
7641 );
7642 }
7643
7644 #[test]
7647 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7648 for source in [
7649 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7650 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7651 ] {
7652 let messages = errors(source);
7653 assert!(
7654 messages
7655 .iter()
7656 .any(|m| m.contains("not supported yet")
7657 && m.contains("in an `asm` at file scope")),
7658 "{source}\n{messages:?}"
7659 );
7660 }
7661 }
7662
7663 #[test]
7664 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7665 let mut opts = options();
7666 opts.emit = EmitKind::Ir;
7667 for source in [
7668 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7669 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7670 ] {
7671 let result = run(&opts, source);
7672 assert!(result.failed(), "expected this to be reported:\n{source}");
7673 assert!(
7674 result.messages.iter().any(|m| m.contains("not supported yet")),
7675 "{:?}",
7676 result.messages
7677 );
7678 }
7679 }
7680
7681 fn round_trip(source: &str) -> (String, String) {
7683 let printed = ir(source);
7684 let mut opts = options();
7685 opts.emit = EmitKind::Ir;
7686 let mut fs = MemoryFileSystem::new();
7687 fs.insert("/main.ir", printed.clone().into_bytes());
7688 let result = compile_ir(&opts, "/main.ir", &fs);
7689 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7690 (printed, result.text().to_owned())
7691 }
7692
7693 #[test]
7694 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7695 let (printed, again) = round_trip(
7699 "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",
7700 );
7701 assert_eq!(printed, again);
7702 }
7703
7704 #[test]
7705 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7706 let mut opts = options();
7707 opts.emit = EmitKind::Ir;
7708 let mut fs = MemoryFileSystem::new();
7709 let text = "\
7710; ModuleID = 'a.c'
7711; format 0
7712target triple = \"x86_64-unknown-linux-gnu\"
7713target datalayout = \"e-p:64:64-i64:64-S128\"
7714
7715func @f(), linkage(external) {
7716block0:
7717 frobnicate
7718}
7719";
7720 fs.insert("/main.ir", text.as_bytes().to_vec());
7721 let result = compile_ir(&opts, "/main.ir", &fs);
7722 assert!(result.failed());
7723 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7724 }
7725
7726 #[test]
7727 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7728 let mut opts = options();
7731 opts.emit = EmitKind::Ir;
7732 let mut fs = MemoryFileSystem::new();
7733 let text = "\
7734; ModuleID = 'a.c'
7735; format 0
7736target triple = \"x86_64-unknown-linux-gnu\"
7737target datalayout = \"e-p:64:64-i64:64-S128\"
7738
7739func @f(), linkage(external) {
7740block0:
7741 %0 = iconst.i32 1
7742 return %0
7743}
7744";
7745 fs.insert("/main.ir", text.as_bytes().to_vec());
7746 let result = compile_ir(&opts, "/main.ir", &fs);
7747 assert!(result.failed());
7748 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7749 }
7750
7751 #[test]
7752 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7753 let mut fs = MemoryFileSystem::new();
7755 fs.insert("/main.ir", Vec::new());
7756 let result = compile_ir(&options(), "/main.ir", &fs);
7757 assert!(result.failed());
7758 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7759 }
7760
7761 #[test]
7762 fn the_printed_ir_reads_back_as_the_same_module() {
7763 let text = ir("\
7766struct point { int x, y; };
7767static const char greeting[] = \"hi\";
7768int table[4] = { 1, 2, 3 };
7769int puts(const char *);
7770double half(double x) { return x / 2.0; }
7771int f(int n) {
7772 int total = 0;
7773 for (int i = 0; i < n; i++) {
7774 if (i == 3) continue;
7775 total += table[i];
7776 }
7777 switch (n) {
7778 case 0: total = 1;
7779 case 1: total++; break;
7780 default: total = -total;
7781 }
7782 struct point p = { total, 1 };
7783 int *q = &p.y;
7784 puts(greeting);
7785 return p.x + *q;
7786}
7787int dispatch(int c) {
7788 void *p = c ? &&one : &&two;
7789 goto *p;
7790one:
7791 return 1;
7792two:
7793 return 2;
7794}
7795int assembly(int x, int *p) {
7796 int r;
7797 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7798 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7799 return r;
7800away:
7801 return 0;
7802}
7803");
7804 let mut names = Interner::new();
7805 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7806 assert_eq!(rucc_ir::print(&module, &names), text);
7807 }
7808
7809 #[test]
7810 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7811 let mut opts = options();
7815 opts.emit = EmitKind::Object;
7816 opts.save_temps = rucc_session::SaveTemps::Object;
7817 let result = run(&opts, "#define N 2\nint a[N];\n");
7818 assert_eq!(result.messages, Vec::<String>::new());
7819 let text = result.temps.preprocessed.expect("the preprocessed text");
7820 assert!(text.contains("int a[2];"), "{text}");
7821 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7822 let asm = result.temps.assembly.expect("the assembly");
7823 assert!(asm.contains("a:"), "{asm}");
7824 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7825 }
7826
7827 #[test]
7828 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7829 let mut opts = options();
7832 opts.emit = EmitKind::Object;
7833 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7834 }
7835
7836 #[test]
7837 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7838 let mut opts = options();
7841 opts.emit = EmitKind::Ir;
7842 opts.save_temps = rucc_session::SaveTemps::Cwd;
7843 let result = run(&opts, "int a;\n");
7844 assert!(result.temps.preprocessed.is_some());
7845 assert_eq!(result.temps.assembly, None);
7846 }
7847}