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(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 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 !opts.warnings && diag.severity == Severity::Warning {
453 continue;
454 }
455 if diag.severity.is_fatal()
456 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
457 {
458 errors += 1;
459 }
460 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
461 }
462 if errors > 0 {
463 artifact = Artifact::Nothing;
465 }
466 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
469}
470
471#[must_use]
481pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
482 let mut sess = Session::new(opts.clone());
483 if opts.emit != EmitKind::Ir {
484 return failure(format!(
485 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
486 the C in front of it became",
487 opts.emit.as_str()
488 ));
489 }
490 let bytes = match fs.read(Path::new(name)) {
491 Ok(bytes) => bytes,
492 Err(e) => return failure(format!("{name}: {e}")),
493 };
494 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
495 return failure(format!("{name}: this is not text, so it is not IR"));
496 };
497
498 let module = match rucc_ir::parse(text, &mut sess.interner) {
499 Ok(module) => module,
500 Err(error) => {
501 return failure(format!("{name}:{}: {}", error.line, error.message));
502 }
503 };
504 let mut diagnostics: Vec<Diagnostic> = Vec::new();
505 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
506 for error in errors {
507 diagnostics.push(invalid(&format!("invalid IR, {error}")));
508 }
509 }
510 let mut messages = Vec::with_capacity(diagnostics.len());
511 for diag in &diagnostics {
512 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
513 }
514 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
515 let artifact = if errors > 0 {
516 Artifact::Nothing
517 } else {
518 Artifact::Text(rucc_ir::print(&module, &sess.interner))
519 };
520 Compiled {
522 artifact,
523 messages,
524 errors,
525 fired: Fired::new(),
526 pressure: Pressure::new(),
527 lowerings: Lowerings::new(),
528 dumps: Vec::new(),
529 remarks: String::new(),
530 deps: Vec::new(),
531 temps: Temps::default(),
532 }
533}
534
535fn instrument(
558 module: &mut rucc_ir::Module,
559 names: &mut Interner,
560 opts: &Options,
561) -> Result<Instrumented, Vec<Diagnostic>> {
562 if !opts.safety.instruments() {
563 return Ok(Instrumented::default());
564 }
565 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
566 checks.freed = rucc_safety::ending::checks(module, names);
574 let interposed = rucc_safety::redirect(module, names);
579 let crossings = rucc_safety::witness(module, names);
582 match rucc_ir::verify(module, names) {
583 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
584 Err(errors) => Err(errors
585 .iter()
586 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
587 .collect()),
588 }
589}
590
591#[derive(Clone, Copy, Debug, Default)]
597struct Instrumented {
598 checks: rucc_safety::Counts,
600 interposed: usize,
602 crossings: rucc_safety::Sites,
604}
605
606fn optimize(
618 module: &mut rucc_ir::Module,
619 names: &Interner,
620 target: &TargetInfo,
621 opts: &Options,
622 file: &str,
623 dumps: &mut Vec<rucc_opt::Dump>,
624 remarks: &mut String,
625) -> Result<(), Vec<Diagnostic>> {
626 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
627 settings.interposition = match opts.interposition {
633 true => replaceable(target, opts),
634 false => IrPic::Executable,
635 };
636 settings.toggles.clone_from(&opts.passes);
637 settings.fuel = opts.pass_fuel.iter().cloned().collect();
638 settings.global_fuel = opts.pass_fuel_global;
639 settings.verify |= opts.verify_each;
640 for (on, spec) in &opts.pass_gates {
641 if let Err(why) = settings.gates.add(*on, spec) {
644 return Err(vec![internal(&why)]);
645 }
646 }
647 for spec in &opts.dump_ir {
648 if let Err(why) = settings.dumps.add(spec) {
651 return Err(vec![internal(&why)]);
652 }
653 }
654 let mut wants = rucc_opt::Wants::none();
655 for spec in &opts.opt_info {
656 if let Err(why) = wants.add(spec) {
659 return Err(vec![internal(&why)]);
660 }
661 }
662 let report = rucc_opt::run(module, names, &settings);
663 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
664 dumps.extend(report.dumps);
665 match report.broke.is_empty() {
666 true => Ok(()),
667 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
668 }
669}
670
671fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
705 match (target.tuple.os().object_format(), opts.pic) {
706 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
707 _ => IrPic::Executable,
708 }
709}
710
711fn generate(
712 module: &mut rucc_ir::Module,
713 names: &mut Interner,
714 target: &TargetInfo,
715 opts: &Options,
716 recording: &mut Recording<'_>,
717 assembly: &mut Option<String>,
718) -> Result<Artifact, Vec<Diagnostic>> {
719 let Some(machine) = Machine::for_target(target) else {
720 return Err(vec![unsupported(&format!(
721 "there is no back end for {} in this compiler yet, so there is nothing to generate",
722 target.tuple
723 ))]);
724 };
725 if opts.protector != Protector::None && machine.conv.guard.is_none() {
730 return Err(vec![unsupported(&format!(
731 "{} is not supported for {} yet, because the stack protector on that target is not \
732 the one this compiler writes",
733 opts.protector, target.tuple
734 ))]);
735 }
736 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
742 return Err(vec![unsupported(&format!(
743 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
744 for it there is not the note this compiler writes",
745 opts.control, target.tuple
746 ))]);
747 }
748 let profile = match machine.conv.trace {
754 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
755 None if opts.profile => {
756 return Err(vec![unsupported(&format!(
757 "-pg is not supported for {} yet, because the profiler's hook on that target is \
758 not the one this compiler calls",
759 target.tuple
760 ))]);
761 }
762 None => None,
763 };
764 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
769 return Err(vec![unsupported(&format!(
770 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
771 the room is there is not the section this compiler writes",
772 target.tuple
773 ))]);
774 }
775 let flags = pipeline::Flags {
776 frame_pointer: opts.frame_pointer,
777 red_zone: opts.red_zone,
778 stack_clash: opts.stack_clash,
779 landing: opts.control.branch(),
780 profile: match profile {
781 None => pipeline::Profile::No,
782 Some(true) => pipeline::Profile::Early,
783 Some(false) => pipeline::Profile::Late,
784 },
785 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
786 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
793 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
798 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
803 accurate: opts.cycle_accurate_model,
805 verify: opts.verify_each,
808 goal: Goal::for_size(opts.opt_level.is_size()),
813 };
814
815 if opts.safety.instruments() {
824 rucc_opt::heap::annotate(module, names);
834 rucc_safety::handover::arrange(module);
841 rucc_safety::lower(module, names);
842 if let Err(errors) = rucc_ir::verify(module, names) {
843 return Err(errors
844 .iter()
845 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
846 .collect());
847 }
848 }
849
850 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
859
860 let mut funcs = Vec::new();
861 let mut complaints = Vec::new();
862 for id in module.funcs() {
863 if module[id].is_declaration() {
864 continue;
865 }
866 match pipeline::compile_recording(
867 &mut module[id],
868 names,
869 &machine,
870 &elsewhere,
871 flags,
872 recording,
873 ) {
874 Ok(func) => funcs.push(func),
875 Err(why) => {
876 let name = names.resolve(module[id].name).to_owned();
877 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
880 let said = format!("cannot generate code for '{name}': {why}");
881 complaints.push(unsupported_at(&said, span));
882 }
883 }
884 }
885 if !complaints.is_empty() {
886 return Err(complaints);
887 }
888 let (globals, aliases) = match opts.emit {
894 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
895 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
896 rucc_asm::aliases(module, names).map_err(refused)?,
897 ),
898 _ => (rucc_asm::Globals::default(), Vec::new()),
899 };
900 let unwind = opts.unwinds();
904 match opts.emit {
905 EmitKind::Asm => {
906 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
907 .map(Artifact::Text)
908 .map_err(refused)
909 }
910 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
914 if opts.save_temps.wanted() {
915 let listing = rucc_asm::print(
916 &funcs,
917 &globals,
918 &aliases,
919 names,
920 target,
921 unwind,
922 output(opts, target),
923 );
924 *assembly = Some(listing.map_err(refused)?);
925 }
926 let text = rucc_asm::assemble(&funcs, names, target, unwind).map_err(refused)?;
927 let data = globals.image();
928 let bytes = rucc_object::write(&text, &data, &aliases, target, output(opts, target))
931 .map_err(wrote)?;
932 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
937 Ok(Artifact::Object { bytes, defines })
938 }
939 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
940 }
941}
942
943fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
955 let mut features = 0;
956 if target.tuple.arch() == Arch::X86_64 {
957 if opts.control.branch() {
958 features |= rucc_object::Property::IBT;
959 }
960 if opts.control.ret() {
961 features |= rucc_object::Property::SHSTK;
962 }
963 }
964 rucc_object::Output {
965 sections: rucc_object::Sections {
966 functions: opts.function_sections,
967 data: opts.data_sections,
968 },
969 property: rucc_object::Property { features },
970 }
971}
972
973fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
979 match why {
980 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
981 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
982 }
983}
984
985fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
992 match why {
993 rucc_asm::Error::Thread { .. }
994 | rucc_asm::Error::IFunc { .. }
995 | rucc_asm::Error::Frame { .. } => {
996 vec![unsupported(&why.to_string())]
997 }
998 _ => vec![internal(&why.to_string())],
999 }
1000}
1001
1002fn unsupported(message: &str) -> Diagnostic {
1008 unsupported_at(message, Span::DUMMY)
1009}
1010
1011fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1017 Diagnostic::error(message.to_owned(), span)
1018 .with_code("E0653")
1019 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1020}
1021
1022fn invalid(message: &str) -> Diagnostic {
1024 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1025}
1026
1027fn internal(message: &str) -> Diagnostic {
1029 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1030 .with_code("E0652")
1031 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1032}
1033
1034fn failure(message: String) -> Compiled {
1037 Compiled {
1038 artifact: Artifact::Nothing,
1039 messages: vec![format!("rucc: error: {message}")],
1040 errors: 1,
1041 fired: Fired::new(),
1042 pressure: Pressure::new(),
1043 lowerings: Lowerings::new(),
1044 dumps: Vec::new(),
1045 remarks: String::new(),
1046 deps: Vec::new(),
1047 temps: Temps::default(),
1048 }
1049}
1050
1051#[cfg(test)]
1052mod tests {
1053 use rucc_session::{MemoryFileSystem, Std};
1054 use rucc_target::Triple;
1055
1056 use super::*;
1057
1058 fn options() -> Options {
1059 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1060 opts.emit = EmitKind::Tast;
1061 opts
1062 }
1063
1064 fn run(opts: &Options, source: &str) -> Compiled {
1065 let mut fs = MemoryFileSystem::new();
1066 fs.insert("/main.c", source.to_owned().into_bytes());
1067 compile(opts, "/main.c", &fs)
1068 }
1069
1070 fn freestanding() -> Options {
1074 let mut opts = options();
1075 opts.hosted = false;
1076 opts.search.push_system(rucc_session::runtime::DIR);
1077 opts
1078 }
1079
1080 fn shipped(source: &str) -> String {
1082 let result = run(&freestanding(), source);
1083 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1084 result.text().to_owned()
1085 }
1086
1087 fn tast(source: &str) -> String {
1089 let result = run(&options(), source);
1090 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1091 result.text().to_owned()
1092 }
1093
1094 #[test]
1095 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1096 let text = shipped(concat!(
1097 "#include <stdarg.h>\n",
1098 "int sum(int n, ...) {\n",
1099 " va_list ap, copy;\n",
1100 " va_start(ap, n);\n",
1101 " va_copy(copy, ap);\n",
1102 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1103 " va_end(ap);\n",
1104 " va_end(copy);\n",
1105 " return total;\n",
1106 "}\n",
1107 ));
1108 assert!(text.contains("va-start"), "{text}");
1109 assert!(text.contains("va-copy"), "{text}");
1110 assert!(text.contains("va-arg"), "{text}");
1111 assert!(text.contains("va-end"), "{text}");
1112 }
1113
1114 #[test]
1118 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1119 let text = shipped(concat!(
1120 "#define __need___va_list\n",
1121 "#include <stdarg.h>\n",
1122 "int vprint(const char *f, __gnuc_va_list ap);\n",
1123 "#ifdef va_start\n",
1124 "#error va_start should not be defined\n",
1125 "#endif\n",
1126 "#ifdef _VA_LIST_DEFINED\n",
1127 "#error va_list should not have been made\n",
1128 "#endif\n",
1129 ));
1130 assert!(text.contains("vprint"), "{text}");
1131 }
1132
1133 #[test]
1136 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1137 let text = shipped(concat!(
1138 "#define __need_size_t\n",
1139 "#include <stddef.h>\n",
1140 "#ifdef offsetof\n",
1141 "#error offsetof should not be defined yet\n",
1142 "#endif\n",
1143 "#define __need_ptrdiff_t\n",
1144 "#include <stddef.h>\n",
1145 "#include <stddef.h>\n",
1146 "size_t a;\n",
1147 "ptrdiff_t b;\n",
1148 "wchar_t c;\n",
1149 "max_align_t d;\n",
1150 "void *e = NULL;\n",
1151 "struct P { int x; long y; };\n",
1152 "size_t f = offsetof(struct P, y);\n",
1153 ));
1154 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1155 assert!(text.contains("decl #1 b : long"), "{text}");
1156 }
1157
1158 #[test]
1159 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1160 let text = shipped(concat!(
1161 "#include <limits.h>\n",
1162 "#include <float.h>\n",
1163 "int bits = CHAR_BIT;\n",
1164 "long big = LONG_MAX;\n",
1165 "int low = INT_MIN;\n",
1166 "int radix = FLT_RADIX;\n",
1167 "int digits = DBL_MANT_DIG;\n",
1168 ));
1169 assert!(text.contains("const 8 : int"), "{text}");
1170 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1171 assert!(text.contains("const 2 : int"), "{text}");
1172 assert!(text.contains("const 53 : int"), "{text}");
1173 }
1174
1175 #[test]
1179 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1180 let text = shipped(concat!(
1181 "#include <stdint.h>\n",
1182 "int64_t a = INT64_C(1);\n",
1183 "uint_least16_t b;\n",
1184 "intptr_t c;\n",
1185 "uintmax_t d = UINTMAX_MAX;\n",
1186 "int wide = sizeof(int_fast64_t);\n",
1187 ));
1188 assert!(text.contains("decl #0 a : long"), "{text}");
1189 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1190 assert!(text.contains("decl #2 c : long"), "{text}");
1191 }
1192
1193 #[test]
1204 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1205 let text = shipped(concat!(
1206 "#include <mmintrin.h>\n",
1207 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1208 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1209 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1210 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1211 "void done(void) { _mm_empty(); }\n",
1212 ));
1213 assert!(text.contains("add"), "{text}");
1214 assert!(text.contains("pack"), "{text}");
1215 assert!(text.contains("shift"), "{text}");
1216 }
1217
1218 #[test]
1223 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1224 let text = shipped(concat!(
1225 "#include <mm_malloc.h>\n",
1226 "void *get(void) { return _mm_malloc(64, 16); }\n",
1227 "void put(void *p) { _mm_free(p); }\n",
1228 ));
1229 assert!(text.contains("get"), "{text}");
1230 assert!(text.contains("put"), "{text}");
1231 }
1232
1233 #[test]
1245 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1246 let text = shipped(concat!(
1247 "#include <xmmintrin.h>\n",
1248 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1249 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1250 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1251 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1252 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1253 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1254 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1255 "void *room(void) { return _mm_malloc(64, 16); }\n",
1256 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1257 ));
1258 assert!(text.contains("add"), "{text}");
1259 assert!(text.contains("mask"), "{text}");
1260 assert!(text.contains("pick"), "{text}");
1261 assert!(text.contains("wide"), "{text}");
1262 }
1263
1264 #[test]
1271 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1272 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1273 for absent in [
1274 "_mm_sqrt_ps",
1275 "_mm_sqrt_ss",
1276 "_mm_rsqrt_ps",
1277 "_mm_rsqrt_ss",
1278 "_mm_getcsr",
1279 "_mm_setcsr",
1280 ] {
1281 let defined = text.contains(&format!("{absent}("));
1282 assert!(!defined, "{absent} is defined and the header says it is not");
1283 assert!(text.contains(absent), "{absent} is absent and unexplained");
1284 }
1285 }
1286
1287 #[test]
1288 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1289 let text = shipped(concat!(
1290 "#include <emmintrin.h>\n",
1291 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1292 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1293 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1294 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1295 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1296 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1297 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1298 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1299 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1300 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1301 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1302 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1303 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1304 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1305 ));
1306 assert!(text.contains("wide"), "{text}");
1307 assert!(text.contains("pack"), "{text}");
1308 assert!(text.contains("near"), "{text}");
1309 assert!(text.contains("half"), "{text}");
1310 }
1311
1312 #[test]
1316 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1317 let text = shipped(concat!(
1318 "#include <immintrin.h>\n",
1319 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1320 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1321 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1322 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1323 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1324 "}\n",
1325 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1326 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1327 ));
1328 assert!(text.contains("matching"), "{text}");
1329 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1330 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1331 }
1332
1333 #[test]
1337 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1338 let text = shipped(concat!(
1339 "#include <x86intrin.h>\n",
1340 "void barriers(void *p) {\n",
1341 " _mm_lfence();\n",
1342 " _mm_sfence();\n",
1343 " _mm_mfence();\n",
1344 " _mm_pause();\n",
1345 " _mm_clflush(p);\n",
1346 "}\n",
1347 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1348 ));
1349 assert!(text.contains("barriers"), "{text}");
1350 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1351 }
1352
1353 #[test]
1357 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1358 let text = shipped(concat!(
1359 "#include <immintrin.h>\n",
1360 "#include <emmintrin.h>\n",
1361 "#include <immintrin.h>\n",
1362 "#include <x86intrin.h>\n",
1363 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1364 ));
1365 assert!(text.contains("twice"), "{text}");
1366 }
1367
1368 #[test]
1372 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1373 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1374 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1375 let defined = text.contains(&format!("{absent}("));
1376 assert!(!defined, "{absent} is defined and the header says it is not");
1377 assert!(text.contains(absent), "{absent} is absent and unexplained");
1378 }
1379 }
1380
1381 #[test]
1382 fn the_three_formality_headers_still_have_to_work() {
1383 let text = shipped(concat!(
1384 "#include <stdbool.h>\n",
1385 "#include <stdalign.h>\n",
1386 "#include <iso646.h>\n",
1387 "#include <stdnoreturn.h>\n",
1388 "int t = true and not false;\n",
1389 "_Alignas(16) char buf[16];\n",
1390 "int a = alignof(long);\n",
1391 ));
1392 assert!(text.contains("decl #0 t : int"), "{text}");
1393 assert!(text.contains("const 8 : unsigned long"), "{text}");
1394 }
1395
1396 #[test]
1404 fn every_shipped_header_can_be_included_twice() {
1405 let once: String = rucc_session::runtime::names()
1406 .iter()
1407 .map(|name| format!("#include <{name}>\n"))
1408 .collect();
1409 let twice = once.repeat(2);
1410 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1411 }
1412
1413 #[test]
1414 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1415 let fs = MemoryFileSystem::new();
1416 let result = compile(&options(), "/nope.c", &fs);
1417 assert!(result.failed());
1418 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1419 assert!(result.text().is_empty());
1420 }
1421
1422 #[test]
1423 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1424 let text = tast("int x = 1;\n");
1425 let expected = "\
1426decl #0 x : int object external static defined
1427 init
1428 +0
1429 const 1 : int
1430";
1431 assert_eq!(text, expected);
1432 }
1433
1434 #[test]
1435 fn the_macros_are_expanded_before_anything_is_parsed() {
1436 let text = tast("#define N 2\nint a[N];\n");
1440 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1441 }
1442
1443 #[test]
1449 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1450 let text = tast(concat!(
1451 "#pragma pack(4)\n",
1452 "struct s { int a; };\n",
1453 "#pragma pack()\n",
1454 "int b;\n",
1455 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1456 ));
1457 assert!(text.contains("decl #0 b : int"), "{text}");
1458 assert!(text.contains("decl #1 c : int"), "{text}");
1459 }
1460
1461 #[test]
1469 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1470 tast(concat!(
1471 "struct A { char c; int i; } __attribute__((packed));\n",
1472 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1473 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1474 "struct B { char c; int i; } __attribute__((aligned));\n",
1477 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1478 "struct C { char c; int i __attribute__((packed)); };\n",
1479 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1480 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1481 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1482 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1483 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1484 "struct E { char c; _Alignas(8) int i; };\n",
1485 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1486 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1487 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1488 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1489 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1492 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1493 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1494 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1495 "struct I { [[gnu::packed]] char c; int i; };\n",
1498 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1499 "struct J { char c; [[gnu::packed]] int i; };\n",
1500 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1501 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1502 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1503 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1504 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1505 "union L { char c; int i; } __attribute__((packed));\n",
1506 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1507 "struct O { char c; int i; } __attribute__((__packed__));\n",
1511 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
1512 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
1513 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
1514 ));
1515 }
1516
1517 #[test]
1530 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
1531 let text = tast(concat!(
1532 "struct one { int x; };\n",
1533 "struct two { long y; };\n",
1534 "typedef union { struct one *a; struct two *b; void *any; }\n",
1535 " __attribute__((__transparent_union__)) arg;\n",
1536 "int takes(arg v);\n",
1537 "int f(struct one *p, struct two *q, char *c) {\n",
1538 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
1539 "}\n",
1540 "int takes(struct one *p);\n",
1542 "int (*as_a_member)(struct one *) = takes;\n",
1543 "int (*as_the_union)(arg) = takes;\n",
1544 ));
1545 assert!(text.contains("compound-literal"), "{text}");
1546 }
1547
1548 #[test]
1554 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
1555 let text = tast(concat!(
1556 "struct sockaddr { int family; };\n",
1557 "struct sockaddr_in { int family; int addr; };\n",
1558 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
1559 " addr_arg __attribute__((__transparent_union__));\n",
1560 "int bind_to(int fd, addr_arg where);\n",
1561 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
1562 ));
1563 assert!(text.contains("compound-literal"), "{text}");
1564 }
1565
1566 #[test]
1574 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
1575 let result = run(
1576 &options(),
1577 concat!(
1578 "union wider { int small; double large; } __attribute__((transparent_union));\n",
1579 "struct plain { int x; } __attribute__((transparent_union));\n",
1580 ),
1581 );
1582 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
1583 assert!(!result.failed(), "{:?}", result.messages);
1584 for message in &result.messages {
1585 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
1586 }
1587 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
1588 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
1589 }
1590
1591 #[test]
1600 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
1601 let packed = body(concat!(
1602 "struct P { char c; int v; } __attribute__((packed));\n",
1603 "int f(struct P *p) { return p->v; }\n",
1604 ));
1605 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
1606 let plain = body(concat!(
1608 "struct P { char c; int v; };\n",
1609 "int f(struct P *p) { return p->v; }\n",
1610 ));
1611 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
1612 }
1613
1614 #[test]
1621 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
1622 let stepped = body(concat!(
1623 "struct P { char c; int v[4]; } __attribute__((packed));\n",
1624 "int f(struct P *p, int i) { return p->v[i]; }\n",
1625 ));
1626 assert!(stepped.contains(", align 1,"), "{stepped}");
1627 assert!(!stepped.contains(", align 4,"), "{stepped}");
1628 let nested = body(concat!(
1629 "struct Inner { int v; };\n",
1630 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
1631 "int f(struct P *p) { return p->in.v; }\n",
1632 ));
1633 assert!(nested.contains(", align 1,"), "{nested}");
1634 assert!(!nested.contains(", align 4,"), "{nested}");
1635 }
1636
1637 #[test]
1653 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
1654 let through = body(concat!(
1655 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1656 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
1657 ));
1658 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
1659 let stepped = body(concat!(
1662 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1663 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
1664 ));
1665 assert!(stepped.contains(", align 1,"), "{stepped}");
1666 assert!(!stepped.contains(", align 4,"), "{stepped}");
1667 let plain = body(concat!(
1670 "typedef unsigned int word;\n",
1671 "unsigned int f(const void *p) { return *(const word *)p; }\n",
1672 ));
1673 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
1674 }
1675
1676 #[test]
1687 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
1688 let prefix = concat!(
1689 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
1690 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
1691 );
1692 let loaded =
1693 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
1694 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
1695 assert!(!loaded.contains("align 16"), "{loaded}");
1696 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
1699 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
1700 let aligned =
1702 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
1703 assert!(aligned.contains("align 16"), "{aligned}");
1704 }
1705
1706 #[test]
1715 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
1716 tast(concat!(
1717 "int v __attribute__((aligned(64)));\n",
1718 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
1719 "__attribute__((aligned(32))) int w;\n",
1722 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
1723 "[[gnu::aligned(16)]] int x;\n",
1724 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
1725 "int y __attribute__((aligned(2)));\n",
1728 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
1729 "void f(void) { int a __attribute__((aligned(128)));\n",
1731 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
1732 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1735 "void g(void) __attribute__((aligned(256)));\n",
1738 "void g(void) {}\n",
1739 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
1740 ));
1741 }
1742
1743 #[test]
1747 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
1748 let text = asm(concat!(
1749 "int v __attribute__((aligned(64)));\n",
1750 "void g(void) __attribute__((aligned(256)));\n",
1751 "void g(void) {}\n",
1752 "void plain(void) {}\n",
1753 ));
1754 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
1755 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1756 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
1757 }
1758
1759 #[test]
1765 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
1766 let source = concat!(
1767 "void g(void) __attribute__((aligned(256)));\n",
1768 "void g(void) {}\n",
1769 "void small(void) __attribute__((aligned(4)));\n",
1770 "void small(void) {}\n",
1771 "void plain(void) {}\n",
1772 );
1773 let listing = |align: Option<u32>| {
1774 let mut opts = options();
1775 opts.emit = EmitKind::Asm;
1776 opts.align_functions = align;
1777 let result = run(&opts, source);
1778 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
1779 result.text().to_owned()
1780 };
1781
1782 let text = listing(Some(32));
1783 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
1784 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
1785 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
1786
1787 let text = listing(Some(8));
1790 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
1791 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1792 }
1793
1794 #[test]
1803 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
1804 tast(concat!(
1805 "typedef int L __attribute__((aligned(2)));\n",
1806 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
1807 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
1808 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
1810 "struct T { char c; L x; };\n",
1811 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
1812 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
1813 "typedef int H __attribute__((aligned(16)));\n",
1815 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
1816 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
1817 "struct U { char c; H x; };\n",
1818 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
1819 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
1820 "typedef L M __attribute__((aligned(8)));\n",
1823 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
1824 "typedef L N;\n",
1827 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
1828 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1830 ));
1831 let text = asm(concat!(
1832 "typedef int L __attribute__((aligned(2)));\n",
1833 "typedef int H __attribute__((aligned(16)));\n",
1834 "L low;\n",
1835 "H high;\n",
1836 ));
1837 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
1838 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
1839 }
1840
1841 #[test]
1849 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
1850 tast(concat!(
1851 "typedef int __attribute__((vector_size(16))) v4si;\n",
1852 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
1853 "typedef char __attribute__((vector_size(16))) v16qi;\n",
1854 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
1855 "typedef int __attribute__((vector_size(4))) v1si;\n",
1858 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
1859 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
1861 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
1862 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
1863 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
1864 "v4si g;\n",
1867 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
1868 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
1869 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
1872 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
1874 ));
1875 }
1876
1877 #[test]
1887 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
1888 tast(concat!(
1889 "typedef int __attribute__((vector_size(8))) v2si;\n",
1890 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
1891 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
1892 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
1894 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
1895 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
1898 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
1899 ));
1900 }
1901
1902 #[test]
1910 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
1911 let result = run(
1912 &options(),
1913 concat!(
1914 "typedef int __attribute__((vector_size(16))) v4si;\n",
1915 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
1916 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
1917 " v4si v = { 1, 2, 3, 4 };\n",
1918 " v[0] = n;\n",
1919 " v[1] += n;\n",
1920 " v[2]++;\n",
1921 " *&v[3] = n;\n",
1922 " v4ui shifted = a >> b;\n",
1924 " shifted <<= b;\n",
1925 " *out = v + (v4si)shifted + (1 << b);\n",
1928 "}\n",
1929 "void refused(const v4si c) {\n",
1932 " c[0] = 1;\n",
1933 "}\n",
1934 ),
1935 );
1936 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
1937 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
1938 }
1939
1940 #[test]
1947 fn a_record_that_asks_for_the_other_byte_order_is_refused_rather_than_laid_out_in_this_one() {
1948 let opts = options();
1949 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
1950 assert_eq!(
1951 run(&opts, big).messages,
1952 ["/main.c:1:36: error: 'scalar_storage_order' is not implemented yet [E0688]\n\
1953 /main.c:1:36: note: every scalar in this record would be read in the wrong byte \
1954 order"]
1955 );
1956
1957 let armoured =
1958 "struct s { int i; } __attribute__((__scalar_storage_order__(\"little-endian\")));\n";
1959 let messages = run(&opts, armoured).messages;
1960 assert!(messages[0].contains("[E0688]"), "{messages:?}");
1961
1962 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
1965 assert!(run(&opts, front).messages[0].contains("[E0688]"), "{front}");
1966 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
1967 assert!(run(&opts, standard).messages[0].contains("[E0688]"), "{standard}");
1968 }
1969
1970 #[test]
1980 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
1981 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
1983 assert_eq!(
1984 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
1985 1
1986 );
1987 assert_eq!(
1988 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
1989 1
1990 );
1991 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
1992 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
1994 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
1995 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
1997 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
1998 }
1999
2000 fn bit_field_byte(record: &str) -> u64 {
2002 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2003 let body = body(&source);
2004 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2005 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2006 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2007 }
2008
2009 #[test]
2015 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2016 tast(concat!(
2017 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2018 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2019 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2020 "struct b { char c; __attribute__((packed)) int i; };\n",
2021 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2022 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2023 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2024 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2025 ));
2026 }
2027
2028 #[test]
2034 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2035 tast(concat!(
2036 "#pragma pack(1)\n",
2037 "struct A { char c; int i; };\n",
2038 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2039 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2040 "#pragma pack()\n",
2041 "struct B { char c; int i; };\n",
2042 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2043 "#pragma pack(2)\n",
2044 "struct C { char c; int i; double d; };\n",
2045 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2046 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2047 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2049 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2050 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2051 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2053 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2054 "#pragma pack()\n",
2055 "#pragma pack(push, 1)\n",
2056 "struct D { char c; short s; };\n",
2057 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2058 "#pragma pack(pop)\n",
2059 "struct E { char c; short s; };\n",
2060 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2061 "struct H { char c;\n",
2063 "#pragma pack(1)\n",
2064 " int i; };\n",
2065 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2066 "#pragma pack(1)\n",
2067 "struct I { char c;\n",
2068 "#pragma pack()\n",
2069 " int i; };\n",
2070 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2071 "#pragma pack()\n",
2072 "#pragma pack(push, 8)\n",
2074 "#pragma pack(push, 1)\n",
2075 "struct P { char c; int i; };\n",
2076 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2077 "#pragma pack(pop)\n",
2078 "struct Q { char c; int i; };\n",
2079 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2080 "#pragma pack(pop)\n",
2081 "#pragma pack(16)\n",
2083 "struct R { char c; int i; };\n",
2084 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2085 "#pragma pack()\n",
2086 "#pragma pack(1)\n",
2087 "struct S { char c; int i : 5; int j : 20; };\n",
2088 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2089 "union T { char c; int i; };\n",
2090 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2091 "#pragma pack()\n",
2092 ));
2093 }
2094
2095 #[test]
2099 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2100 let result = run(
2101 &options(),
2102 concat!(
2103 "#pragma pack 4\n",
2104 "#pragma pack(pop)\n",
2105 "#pragma pack(3)\n",
2106 "#pragma pack(1) junk\n",
2107 "#pragma pack(push, 1\n",
2108 "#pragma pack(x)\n",
2109 "#pragma pack(0)\n",
2112 "#pragma pack(push)\n",
2113 "struct s { char c; int i; };\n",
2114 "#pragma pack(pop)\n",
2115 "#pragma pack(pop, foo)\n",
2116 ),
2117 );
2118 let expected = [
2119 "missing `(` after `#pragma pack` - ignored",
2120 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2121 "alignment must be a small power of two, not 3",
2122 "junk at end of `#pragma pack`",
2123 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2124 "unknown action `x` for `#pragma pack` - ignored",
2125 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2126 ];
2127 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2128 for (message, want) in result.messages.iter().zip(expected) {
2129 assert!(message.contains(want), "expected {want:?} in {message:?}");
2130 }
2131 }
2132
2133 #[test]
2140 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2141 let result = run(
2142 &options(),
2143 concat!(
2144 "#pragma pack(push, 1)\n",
2145 "#pragma pack(pop)\n",
2146 "#define API\n",
2147 "API const char version[] = \"3.53.4\";\n",
2148 "const char *get(void) { return version; }\n",
2149 ),
2150 );
2151 assert!(result.messages.is_empty(), "{:?}", result.messages);
2152 }
2153
2154 #[test]
2158 fn the_wide_integer_answers_to_all_three_of_its_names() {
2159 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2160 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2161 assert!(text.contains("decl #1 b : __int128"), "{text}");
2162 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2163 }
2164
2165 #[test]
2166 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2167 let text = tast("long f(int a, long b) { return a + b; }\n");
2171 assert!(text.contains("convert arithmetic"), "{text}");
2172 }
2173
2174 #[test]
2175 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2176 for source in [
2177 "#error stop\n",
2178 "int f(void) { return 1 + ; }\n",
2179 "int f(void) { return undeclared; }\n",
2180 ] {
2181 let result = run(&options(), source);
2182 assert!(result.failed(), "expected this to fail:\n{source}");
2183 assert!(
2184 result.text().is_empty(),
2185 "a file that did not compile wrote a tree:\n{source}"
2186 );
2187 }
2188 }
2189
2190 #[test]
2191 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2192 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2196 assert_eq!(result.errors, 1, "{:?}", result.messages);
2197 }
2198
2199 #[test]
2200 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2201 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2205 assert_eq!(result.errors, 1, "{:?}", result.messages);
2206 }
2207
2208 #[test]
2209 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2210 let source = "int f(void) { char c = 300; return c; }\n";
2211 let plain = run(&options(), source);
2212 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2213 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2214 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2215
2216 let mut opts = options();
2217 opts.warnings_are_errors = true;
2218 let strict = run(&opts, source);
2219 assert!(strict.failed());
2220 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2221 for message in &strict.messages {
2222 assert!(!message.contains("warning:"), "{message}");
2223 }
2224 }
2225
2226 #[test]
2227 fn w_drops_the_warning_before_werror_can_promote_it() {
2228 let source = "int f(void) { char c = 300; return c; }\n";
2229 let mut opts = options();
2230 opts.warnings = false;
2231 let quiet = run(&opts, source);
2232 assert_eq!(quiet.messages, Vec::<String>::new());
2233 assert_eq!(quiet.errors, 0);
2234 assert!(!quiet.text().is_empty(), "and the file still compiles");
2235
2236 opts.warnings_are_errors = true;
2239 let both = run(&opts, source);
2240 assert_eq!(both.messages, Vec::<String>::new());
2241 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2242 }
2243
2244 #[test]
2245 fn the_dialect_reaches_the_keywords_and_the_checking() {
2246 let source = "typeof(1) x;\n";
2249 let mut opts = options();
2250 opts.std = Std::C23;
2251 opts.gnu_extensions = false;
2252 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2253
2254 opts.std = Std::C17;
2255 assert!(run(&opts, source).failed());
2256 }
2257
2258 #[test]
2259 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2260 let mut opts = options();
2261 opts.emit = EmitKind::Object;
2262 let result = run(&opts, "int x = 1;\n");
2263 assert!(!result.failed(), "{:?}", result.messages);
2264 assert!(result.text().is_empty());
2265 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2268 }
2269
2270 fn mir(source: &str) -> String {
2272 let mut opts = options();
2273 opts.emit = EmitKind::MirFinal;
2274 let result = run(&opts, source);
2275 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2276 result.text().to_owned()
2277 }
2278
2279 #[test]
2285 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2286 let text = mir("int add(int a, int b) { return a + b; }\n");
2287 assert!(text.starts_with("mfunc @add {"), "{text}");
2288 assert!(text.contains("x64.add_rr_32"), "{text}");
2289 assert!(text.contains("x64.ret"), "{text}");
2290 assert!(!text.contains('%'), "{text}");
2293 }
2294
2295 #[test]
2297 fn a_function_with_no_body_produces_no_machine_function() {
2298 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2299 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2300 assert!(text.contains("mfunc @f {"), "{text}");
2301 assert!(text.contains("x64.call"), "{text}");
2302 }
2303
2304 #[test]
2306 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2307 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2308 let first = text.find("mfunc @a").expect("the first function");
2309 let second = text.find("mfunc @b").expect("the second function");
2310 assert!(first < second, "{text}");
2311 }
2312
2313 #[test]
2315 fn the_target_decides_which_convention_the_generated_code_follows() {
2316 let mut opts = options();
2317 opts.emit = EmitKind::MirFinal;
2318 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2319 assert!(linux.contains("$rdi"), "{linux}");
2320
2321 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2322 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2323 assert!(windows.contains("$rcx"), "{windows}");
2324 assert!(!windows.contains("$rdi"), "{windows}");
2325 }
2326
2327 #[test]
2335 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2336 let source = concat!(
2337 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2338 "int size(void) { return sizeof(struct S); }\n",
2339 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2340 );
2341
2342 let mut opts = options();
2343 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2344 let windows = run(&opts, source);
2345 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2346
2347 let linux = run(&options(), source);
2348 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2349 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2350
2351 let mut opts = options();
2354 opts.ms_extensions = Some(true);
2355 let asked = run(&opts, source);
2356 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2357 }
2358
2359 #[test]
2361 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2362 let mut opts = options();
2363 opts.emit = EmitKind::MirFinal;
2364 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2365 let result = run(&opts, "int f(int a) { return a; }\n");
2366 assert!(result.failed());
2367 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2368 assert!(result.text().is_empty());
2369 }
2370
2371 #[test]
2378 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2379 let mut opts = options();
2380 opts.emit = EmitKind::MirFinal;
2381 let source = "void a(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n\
2382 void b(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n";
2383 let result = run(&opts, source);
2384 assert!(result.failed());
2385 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2386 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2387 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2388 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2389 assert!(result.text().is_empty());
2390 }
2391
2392 #[test]
2400 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2401 let mut opts = options();
2402 opts.emit = EmitKind::MirFinal;
2403 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2404 let plain = run(&opts, source);
2405 assert!(!plain.failed(), "{:?}", plain.messages);
2406 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2407
2408 opts.stack_clash = true;
2409 let result = run(&opts, source);
2410 assert!(!result.failed(), "{:?}", result.messages);
2411 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2412 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2413 }
2414
2415 #[test]
2425 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
2426 let mut opts = options();
2427 opts.emit = EmitKind::Object;
2428 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2429 let source = concat!(
2430 "void use(void *p);\n",
2431 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
2432 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
2433 );
2434 let result = run(&opts, source);
2435 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2436 let bytes = match result.artifact {
2437 Artifact::Object { bytes, .. } => bytes,
2438 other => panic!("expected an object, got {other:?}"),
2439 };
2440 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2441
2442 let mut opts = options();
2445 opts.emit = EmitKind::Object;
2446 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2447 }
2448
2449 #[test]
2460 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
2461 let source = concat!(
2462 "void other(void *p);\n",
2463 "void takes(void (*f)(void *));\n",
2464 "void (*held)(void *);\n",
2465 "void pass(void) { takes(other); }\n",
2466 "void keep(void) { held = other; }\n",
2467 "void call(void) { other(0); }\n",
2468 );
2469 let mut opts = options();
2470 opts.emit = EmitKind::Object;
2471 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2472 let result = run(&opts, source);
2473 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2474 let bytes = match result.artifact {
2475 Artifact::Object { bytes, .. } => bytes,
2476 other => panic!("expected an object, got {other:?}"),
2477 };
2478 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2479
2480 let mut opts = options();
2483 opts.emit = EmitKind::Object;
2484 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2485 }
2486
2487 #[test]
2501 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
2502 let mut opts = options();
2503 opts.emit = EmitKind::MirFinal;
2504 let source =
2505 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
2506 let result = run(&opts, source);
2507 assert!(result.failed());
2508 assert!(
2509 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
2510 "{result:?}"
2511 );
2512 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
2513 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
2514 }
2515
2516 #[test]
2518 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
2519 let mut opts = options();
2520 opts.emit = EmitKind::MirFinal;
2521 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
2522 let result = run(&opts, source);
2523 assert!(result.failed());
2524 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
2525 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
2526 assert!(!note.contains("spec/17-milestones.md"), "{note}");
2527 }
2528
2529 #[test]
2531 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
2532 let source = "int f(int a) { return a; }\n";
2533 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
2534
2535 let mut opts = options();
2536 opts.emit = EmitKind::MirFinal;
2537 opts.frame_pointer = true;
2538 let kept = run(&opts, source).text().to_owned();
2539 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
2540 }
2541
2542 fn asm(source: &str) -> String {
2544 let mut opts = options();
2545 opts.emit = EmitKind::Asm;
2546 let result = run(&opts, source);
2547 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2548 result.text().to_owned()
2549 }
2550
2551 #[test]
2558 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
2559 let text = asm("int add(int a, int b) { return a + b; }\n");
2560 assert!(text.contains("\t.globl\tadd\n"), "{text}");
2561 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
2562 assert!(text.contains("\nadd:\n"), "{text}");
2563 assert!(text.contains("\taddl\t"), "{text}");
2564 assert!(text.contains("\tret\n"), "{text}");
2565 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
2566 assert!(text.contains(".note.GNU-stack"), "{text}");
2569 }
2570
2571 #[test]
2577 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
2578 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
2579 assert!(text.contains("\tcall\t*%"), "{text}");
2580 assert!(text.contains("\tcall\tg\n"), "{text}");
2581 assert!(text.contains("%rdi"), "{text}");
2585 }
2586
2587 #[test]
2591 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
2592 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
2593 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
2594 }
2595
2596 #[test]
2605 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
2606 let arms = "return 1; return 2;";
2607 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
2608 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
2609 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
2610 assert!(
2611 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2612 "{operator}: {text}"
2613 );
2614 assert!(!text.contains("\tset"), "{operator}: {text}");
2615 assert!(!text.contains("\ttest"), "{operator}: {text}");
2616 }
2617 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
2618 for (operator, jump) in unsigned {
2619 let source =
2620 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
2621 let text = asm(&source);
2622 assert!(
2623 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2624 "{operator}: {text}"
2625 );
2626 }
2627
2628 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
2631 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
2632 }
2633
2634 #[test]
2640 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
2641 let text = asm("int f(int a, int b) { return a < b; }\n");
2642 assert!(text.contains("\tsetl\t"), "{text}");
2643 }
2644
2645 fn optimized(source: &str) -> String {
2647 let mut opts = options();
2648 opts.emit = EmitKind::Asm;
2649 opts.opt_level = rucc_session::OptLevel::O2;
2650 let result = run(&opts, source);
2651 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2652 result.text().to_owned()
2653 }
2654
2655 #[test]
2665 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
2666 let arms: String =
2667 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
2668 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2669 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
2670 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
2671 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
2672 }
2673
2674 #[test]
2681 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
2682 let arms: String = (0..16)
2683 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
2684 .collect::<Vec<_>>()
2685 .join(" ");
2686 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2687 assert!(text.matches("\tcmp").count() > 1, "{text}");
2688 }
2689
2690 #[test]
2698 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
2699 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
2700 assert!(text.contains("movl\t$2, %eax"), "{text}");
2701 assert!(!text.contains("cvttsd2si"), "{text}");
2702 }
2703
2704 #[test]
2712 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
2713 let text =
2714 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
2715 assert!(text.contains("movl\t$30, %eax"), "{text}");
2716 assert!(!text.contains("t(%rip)"), "{text}");
2717 }
2718
2719 #[test]
2722 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
2723 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
2724 assert!(text.contains("movl\t$98, %eax"), "{text}");
2725 }
2726
2727 #[test]
2731 fn a_table_that_is_not_read_only_keeps_its_load() {
2732 let text = optimized(
2733 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
2734 );
2735 assert!(!text.contains("movl\t$30, %eax"), "{text}");
2736 }
2737
2738 #[test]
2745 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
2746 let text = optimized(
2747 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
2748 );
2749 assert!(!text.contains("call\tlink_error"), "{text}");
2750 }
2751
2752 #[test]
2754 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2755 let text = asm("long f(void *p) { return (long)p; }\n");
2756 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2761 let mnemonic = line.split_whitespace().next().unwrap_or("");
2762 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2763 }
2764 }
2765
2766 #[test]
2770 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2771 let six = "long a, long b, long c, long d, long e, long f";
2772 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2773
2774 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2781 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2782
2783 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2787 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2788 let eight =
2789 "double a, double b, double c, double d, double e, double f, double g, double h";
2790 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2791 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2792 }
2793
2794 #[test]
2797 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2798 let six = "1, 2, 3, 4, 5, 6";
2799 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2800 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2801
2802 assert!(text.contains("\tmovq\t%"), "{text}");
2803 assert!(text.contains(", (%rsp)\n"), "{text}");
2804 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2805 assert!(text.contains("\tsubq\t$"), "{text}");
2807
2808 let narrow = "int g(int, int, int, int, int, int, int);\n";
2810 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2811 assert!(text.contains("\tmovl\t%"), "{text}");
2812 assert!(text.contains(", (%rsp)\n"), "{text}");
2813 }
2814
2815 #[test]
2818 fn a_variadic_call_counts_registers_and_not_arguments() {
2819 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2820 let decl = "int g(int, ...);\n";
2821 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2822
2823 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2824 assert!(text.contains("\tmovsd\t%"), "{text}");
2825 assert!(text.contains(", (%rsp)\n"), "{text}");
2826 }
2827
2828 #[test]
2833 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2834 let body =
2835 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2836 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2837
2838 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2841 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2842 assert!(!text.contains(", 0(%r"), "{text}");
2843 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2846 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2847
2848 assert!(text.contains("\tsubq\t$"), "{text}");
2850 }
2851
2852 #[test]
2855 fn va_start_writes_the_four_fields_the_psabi_describes() {
2856 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2857 let params = "int a, int b, int c, double d";
2858 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2859
2860 assert!(text.contains(" movl $24, "), "{text}");
2864 assert!(text.contains(" movl $64, "), "{text}");
2865 assert!(text.contains(", 8(%r"), "{text}");
2869 assert!(text.contains(", 16(%r"), "{text}");
2870 let frame: u32 = text
2871 .lines()
2872 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
2873 .expect("a variadic function takes a frame for the save area");
2874 let above = |line: &str| {
2875 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
2876 Some(at > frame)
2877 };
2878 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
2879 }
2880
2881 #[test]
2884 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
2885 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
2886 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
2887 let text = asm(&ints);
2888
2889 assert!(text.contains("$40, "), "{text}");
2892 assert!(text.contains(" cmpl "), "{text}");
2893 assert!(text.contains(" ja "), "{text}");
2897
2898 let arg = "__builtin_va_arg(ap, double)";
2899 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
2900 assert!(text.contains("$160, "), "the last vector slot: {text}");
2901 }
2902
2903 #[test]
2906 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
2907 let decl = "struct pair { long a, b; };\n";
2908 let body = "struct pair p = *q; return p.a + p.b;";
2909 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
2910
2911 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
2912 assert!(!text.contains("\tcall"), "{text}");
2913 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
2915 }
2916
2917 #[test]
2920 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
2921 let decl = "struct bytes { char a[8]; };\n";
2922 let body = "struct bytes p = *q; return p.a[0];";
2923 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
2924
2925 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
2927 }
2928
2929 #[test]
2932 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
2933 let decl = "struct wide { long a, b, c; };\n";
2934 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
2935
2936 assert!(!text.contains("memset"), "nothing calls the library: {text}");
2937 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
2942 }
2943
2944 #[test]
2947 fn a_copy_too_large_to_unroll_calls_the_runtime() {
2948 let decl = "struct huge { char a[4096]; };\n";
2949 let mut opts = options();
2950 opts.emit = EmitKind::Asm;
2951 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
2952 let result = run(&opts, &source);
2953 assert!(!result.failed(), "{:?}", result.messages);
2954 let text = result.text();
2955 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
2956 assert!(text.contains("4096"), "the size travels: {text}");
2959 }
2960
2961 #[test]
2968 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
2969 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
2970 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
2971
2972 let copy = text.find("call\tmemcpy").expect("the copy");
2973 let call = text.find("call\ttake").expect("the call");
2974 assert!(copy < call, "the copy comes first: {text}");
2975 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
2980 assert!(text.contains("$4096, %edx"), "the size: {text}");
2981 }
2982
2983 #[test]
2986 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
2987 let six = "long a, long b, long c, long d, long e, long f";
2988 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
2989 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
2990
2991 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
2995 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
2996 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
2997 }
2998
2999 #[test]
3001 fn the_target_decides_how_the_assembly_is_spelled() {
3002 let mut opts = options();
3003 opts.emit = EmitKind::Asm;
3004 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3005 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3006 assert!(text.contains("__TEXT,__text"), "{text}");
3007 assert!(text.contains("\n_f:\n"), "{text}");
3008 assert!(!text.contains(".note.GNU-stack"), "{text}");
3009 }
3010
3011 fn obj(source: &str) -> Vec<u8> {
3013 let mut opts = options();
3014 opts.emit = EmitKind::Object;
3015 let result = run(&opts, source);
3016 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3017 match result.artifact {
3018 Artifact::Object { bytes, .. } => bytes,
3019 other => panic!("expected an object, got {other:?}"),
3020 }
3021 }
3022
3023 #[test]
3029 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3030 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3031 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3032 let text = asm("int add(int a, int b) { return a + b; }\n");
3033 assert!(
3034 text.contains("\taddl\t"),
3035 "and the listing of it is the same instructions:\n{text}"
3036 );
3037 }
3038
3039 #[test]
3041 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3042 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3043 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3044 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3045 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3046 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3049 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3050 assert!(!text.contains(".globl\thidden"), "{text}");
3051 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3054 }
3055
3056 #[test]
3063 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3064 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3065 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3066 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3067
3068 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3071 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3072
3073 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3076 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3077
3078 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3080 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3081 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3082 }
3083
3084 #[test]
3086 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3087 let text = asm("const char *f(void) { return \"hi\"; }\n");
3088 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3089 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3090 let label = text
3091 .lines()
3092 .find(|line| line.starts_with(".Lstr"))
3093 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3094 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3095 }
3096
3097 #[test]
3099 fn an_address_in_an_initializer_is_left_to_the_linker() {
3100 let source = "int counter;\nint *p = &counter;\n";
3101 let text = asm(source);
3102 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3103 let bytes = obj(source);
3106 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3107 }
3108
3109 #[test]
3118 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3119 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3122 struct m { void (*x)(void); void (*y)(void); };\n\
3123 const struct m t = { a, b };\n");
3124 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3125 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3126
3127 let text =
3130 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3131 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3132
3133 let text = asm("const int fixed = 7;\n");
3135 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3136 }
3137
3138 #[test]
3145 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3146 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3147 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3150 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3151 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3154 assert!(text.contains("%fs:0"), "{text}");
3155 }
3156
3157 #[test]
3163 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3164 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3165 assert!(text.contains("movq\t%fs:0, "), "{text}");
3166 assert!(!text.contains("GOTTPOFF"), "{text}");
3168 }
3169
3170 #[test]
3181 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3182 for (locality, wanted) in
3183 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3184 {
3185 let source =
3186 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3187 let text = asm(&source);
3188 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3189 }
3190 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3192 assert!(text.contains("\tprefetcht0\t"), "{text}");
3193 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3196 assert!(text.contains("\tprefetcht0\t"), "{text}");
3197 assert!(!text.contains("prefetchw"), "{text}");
3198 }
3199
3200 #[test]
3211 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3212 let text = asm("void stop(void) { __builtin_trap(); }\n");
3213 assert!(text.contains("\tud2\n"), "{text}");
3214 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3215
3216 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3217 assert!(text.contains("\tud2\n"), "{text}");
3218 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3219 }
3220
3221 #[test]
3233 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3234 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3235 assert!(!text.contains("assume_aligned"), "{text}");
3236 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3237
3238 let source = "unsigned long width(void);\n\
3239 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3240 let text = asm(source);
3241 assert!(!text.contains("assume_aligned"), "{text}");
3242 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3243 }
3244
3245 #[test]
3255 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3256 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3257 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3258 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3259 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3260
3261 let walk = |depth: u32| {
3262 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3263 asm(&source).matches("movq\t(%r").count()
3264 };
3265 assert_eq!(walk(1), 1, "one link is one load");
3266 assert_eq!(walk(3), 3, "three links are three loads");
3267 }
3268
3269 #[test]
3279 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3280 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3281 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3282 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3283 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3284
3285 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3286 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3287 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3288 }
3289
3290 #[test]
3301 fn a_depth_that_is_not_a_small_constant_is_refused() {
3302 let mut opts = options();
3303 opts.emit = EmitKind::Ir;
3304 for source in [
3305 "void *up(int n) { return __builtin_return_address(n); }\n",
3306 "void *up(void) { return __builtin_frame_address(1000); }\n",
3307 ] {
3308 let messages = run(&opts, source).messages;
3309 let named = messages.iter().any(|m| m.contains("E0705"));
3310 assert!(named, "expected a refusal in {messages:?}");
3311 }
3312 }
3313
3314 #[test]
3326 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3327 let text =
3328 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3329 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
3330 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
3331 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3332
3333 let plain = concat!(
3336 "extern void *alloca(__SIZE_TYPE__);\n",
3337 "void use(void *p);\n",
3338 "void f(unsigned long n) { use(alloca(n)); }\n",
3339 );
3340 let text = asm(plain);
3341 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
3342 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
3343
3344 let own = concat!(
3347 "static void *alloca(unsigned long n) { return 0; }\n",
3348 "void *f(unsigned long n) { return alloca(n); }\n",
3349 );
3350 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3351 }
3352
3353 #[test]
3363 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3364 let inner = "{ use(__builtin_alloca(n)); }";
3365 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3366 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3367 let text = asm(&source);
3368 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3372 let taking = line.contains("subq");
3373 let leaving = line.contains("%rbp");
3374 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3375 }
3376 }
3377 }
3378
3379 #[test]
3381 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3382 let source = "int callee(void); int g(void) { return callee(); }\n";
3386 let bytes = obj(source);
3387 assert!(
3388 bytes.windows(7).any(|w| w == b"callee\0"),
3389 "the object has to name the callee for the linker to find it"
3390 );
3391 let text = asm(source);
3392 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3393 }
3394
3395 #[test]
3401 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3402 let mut opts = options();
3403 opts.emit = EmitKind::Executable;
3405 let result = run(&opts, "int main(void) { return 0; }\n");
3406 assert_eq!(result.messages, Vec::<String>::new());
3407 match result.artifact {
3408 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3409 other => panic!("expected an object, got {other:?}"),
3410 }
3411 }
3412
3413 #[test]
3415 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3416 let mut opts = options();
3417 opts.emit = EmitKind::Object;
3418 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3419 let result = run(&opts, "int f(void) { return 0; }\n");
3420 assert!(result.failed(), "an object nobody can read is worse than a message");
3421 assert!(
3422 result.messages.iter().any(|m| m.contains("no object writer")),
3423 "{:?}",
3424 result.messages
3425 );
3426 }
3427
3428 fn ir(source: &str) -> String {
3430 let mut opts = options();
3431 opts.emit = EmitKind::Ir;
3432 let result = run(&opts, source);
3433 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3434 result.text().to_owned()
3435 }
3436
3437 fn errors(source: &str) -> Vec<String> {
3439 let mut opts = options();
3440 opts.emit = EmitKind::Ir;
3441 let result = run(&opts, source);
3442 assert!(result.failed(), "expected this to be refused:\n{source}");
3443 result.messages
3444 }
3445
3446 fn body(source: &str) -> String {
3448 let text = ir(source);
3449 let (_, rest) = text.split_once("{\n").expect("a function definition");
3450 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3451 body.to_owned()
3452 }
3453
3454 #[test]
3462 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3463 let source = "inline int f(int x) { return x + 1; }\n";
3464 let with = |flag: bool| {
3465 let mut opts = options();
3466 opts.emit = EmitKind::Ir;
3467 opts.gnu89_inline = flag;
3468 let result = run(&opts, source);
3469 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3470 result.text().to_owned()
3471 };
3472
3473 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3476
3477 assert!(with(true).contains("block0"), "a body: {}", with(true));
3480 }
3481
3482 #[test]
3489 fn an_access_through_a_type_names_the_type_it_went_through() {
3490 let source = "\
3491struct s { int a; float b; };\n\
3492union u { int i; float f; };\n\
3493int scalar(int *p) { return *p; }\n\
3494float member(struct s *p) { p->a = 1; return p->b; }\n\
3495int element(int *a, long i) { return a[i]; }\n\
3496float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3497 let text = ir(source);
3498 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3499 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3500 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3501 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3504 assert_eq!(named, 6, "six accesses: {text}");
3505 }
3506
3507 #[test]
3514 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3515 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3516 let mut opts = options();
3517 opts.emit = EmitKind::Ir;
3518 opts.strict_aliasing = false;
3519 let result = run(&opts, source);
3520 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3521 let text = result.text().to_owned();
3522 assert!(!text.contains("tbaa"), "not even the root: {text}");
3523 }
3524
3525 #[test]
3533 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3534 let mut opts = options();
3535 opts.emit = EmitKind::Ir;
3536 opts.std = Std::C89;
3537 let compiled = |source: &str| {
3538 let result = run(&opts, source);
3539 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3540 result.text().to_owned()
3541 };
3542
3543 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3544 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3545 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3546
3547 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3549 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3550 }
3551
3552 #[test]
3560 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3561 let mut opts = options();
3562 opts.emit = EmitKind::Ir;
3563 opts.std = Std::C89;
3564 let compiled = |source: &str| {
3565 let result = run(&opts, source);
3566 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3567 result.text().to_owned()
3568 };
3569
3570 let text = compiled("int f(void) { return g(); }\n");
3572 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3573 assert!(text.contains("i32"), "and it gives back an int: {text}");
3574
3575 let text = compiled("int f(char c) { return g(c); }\n");
3578 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3579
3580 let mut opts = options();
3583 opts.std = Std::C89;
3584 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3585 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3586 }
3587
3588 #[test]
3598 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3599 let mut opts = options();
3600 opts.emit = EmitKind::Ir;
3601 opts.std = Std::C89;
3602 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3603 .text()
3604 .to_owned();
3605 assert!(text.contains("func @f()"), "the caller is there: {text}");
3606 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3607 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3608 }
3609
3610 #[test]
3618 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3619 let mut opts = options();
3620 opts.emit = EmitKind::Ir;
3621 opts.std = Std::C89;
3622 let compiled = |source: &str| run(&opts, source).text().to_owned();
3623
3624 let text = compiled("f (c) unsigned char c; { return c; }\n");
3625 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3626 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3627 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3628
3629 let text = compiled("f (s) short s; { return s; }\n");
3631 assert!(text.contains("trunc.i16"), "cut down: {text}");
3632 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3633
3634 let text = compiled("f (x) float x; { return x * 2; }\n");
3637 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3638 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3639
3640 let text = compiled("int f(unsigned char c) { return c; }\n");
3643 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3644 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3645 }
3646
3647 #[test]
3656 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3657 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3659 let cases = [
3660 ("static counted;\n", ["", "error", "warning", "error"]),
3661 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3662 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3663 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3664 (
3665 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3666 ["warning", "error", "warning", "error"],
3667 ),
3668 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3669 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3670 ];
3671
3672 for (source, wanted) in cases {
3673 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3674 let mut opts = options();
3675 opts.std = std;
3676 opts.permissive = permissive;
3677 let said = run(&opts, source).messages.join("\n");
3678 let severity = if said.contains(": error: ") {
3679 "error"
3680 } else if said.contains(": warning: ") {
3681 "warning"
3682 } else {
3683 ""
3684 };
3685 let how = if permissive { " -fpermissive" } else { "" };
3686 assert_eq!(
3687 severity,
3688 wanted,
3689 "under -std={}{how}, {source} was answered with `{said}`",
3690 std.as_str()
3691 );
3692 if wanted.is_empty() {
3693 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3694 }
3695 }
3696 }
3697 }
3698
3699 #[test]
3708 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3709 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3710 let cases = [
3711 (
3712 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3713 "first argument to 'va_arg' not of type 'va_list'",
3714 ["error", "error", "error", "error"],
3715 ),
3716 (
3717 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3718 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3719 ["warning", "error", "warning", "error"],
3720 ),
3721 (
3722 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3723 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3724 cast",
3725 ["warning", "error", "warning", "error"],
3726 ),
3727 (
3728 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3729 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3730 ["warning", "error", "warning", "error"],
3731 ),
3732 ];
3733
3734 for (source, message, wanted) in cases {
3735 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3736 let mut opts = options();
3737 opts.std = std;
3738 opts.permissive = permissive;
3739 let said = run(&opts, source).messages.join("\n");
3740 let how = if permissive { " -fpermissive" } else { "" };
3741 assert!(
3742 said.contains(&format!(": {wanted}: {message}")),
3743 "under -std={}{how}, {source} was answered with `{said}`",
3744 std.as_str()
3745 );
3746 }
3747 }
3748 }
3749
3750 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3752 let mut opts = options();
3753 opts.emit = EmitKind::Ir;
3754 opts.safety = tier;
3755 let result = run(&opts, source);
3756 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3757 result.text().to_owned()
3758 }
3759
3760 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3761
3762 fn padded_ir(padding: Padding, source: &str) -> String {
3764 let mut opts = options();
3765 opts.emit = EmitKind::Ir;
3766 opts.safety = rucc_session::Safety::Detect;
3767 opts.padding = padding;
3768 let result = run(&opts, source);
3769 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3770 result.text().to_owned()
3771 }
3772
3773 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3774 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3775
3776 #[test]
3777 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3778 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3782 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3783 }
3784
3785 #[test]
3786 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3787 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3790 assert!(!text.contains("owns"), "{text}");
3791 }
3792
3793 #[test]
3794 fn a_member_of_a_union_owns_nothing_after_it() {
3795 let text = padded_ir(
3799 Padding::Ignored,
3800 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3801 );
3802 assert!(!text.contains("owns"), "{text}");
3803 }
3804
3805 #[test]
3806 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3807 let text = padded_ir(
3812 Padding::Ignored,
3813 "struct inner { char c; };\n\
3814 struct outer { struct inner in; int x; };\n\
3815 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3816 );
3817 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3818 }
3819
3820 #[test]
3821 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3822 let text = ir(READS_THROUGH_A_POINTER);
3826 assert!(!text.contains("check_"), "{text}");
3827 assert!(!text.contains("cap_of"), "{text}");
3828 }
3829
3830 #[test]
3831 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3832 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3833 assert!(text.contains("cap_of"), "{text}");
3834 assert!(text.contains("check_bounds"), "{text}");
3835 assert!(text.contains("check_live"), "{text}");
3836 assert!(text.contains("check_deriv"), "{text}");
3838 assert!(text.contains("check_type"), "{text}");
3840 }
3841
3842 #[test]
3843 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3844 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3848 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3849 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3850 }
3851 }
3852
3853 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3855 let mut opts = options();
3856 opts.emit = EmitKind::SafetySummary;
3857 opts.safety = tier;
3858 let result = run(&opts, source);
3859 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3860 result.text().to_owned()
3861 }
3862
3863 #[test]
3864 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3865 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3866 assert!(text.contains("\"tier\": \"detect\""), "{text}");
3867 assert!(
3869 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
3870 "{text}"
3871 );
3872 assert!(
3873 text.contains(
3874 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
3875 ),
3876 "{text}"
3877 );
3878 }
3879
3880 #[test]
3881 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
3882 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
3886 assert!(text.contains("\"tier\": \"off\""), "{text}");
3887 assert!(
3888 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
3889 "{text}"
3890 );
3891 }
3892
3893 #[test]
3894 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
3895 let text = summary(
3896 rucc_session::Safety::Detect,
3897 "void *memcpy(void *, const void *, unsigned long);\n\
3898 int puts(const char *);\n\
3899 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
3900 );
3901 assert!(text.contains("\"interposed\": 1"), "{text}");
3902 assert!(text.contains("\"puts\""), "{text}");
3903 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
3907 }
3908
3909 #[test]
3910 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
3911 let text = summary(
3916 rucc_session::Safety::Detect,
3917 "void *memcpy(void *, const void *, unsigned long);\n\
3918 int puts(const char *);\n\
3919 void *table[2] = { (void *)memcpy, (void *)puts };\n\
3920 void *f(int i) { return table[i]; }\n",
3921 );
3922 assert!(text.contains("\"interposed\": 1"), "{text}");
3923 assert!(text.contains("\"puts\""), "{text}");
3924 assert!(!text.contains("\"memcpy\""), "{text}");
3925 }
3926
3927 #[test]
3928 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3929 let text = summary(
3933 rucc_session::Safety::Detect,
3934 "void *notes_open(void);\n\
3935 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3936 );
3937 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3938 assert!(text.contains("\"notes_open\""), "{text}");
3939 }
3940
3941 #[test]
3942 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3943 let text = summary(
3946 rucc_session::Safety::Detect,
3947 "static int len(const char *p) { return p ? 1 : 0; }\n\
3948 int f(void) { return len(\"x\"); }\n",
3949 );
3950 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3951 }
3952
3953 fn granules(source: &str) -> String {
3955 let mut opts = options();
3956 opts.emit = EmitKind::TypeGranules;
3957 let result = run(&opts, source);
3958 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3959 result.text().to_owned()
3960 }
3961
3962 #[test]
3963 fn the_granule_report_names_every_record_and_both_keyings() {
3964 let text = granules(
3965 "struct hot { char *p; int a; int b; };\n\
3966 int f(struct hot *h) { return h->a; }\n",
3967 );
3968 assert!(text.contains("struct hot"), "{text}");
3969 assert!(text.contains("every type distinct"), "{text}");
3972 assert!(text.contains("every pointer one type"), "{text}");
3973 assert!(text.contains("budget"), "{text}");
3974 }
3975
3976 #[test]
3977 fn a_record_nothing_uses_is_still_measured() {
3978 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3981 assert!(text.contains("struct unused"), "{text}");
3982 }
3983
3984 #[test]
3985 fn the_granule_report_stops_before_anything_is_lowered() {
3986 let text = granules(
3990 "struct wide { long double d; };\n\
3991 long double f(long double x) { return x * x; }\n",
3992 );
3993 assert!(text.contains("struct wide"), "{text}");
3994 }
3995
3996 #[test]
3997 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
3998 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
4001 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
4002 }
4003
4004 #[test]
4005 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
4006 let text = summary(
4007 rucc_session::Safety::Detect,
4008 "unsigned long f(int *p) { return (unsigned long) p; }\n",
4009 );
4010 assert!(text.contains("\"exposed\": 1"), "{text}");
4011 }
4012
4013 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
4015 let mut opts = options();
4016 opts.emit = EmitKind::Asm;
4017 opts.safety = tier;
4018 let result = run(&opts, source);
4019 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4020 result.text().to_owned()
4021 }
4022
4023 #[test]
4024 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
4025 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4026 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
4027 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
4028 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
4029 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
4030 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
4031 }
4032
4033 #[test]
4034 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
4035 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4039 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4040 assert_eq!(text.matches(§ion).count(), 5, "{text}");
4041 for index in 0..5 {
4042 let name = format!("__rucc_safety_desc_{index}");
4043 assert!(text.contains(&format!("{name}:\n")), "{text}");
4046 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4047 }
4048 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
4049 }
4050
4051 #[test]
4059 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4060 let text = ir(concat!(
4061 "int g;\n",
4062 "int a = __builtin_constant_p(1);\n",
4063 "int b = __builtin_constant_p(g);\n",
4064 "int c = __builtin_constant_p(\"abc\");\n",
4065 "int d = __builtin_constant_p(&g);\n",
4066 "int e = __builtin_constant_p(1.5);\n",
4067 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4068 ));
4069 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4070 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4071 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4072 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4073 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4074 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4075 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4076
4077 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4081 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4082 }
4083
4084 #[test]
4093 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4094 let text = body("void f(void) { __builtin_abort(); }\n");
4095 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4096
4097 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4100 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4101 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4102 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4103 }
4104
4105 #[test]
4118 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4119 let text = ir(concat!(
4120 "char d[8];\n",
4121 "void f(const char *s, unsigned long n) {\n",
4122 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4123 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4124 " __builtin___memset_chk(d, 0, n, 8);\n",
4125 "}\n",
4126 ));
4127 assert!(text.contains("call @__memcpy_chk("), "{text}");
4128 assert!(text.contains("call @__strcpy_chk("), "{text}");
4129 assert!(text.contains("call @__memset_chk("), "{text}");
4130 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4131 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4132 }
4133
4134 #[test]
4142 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4143 let text = ir(concat!(
4144 "extern char *p;\n",
4145 "char d[8];\n",
4146 "void f(const char *s, unsigned long n) {\n",
4147 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4148 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4149 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4150 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4151 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4152 "}\n",
4153 ));
4154
4155 assert!(
4157 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4158 "{text}"
4159 );
4160
4161 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4164 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4165 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4166
4167 assert!(text.contains("call @__sprintf_chk("), "{text}");
4170
4171 let asm = asm(concat!(
4174 "void f(char *p, const char *s, unsigned long n) {\n",
4175 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4176 "}\n",
4177 ));
4178 assert!(asm.contains("call\tmemcpy"), "{asm}");
4179 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4180 }
4181
4182 #[test]
4190 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4191 let text = ir(concat!(
4192 "char d[64];\n",
4193 "int f(const char *fmt, ...) {\n",
4194 " __builtin_va_list ap;\n",
4195 " __builtin_va_start(ap, fmt);\n",
4196 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4197 " __builtin_va_end(ap);\n",
4198 " return n;\n",
4199 "}\n",
4200 ));
4201 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4202 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4203 }
4204
4205 #[test]
4216 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4217 let text = body(concat!(
4218 "long long llabs(long long);\n",
4219 "long long f(long long x) { return llabs(x); }\n",
4220 ));
4221 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4222 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4223 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4224 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4225 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4226
4227 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4230 assert!(text.contains("iconst.i32 31"), "{text}");
4231 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4232 assert!(text.contains("iconst.i64 63"), "{text}");
4233
4234 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4237 assert!(!text.contains("call"), "{text}");
4238
4239 let text = ir(concat!(
4241 "long long llabs(long long b);\n",
4242 "long long g(long long x) { return llabs(x); }\n",
4243 "long long llabs(long long b) { return 7; }\n",
4244 ));
4245 assert!(!text.contains("call @llabs"), "{text}");
4246 }
4247
4248 #[test]
4255 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4256 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4257 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4258
4259 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4262 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4263 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4264 }
4265
4266 #[test]
4272 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4273 for (name, ty, width) in [
4274 ("__builtin_bswap16", "unsigned short", "i16"),
4275 ("__builtin_bswap32", "unsigned", "i32"),
4276 ("__builtin_bswap64", "unsigned long long", "i64"),
4277 ] {
4278 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4279 let text = body(&source);
4280 assert_eq!(
4281 text,
4282 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4283 "{name}"
4284 );
4285 }
4286 }
4287
4288 #[test]
4295 fn the_bit_counts_are_instructions_and_not_calls() {
4296 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4297 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4298
4299 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4300 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4301
4302 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4303 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4304 }
4305
4306 #[test]
4315 fn the_bit_counts_ask_about_the_width_their_name_says() {
4316 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4317 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4318 assert!(text.contains("%1 = ctlz %0"), "{text}");
4319 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4320
4321 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4324 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4325 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4326
4327 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4328 assert!(text.contains("%1 = ctpop %0"), "{text}");
4329 assert!(!text.contains("call"), "{text}");
4330 }
4331
4332 #[test]
4337 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4338 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4339 assert!(text.contains("%1 = ctpop %0"), "{text}");
4340 assert!(text.contains("iconst.i32 1"), "{text}");
4341 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4342 }
4343
4344 #[test]
4350 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4351 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4352 assert!(text.contains("%1 = cttz %0"), "{text}");
4353 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4354 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4355 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4356 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4357 assert!(!text.contains("br_if"), "no branch: {text}");
4358 }
4359
4360 #[test]
4370 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4371 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4372 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4373 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4374 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4375 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4376 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4377 assert!(text.contains("%7 = ctlz %6"), "{text}");
4378 assert!(!text.contains("call"), "{text}");
4379 assert!(!text.contains("br_if"), "no branch: {text}");
4380 }
4381
4382 #[test]
4388 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4389 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4390 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4391 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4392 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4393
4394 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4395 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4396
4397 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4400 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4401 }
4402
4403 #[test]
4411 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4412 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4413 assert!(text.contains("iconst.i64 63"), "{text}");
4414 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4415 assert!(!text.contains("call"), "{text}");
4416
4417 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4418 assert!(text.contains("iconst.i64 63"), "{text}");
4419 assert!(!text.contains("call"), "{text}");
4420 }
4421
4422 #[test]
4430 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4431 let text =
4432 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4433 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4434 assert!(!text.contains("store"), "nothing is written: {text}");
4435 assert!(!text.contains("call"), "{text}");
4436
4437 let text =
4440 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4441 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4442 assert!(!text.contains("store"), "{text}");
4443
4444 let text = body(concat!(
4447 "int g(void);\n",
4448 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4449 ));
4450 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4451 }
4452
4453 #[test]
4463 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4464 let text =
4465 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4466 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4467 assert!(text.contains("store %3 -> %2"), "{text}");
4468 assert!(!text.contains("call"), "{text}");
4469
4470 let text =
4471 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4472 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4473
4474 let text =
4475 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4476 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4477
4478 let text = body(
4481 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4482 );
4483 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4484 }
4485
4486 #[test]
4494 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4495 let text = body(
4496 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4497 );
4498 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4499 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4500 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4501
4502 let text = body(
4505 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4506 );
4507 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4508 assert!(!text.contains("sext."), "{text}");
4509 assert!(!text.contains("zext.i64"), "{text}");
4511 }
4512
4513 #[test]
4521 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4522 let text =
4523 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4524 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4525 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4526 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4527 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4528 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4529 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4530 }
4531
4532 #[test]
4539 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4540 for name in ["add", "sub", "mul"] {
4541 let source = format!(
4542 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4543 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4544 );
4545 let mut opts = options();
4546 opts.emit = EmitKind::MirFinal;
4547 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4548 }
4549 }
4550
4551 #[test]
4554 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4555 let messages =
4556 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4557 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4558
4559 let messages =
4560 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4561 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4562 }
4563
4564 #[test]
4575 fn an_ordered_access_is_ordered_in_the_ir() {
4576 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4577 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4578
4579 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4580 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4581
4582 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4583 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4584
4585 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4586 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4587
4588 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4591 assert!(text.contains("trunc.i8 %1"), "{text}");
4592 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4593 }
4594
4595 #[test]
4604 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4605 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4606 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4607 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4608
4609 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4610 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4611 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4612
4613 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4614 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4615 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4616 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4617 }
4618
4619 #[test]
4629 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4630 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4631 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4632
4633 for weaker in ["1", "2", "3", "4"] {
4634 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4635 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4636 }
4637 }
4638
4639 #[test]
4649 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
4650 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
4651 let source = format!("void f(void) {{ {name}(); }}\n");
4652 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
4653 let text = body(&source);
4654 assert!(text.contains("fence seq_cst"), "{name}: {text}");
4655 }
4656
4657 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
4658 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4659 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
4660 }
4661
4662 #[test]
4668 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4669 let text =
4672 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4673 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4674 assert!(text.contains("return %3"), "the value it found: {text}");
4675
4676 let text =
4677 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4678 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4679 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4680
4681 let text = body(
4684 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4685 );
4686 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4687 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4688 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4689 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4690
4691 let text = body(
4694 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4695 );
4696 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4697 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4698 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4699 }
4700
4701 #[test]
4708 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4709 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4710 for (ty, suffix, reg) in widths {
4711 let source = format!(
4712 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4713 );
4714 let text = asm(&source);
4715 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4716 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4717 assert!(text.contains("sete\t"), "{ty}: {text}");
4718 }
4719 let source =
4720 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4721 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4722
4723 for order in ["0", "2", "3", "4", "5"] {
4727 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4728 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4729 let text = asm(&source);
4730 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4731 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4732 }
4733 }
4734
4735 #[test]
4747 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4748 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4749 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4750 assert!(text.contains("return %2"), "the value that was there: {text}");
4751
4752 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4753 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4754 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4755
4756 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4757 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4758 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4759
4760 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4762 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4763
4764 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4767 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4768
4769 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4770 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4771
4772 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4775 assert!(text.contains("release"), "{text}");
4776 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4777
4778 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4782 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4783 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4784
4785 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4788 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4789
4790 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4791 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4792 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4793
4794 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4797 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4798 assert!(text.contains("%3 = and %2, %1"), "{text}");
4799 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4800 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4801 }
4802
4803 #[test]
4814 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4815 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4816 for (ty, suffix, reg) in widths {
4817 for (name, call, insn) in [
4818 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4819 ("or", "__sync_fetch_and_or(p, v)", "or"),
4820 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4821 ] {
4822 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4823 let text = asm(&source);
4824 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4825 assert!(
4826 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4827 "{ty} {name}: {text}"
4828 );
4829 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4830 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4832 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4833 }
4834 }
4835 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4836 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4837
4838 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4842 assert!(text.contains("cmpxchgl\t"), "{text}");
4843 assert!(text.contains("andl\t"), "{text}");
4844 assert!(text.contains("notl\t"), "{text}");
4845 }
4846
4847 #[test]
4856 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4857 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4858 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4859 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4860
4861 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4862 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4863 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4864
4865 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4868 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4869 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4870 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4871 }
4872
4873 #[test]
4884 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4885 for pointer in ["char", "int", "void"] {
4886 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4887 let text = body(&source);
4888 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4889 assert!(
4890 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4891 "{pointer}: {text}"
4892 );
4893 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4894
4895 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4896 let text = body(&source);
4897 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4898 }
4899
4900 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4903 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4904 assert!(text.contains("setne\t"), "{text}");
4905 }
4906
4907 #[test]
4915 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4916 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4917 for (ty, suffix, reg) in widths {
4918 let source =
4919 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4920 let text = asm(&source);
4921 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4922 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4923
4924 let source =
4925 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4926 let text = asm(&source);
4927 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4928 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4929 }
4930 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4931 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4932
4933 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4936 let text = asm(source);
4937 assert!(text.contains("negl\t"), "{text}");
4938 assert!(text.contains("xaddl\t"), "{text}");
4939
4940 for order in ["0", "2", "3", "4", "5"] {
4943 let source =
4944 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4945 let text = asm(&source);
4946 assert!(text.contains("xaddl\t"), "{order}: {text}");
4947 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4948 }
4949
4950 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4954 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4955 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4962 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
4963 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4964 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4965 }
4966
4967 #[test]
4979 fn the_lock_free_questions_are_answered_as_constants() {
4980 for size in ["1", "2", "4", "8"] {
4981 let source =
4982 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4983 let text = asm(&source);
4984 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4985 assert!(!text.contains("call"), "and is not a call: {text}");
4986 }
4987 for size in ["3", "16", "sizeof(long double)"] {
4988 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4989 let text = asm(&source);
4990 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
4991 assert!(!text.contains("call"), "and is not a call either: {text}");
4992 }
4993
4994 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
4998 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
4999 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
5000 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
5001 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
5002 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
5003 }
5004
5005 #[test]
5017 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
5018 let mut opts = options();
5019 opts.emit = EmitKind::Ir;
5020
5021 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
5022 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
5023 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
5024
5025 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
5026 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
5027 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
5028
5029 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
5030 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
5031 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
5032 }
5033
5034 #[test]
5046 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5047 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5048 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5049 assert!(text.contains("shrq"), "with the value halved first: {text}");
5050 assert!(text.contains("addsd"), "and doubled after: {text}");
5051 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5052
5053 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5054 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5055 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5056 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5057 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5058 }
5059
5060 #[test]
5071 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5072 let taken = concat!(
5073 "static long long llabs(long long b) { return 7; }\n",
5074 "long long f(long long x) { return llabs(x); }\n",
5075 );
5076 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5077
5078 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5079 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5080
5081 let plain = concat!(
5082 "long long llabs(long long b);\n",
5083 "long long f(long long x) { return llabs(x); }\n",
5084 );
5085 let mut opts = options();
5086 opts.emit = EmitKind::Ir;
5087 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5088
5089 opts.builtins = false;
5090 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5091
5092 opts.builtins = true;
5093 opts.no_builtin = vec!["llabs".to_owned()];
5094 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5095 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5096 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5097
5098 opts.no_builtin = Vec::new();
5101 opts.builtins = false;
5102 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5103 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5104 }
5105
5106 #[test]
5119 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5120 let text = ir(concat!(
5121 "long a = __builtin_expect(7, 1);\n",
5122 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5123 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5124 ));
5125 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5126 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5127 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5128 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5129
5130 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5133 assert!(text.contains("sext"), "{text}");
5134
5135 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5139 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5140 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5141 assert_eq!(body(source), one);
5142
5143 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5148 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5149 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5150 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5151 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5152 }
5153
5154 #[test]
5166 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5167 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5168 let text = ir(promised);
5169 assert!(text.contains(" unreachable_hint\n"), "{text}");
5170 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5171
5172 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5176 assert!(after.contains("return"), "{after}");
5177
5178 let text = asm(promised);
5181 let mine = text.split_once("\nf:\n").expect("a definition").1;
5182 let mine = mine.split_once("\t.size").expect("a definition").0;
5183 let plain = asm("int f(int x) { if (x) return 1; }\n");
5184 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5185 let plain = plain.split_once("\t.size").expect("a definition").0;
5186 assert_eq!(mine, plain);
5187 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5190 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5191 assert!(!mine.contains("ud2"), "{mine}");
5192 }
5193
5194 #[test]
5201 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5202 let mut opts = options();
5203 opts.emit = EmitKind::Ir;
5204 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5205 assert!(
5206 messages.iter().any(|m| m.contains("__builtin_abort")),
5207 "expected the written name in {messages:?}"
5208 );
5209 }
5210
5211 #[test]
5219 fn a_builtin_nothing_lowers_is_refused_by_name() {
5220 let mut opts = options();
5221 opts.emit = EmitKind::Ir;
5222 let builtin = "__atomic_signal_fence";
5223 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5224 let messages = run(&opts, &source).messages;
5225 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5226 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5227 }
5228
5229 #[test]
5238 fn what_is_refused_is_the_call_and_not_the_name() {
5239 let text = ir(concat!(
5240 "void __atomic_signal_fence(int order) { (void)order; }\n",
5241 "void f(void) { __atomic_signal_fence(5); }\n",
5242 ));
5243 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5244 }
5245
5246 #[test]
5255 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5256 let text = ir(concat!(
5257 "struct S { char a[8]; int n; char b[12]; };\n",
5258 "char g[32];\n",
5259 "struct S gs;\n",
5260 "unsigned long whole = __builtin_object_size(g, 0);\n",
5261 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5262 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5263 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5264 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5265 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5266 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5267 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5268 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5269 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5270 ));
5271 for (name, size) in [
5272 ("whole", 32),
5273 ("moved", 28),
5274 ("back", 4),
5275 ("outer", 24),
5276 ("inner", 8),
5277 ("scalar", 4),
5278 ("after", 16),
5279 ("into", 10),
5280 ("text", 6),
5281 ("dyn", 12),
5282 ] {
5283 let said = format!("global @{name} : i64 = {size},");
5284 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5285 }
5286 }
5287
5288 #[test]
5296 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5297 let text = body(concat!(
5298 "struct S { char a[8]; int n; char b[12]; };\n",
5299 "unsigned long f(void) {\n",
5300 " char loc[20];\n",
5301 " struct S ls;\n",
5302 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5303 "}\n",
5304 ));
5305 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5306 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5307 }
5308
5309 #[test]
5319 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5320 let text = ir(concat!(
5321 "struct T { int n; char f[]; };\n",
5322 "extern char *p;\n",
5323 "extern struct T *t;\n",
5324 "unsigned long largest = __builtin_object_size(p, 0);\n",
5325 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5326 "unsigned long least = __builtin_object_size(p, 2);\n",
5327 "unsigned long tight = __builtin_object_size(p, 3);\n",
5328 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5329 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5330 ));
5331 for name in ["largest", "nearest", "flex"] {
5332 let said = format!("global @{name} : i64 = -1,");
5336 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5337 }
5338 for name in ["least", "tight"] {
5339 let said = format!("global @{name} : i64 = 0,");
5340 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5341 }
5342 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5343 }
5344
5345 #[test]
5352 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5353 let text = body(concat!(
5354 "extern char *side(void);\n",
5355 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5356 ));
5357 assert!(!text.contains("call"), "nothing is called: {text}");
5358 }
5359
5360 #[test]
5365 fn a_kind_that_is_not_one_of_the_four_is_refused() {
5366 for source in [
5367 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
5368 + "{ return __builtin_object_size(p, k); }\n",
5369 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
5370 .to_owned(),
5371 "extern char *p;\nunsigned long f(void) ".to_owned()
5372 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
5373 ] {
5374 let messages = errors(&source);
5375 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
5376 assert!(named, "expected a complaint about the kind in {messages:?}");
5377 }
5378 }
5379
5380 #[test]
5386 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
5387 let text = ir(concat!(
5388 "void *buf[5];\n",
5389 "int f(void) {\n",
5390 " if (__builtin_setjmp(buf)) return 2;\n",
5391 " return 1;\n",
5392 "}\n",
5393 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5394 ));
5395 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5396 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5397 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5398 }
5399
5400 #[test]
5408 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5409 let text = ir(concat!(
5410 "void *buf[5];\n",
5411 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5412 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5413 ));
5414 let (saves, plain) = text.split_once("func @g").expect("both functions");
5415 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5416 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5417 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5418 }
5419
5420 #[test]
5429 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5430 let text =
5431 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5432 let body = text.split_once("\nf:\n").expect("the function").1;
5433 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5434 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5435 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5436 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5437 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5438 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5439 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5440 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5441 }
5442
5443 #[test]
5451 fn a_save_destroys_every_register_the_allocator_hands_out() {
5452 let text =
5453 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5454 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5455 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5456 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5457 }
5458 }
5459
5460 #[test]
5467 fn the_restore_puts_the_frame_back_before_it_jumps() {
5468 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5469 let mut opts = options();
5470 opts.emit = EmitKind::Asm;
5471 opts.opt_level = level;
5472 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5473 let result = run(&opts, source);
5474 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5475 let text = result.text().to_owned();
5476 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5477 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5478 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5479 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5480 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5481 }
5482 }
5483
5484 #[test]
5490 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5491 for source in [
5492 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5493 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5494 ] {
5495 let messages = errors(source);
5496 let named = messages.iter().any(|m| m.contains("E0710"));
5497 assert!(named, "expected a complaint about the value in {messages:?}");
5498 }
5499 }
5500
5501 #[test]
5506 fn a_static_function_nothing_refers_to_is_not_emitted() {
5507 let text = ir("static int dropped(void) { return 1; }\n\
5508 static int kept(void) { return 2; }\n\
5509 int main(void) { return kept(); }\n");
5510 assert!(text.contains("func @kept"), "{text}");
5511 assert!(!text.contains("dropped"), "{text}");
5512 }
5513
5514 #[test]
5520 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5521 let text = ir("static int ping(void);\n\
5522 static int pong(void) { return ping(); }\n\
5523 static int ping(void) { return pong(); }\n\
5524 int main(void) { return 0; }\n");
5525 assert!(!text.contains("ping"), "{text}");
5526 assert!(!text.contains("pong"), "{text}");
5527 }
5528
5529 #[test]
5535 fn naming_a_static_function_anywhere_keeps_it() {
5536 let text = ir("static int by_address(void) { return 1; }\n\
5537 static int in_an_image(void) { return 2; }\n\
5538 static int deeper(void) { return 3; }\n\
5539 static int reaches_deeper(void) { return deeper(); }\n\
5540 static int (*table[1])(void) = {in_an_image};\n\
5541 int main(void) {\n\
5542 int (*p)(void) = by_address;\n\
5543 return p() + table[0]() + reaches_deeper();\n\
5544 }\n");
5545 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5546 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5547 }
5548 }
5549
5550 #[test]
5556 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5557 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5558 let source = format!(
5559 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5560 int main(void) {{ return 0; }}\n"
5561 );
5562 let text = ir(&source);
5563 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5564 }
5565 }
5566
5567 #[test]
5570 fn a_function_anything_could_call_is_emitted_without_being_called() {
5571 let text =
5572 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5573 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5574 }
5575
5576 #[test]
5583 fn a_classification_c_has_an_operator_for_is_that_operator() {
5584 for (builtin, operator) in [
5585 ("__builtin_isgreater", "binary >"),
5586 ("__builtin_isgreaterequal", "binary >="),
5587 ("__builtin_isless", "binary <"),
5588 ("__builtin_islessequal", "binary <="),
5589 ] {
5590 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5591 let text = tast(&source);
5592 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5593 }
5594 }
5595
5596 #[test]
5605 fn the_classification_builtins_are_comparisons_and_not_calls() {
5606 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5607 assert_eq!(
5608 text,
5609 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5610 %2\n return %3\n"
5611 );
5612
5613 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5615 assert!(text.contains("fcmp one %0, %1"), "{text}");
5616
5617 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5618 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5619
5620 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5621 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5622 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5623 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5624 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5625 assert!(text.contains("%5 = or %3, %4"), "{text}");
5626
5627 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5630 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5631 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5632 assert!(text.contains("%5 = and %3, %4"), "{text}");
5633
5634 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5635 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5636 assert!(text.contains("icmp slt %1, %2"), "{text}");
5637
5638 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5641 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5642
5643 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5646 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5647 }
5648
5649 #[test]
5656 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5657 let text = ir(concat!(
5658 "int a = __builtin_isinff(1e300);\n",
5659 "int b = __builtin_isinf(1e300);\n",
5660 "int c = __builtin_isnan(0.0);\n",
5664 "int d = __builtin_signbit(-0.0);\n",
5665 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5666 ));
5667 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5668 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5669 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5670 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5671 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5672 }
5673
5674 #[test]
5676 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5677 let mut opts = options();
5678 opts.emit = EmitKind::Ir;
5679 let source = concat!(
5680 "int a(int x) { return __builtin_isnan(x); }\n",
5681 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5682 "int c(double x) { return __builtin_isnan(x, x); }\n",
5683 );
5684 let messages = run(&opts, source).messages;
5685 assert_eq!(
5686 messages,
5687 [
5688 "/main.c:1:23: error: non-floating-point argument in call to function \
5689 '__builtin_isnan' [E0685]",
5690 "/main.c:2:30: error: non-floating-point arguments in call to function \
5691 '__builtin_isunordered' [E0685]",
5692 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5693 ]
5694 );
5695 }
5696
5697 #[test]
5706 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5707 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5708 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5712 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5713 assert!(text.contains("%3 = and %1, %2"), "{text}");
5714 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5715 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5716 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5717 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5718 assert!(text.contains("%8 = and %6, %7"), "{text}");
5719
5720 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5724 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5725 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5726
5727 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5728 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5729 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5730 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5731
5732 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5733 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5734 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5735 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5739 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5740 assert!(!text.contains("call"), "{text}");
5741
5742 let text = body(concat!(
5745 "double g(void);\n",
5746 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5747 ));
5748 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5749 }
5750
5751 #[test]
5758 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5759 let text = ir(concat!(
5760 "int a = __builtin_isnormal(1.0);\n",
5761 "int b = __builtin_isnormal(0.0);\n",
5762 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5763 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5764 "int e = __builtin_isinf_sign(1.0);\n",
5765 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5766 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5767 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5768 ));
5769 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5770 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5771 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5772 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5773 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5774 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5775 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5776 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5777 }
5778
5779 #[test]
5785 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5786 let mut opts = options();
5787 opts.emit = EmitKind::Ir;
5788 let source = concat!(
5789 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5790 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5791 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5792 );
5793 let messages = run(&opts, source).messages;
5794 assert_eq!(
5795 messages,
5796 [
5797 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5798 '__builtin_fpclassify' [E0687]",
5799 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5800 [E0511]",
5801 "/main.c:3:23: error: non-floating-point argument in call to function \
5802 '__builtin_fpclassify' [E0685]",
5803 ]
5804 );
5805 }
5806
5807 #[test]
5815 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5816 let text = ir(concat!(
5817 "double a = __builtin_inf();\n",
5818 "float b = __builtin_huge_valf();\n",
5819 "long double c = __builtin_infl();\n",
5820 "double d = __builtin_huge_val();\n",
5821 ));
5822 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5823 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5824 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5825 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5826 assert!(!text.contains("call"), "{text}");
5827 }
5828
5829 #[test]
5838 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5839 let text = ir(concat!(
5840 "double a = __builtin_nan(\"\");\n",
5841 "double b = __builtin_nan(\"0x1\");\n",
5842 "double c = __builtin_nan(\"010\");\n",
5844 "double d = __builtin_nans(\"\");\n",
5845 "double e = __builtin_nans(\"0x1\");\n",
5846 "float f = __builtin_nanf(\"0x1\");\n",
5847 "float g = __builtin_nansf(\"\");\n",
5848 "long double h = __builtin_nansl(\"\");\n",
5849 ));
5850 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5851 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5852 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5853 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5854 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5855 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5856 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5857 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5858
5859 let text = ir(concat!(
5862 "double f(const char *p) { return __builtin_nan(p); }\n",
5863 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5864 ));
5865 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5866 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5867 }
5868
5869 #[test]
5877 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5878 let text = ir(concat!(
5879 "unsigned long a = __builtin_strlen(\"hello\");\n",
5880 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5881 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5882 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5883 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5884 ));
5885 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5886 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5887 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5888 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5889 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5890 assert!(!text.contains("call"), "{text}");
5891
5892 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5894 assert!(text.contains("call @strlen("), "{text}");
5895 }
5896
5897 #[test]
5904 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5905 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5906 assert!(text.contains("bitcast.i64 %0"), "{text}");
5907 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5908 assert!(text.contains("and %1, %2"), "{text}");
5909 assert!(text.contains("bitcast.f64 %3"), "{text}");
5910 assert!(!text.contains("call"), "{text}");
5911
5912 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5913 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5914 assert!(text.contains("%8 = or %4, %7"), "{text}");
5915 assert!(!text.contains("call"), "{text}");
5916
5917 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5920 assert!(text.contains("bitcast.i80 %0"), "{text}");
5921 assert!(text.contains("bitcast.f80"), "{text}");
5922
5923 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5926 assert!(text.contains("fpext.f64 %0"), "{text}");
5927 assert!(text.contains("bitcast.i64 %1"), "{text}");
5928 }
5929
5930 #[test]
5939 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5940 let text =
5941 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5942 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5943 assert!(!text.contains("call"), "{text}");
5944
5945 let text =
5946 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5947 assert!(text.contains("bitcast.i32 %0"), "{text}");
5948 assert!(!text.contains("call"), "{text}");
5949
5950 let text = body(concat!(
5951 "double copysign(double x, double y);\n",
5952 "double f(double x, double y) { return copysign(x, y); }\n",
5953 ));
5954 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5955 assert!(!text.contains("call"), "{text}");
5956
5957 let text = body(concat!(
5958 "float copysignf(float x, float y);\n",
5959 "float f(float x, float y) { return copysignf(x, y); }\n",
5960 ));
5961 assert!(!text.contains("call"), "{text}");
5962
5963 let text = ir(concat!(
5967 "long double fabsl(long double x);\n",
5968 "long double f(long double x) { return fabsl(x); }\n",
5969 ));
5970 assert!(text.contains("call @fabsl"), "{text}");
5971 }
5972
5973 #[test]
5981 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5982 let taken = concat!(
5983 "static double fabs(double b) { return 7; }\n",
5984 "double f(double x) { return fabs(x); }\n",
5985 );
5986 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5987
5988 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5989 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5990
5991 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
5992 let mut opts = options();
5993 opts.emit = EmitKind::Ir;
5994 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
5995
5996 opts.builtins = false;
5997 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
5998
5999 opts.builtins = true;
6000 opts.no_builtin = vec!["fabs".to_owned()];
6001 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
6002 let one = concat!(
6003 "double copysign(double a, double b);\n",
6004 "double f(double x) { return copysign(x, 1.0); }\n",
6005 );
6006 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
6007
6008 opts.no_builtin = Vec::new();
6010 opts.builtins = false;
6011 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
6012 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
6013 }
6014
6015 #[test]
6024 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
6025 let text = ir(concat!(
6026 "double a = __builtin_fabs(-3.5);\n",
6027 "double b = __builtin_copysign(1.0, -0.0);\n",
6028 "double c = __builtin_copysign(0.0, -2.0);\n",
6029 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
6031 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
6032 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
6033 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
6034 "long double i = __builtin_fabsl(-__builtin_infl());\n",
6035 ));
6036 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6037 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6038 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6039 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6040 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6041 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6042 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6043 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6044 }
6045
6046 #[test]
6054 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6055 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6056 assert!(!text.contains("call"), "{text}");
6057 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6058 assert!(!text.contains("call"), "{text}");
6059
6060 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6063 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6064 assert!(!text.contains("call"), "{text}");
6065 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6066 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6067
6068 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6071 assert_eq!(written, text, "the name and the operator are the same thing");
6072
6073 let text = body(concat!(
6075 "double creal(_Complex double z);\n",
6076 "double f(_Complex double z) { return creal(z); }\n",
6077 ));
6078 assert!(!text.contains("call"), "{text}");
6079 let text = body(concat!(
6080 "_Complex float conjf(_Complex float z);\n",
6081 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6082 ));
6083 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6084 assert!(!text.contains("call"), "{text}");
6085
6086 let taken = concat!(
6089 "static double creal(_Complex double z) { return 7; }\n",
6090 "double f(_Complex double z) { return creal(z); }\n",
6091 );
6092 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6093 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6094 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6095 let plain = concat!(
6096 "double cimag(_Complex double z);\n",
6097 "double f(_Complex double z) { return cimag(z); }\n",
6098 );
6099 let mut opts = options();
6100 opts.emit = EmitKind::Ir;
6101 opts.builtins = false;
6102 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6103 opts.builtins = true;
6104 opts.no_builtin = vec!["cimag".to_owned()];
6105 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6106
6107 let text = ir(concat!(
6109 "double a = __builtin_creal(1.5 + 2.5i);\n",
6110 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6111 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6112 ));
6113 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6114 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6115 assert!(
6116 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6117 "the conjugate of a constant is the constant with the second half negated: {text}"
6118 );
6119 assert!(!text.contains("call"), "{text}");
6120 }
6121
6122 #[test]
6130 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6131 let text = ir(concat!(
6132 "double a = __builtin_ceil(1.5);\n",
6133 "double b = __builtin_floor(1.5);\n",
6134 "double c = __builtin_trunc(-1.5);\n",
6135 "double d = __builtin_round(2.5);\n",
6138 "double e = __builtin_ceil(-0.5);\n",
6140 "double f = __builtin_fmax(1.0, 2.0);\n",
6141 "double g = __builtin_fmin(1.0, 2.0);\n",
6142 "float h = __builtin_ceilf(1.25f);\n",
6143 "double ceil(double x);\n",
6146 "double i = ceil(2.25);\n",
6147 ));
6148 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6149 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6150 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6151 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6152 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6153 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6154 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6155 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6156 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6157 assert!(!text.contains("call"), "{text}");
6158 }
6159
6160 #[test]
6168 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6169 let text = ir(concat!(
6170 "double f(double x) { return __builtin_ceil(x); }\n",
6171 "float g(float x) { return __builtin_floorf(x); }\n",
6172 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6173 ));
6174 assert!(text.contains("call @ceil("), "{text}");
6175 assert!(text.contains("call @floorf("), "{text}");
6176 assert!(text.contains("call @fmax("), "{text}");
6177
6178 let text = ir(concat!(
6182 "double f(void) { return __builtin_rint(2.5); }\n",
6183 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6184 ));
6185 assert!(text.contains("call @rint("), "{text}");
6186 assert!(text.contains("call @nearbyint("), "{text}");
6187
6188 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6191 assert!(text.contains("call @fmin("), "{text}");
6192
6193 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6196 let mut opts = options();
6197 opts.emit = EmitKind::Ir;
6198 opts.no_builtin = vec!["ceil".to_owned()];
6199 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6200 }
6201
6202 #[test]
6209 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6210 let text = ir(concat!(
6211 "constexpr int side = 4;\n",
6212 "constexpr int wider = side + 1;\n",
6213 "constexpr double half = 1.5;\n",
6214 "struct point { int x; int y; };\n",
6215 "constexpr struct point origin = { 5, 6 };\n",
6216 "int square[side * side];\n",
6217 "int rectangle[wider];\n",
6218 "int rounded[(int)half * 2];\n",
6219 "int across[origin.y];\n",
6220 "enum named { four = side };\n",
6221 "int e = four;\n",
6222 ));
6223 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6224 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6225 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6226 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6227 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6228
6229 let mut opts = options();
6232 opts.emit = EmitKind::Ir;
6233 let konst = "const int n = 1;\nint a[n];\n";
6234 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6235 assert_eq!(run(&opts, konst).messages, [message]);
6236
6237 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6239 assert_eq!(run(&opts, subscript).messages, [message]);
6240
6241 let address = "constexpr int c = 3;\nint *p = &c;\n";
6243 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6244 pointer target type [E0514]";
6245 assert_eq!(run(&opts, address).messages, [warning]);
6246 }
6247
6248 #[test]
6263 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
6264 let mut opts = options();
6265 opts.emit = EmitKind::Ir;
6266 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
6267
6268 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
6270 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
6271
6272 let plain = concat!(
6275 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
6276 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
6277 );
6278 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
6279
6280 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
6283 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
6284 [E0514]";
6285 assert_eq!(run(&opts, &dropping).messages, [warning]);
6286
6287 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
6290 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
6291 incompatible return type 'const unsigned int (*)[4]' [E0512]";
6292 assert_eq!(run(&opts, wrong).messages, [error]);
6293 }
6294
6295 #[test]
6304 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
6305 let mut opts = options();
6308 opts.std = Std::C17;
6309 let source = concat!(
6310 "int add(a, b)\n",
6311 "int a;\n",
6312 "int b;\n",
6313 "{ return a + b; }\n",
6314 "int promoted(c)\n",
6315 "char c;\n",
6316 "{ return c; }\n",
6317 "int narrow(char);\n",
6318 "int narrow(c)\n",
6319 "char c;\n",
6320 "{ return c; }\n",
6321 "int first(a)\n",
6322 "int a[4];\n",
6323 "{ return a[0]; }\n",
6324 );
6325 let result = run(&opts, source);
6326 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6327 let text = result.text();
6328 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6329 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6330 assert!(text.contains("c : char object automatic defined"), "{text}");
6332 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
6333 assert!(text.contains("first : int(int *) function external defined"), "{text}");
6335 }
6336
6337 #[test]
6344 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
6345 let mut opts = options();
6346 opts.std = Std::C17;
6347 for (source, message) in [
6348 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
6349 (
6350 "int f(a)\nint a;\nint b;\n{ return a; }\n",
6351 "3:5: error: declaration for parameter 'b' but no such parameter",
6352 ),
6353 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
6354 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
6355 (
6356 "int f(a)\nstatic int a;\n{ return a; }\n",
6357 "2:12: error: storage class specified for parameter 'a'",
6358 ),
6359 (
6360 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
6361 "2:7: error: argument 'a' doesn't match prototype",
6362 ),
6363 ] {
6364 let result = run(&opts, source);
6365 assert!(result.failed(), "expected this to fail:\n{source}");
6366 assert!(result.messages[0].contains(message), "{:?}", result.messages);
6367 }
6368
6369 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
6372 let mut older = options();
6373 older.std = Std::C89;
6374 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
6375 let result = run(&opts, implicit);
6376 assert!(
6377 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
6378 "{:?}",
6379 result.messages
6380 );
6381
6382 let mut newer = options();
6386 newer.std = Std::C23;
6387 let plain = "int f(a)\nint a;\n{ return a; }\n";
6388 let result = run(&newer, plain);
6389 assert!(!result.failed(), "{:?}", result.messages);
6390 assert_eq!(
6391 result.messages,
6392 ["/main.c:1:5: warning: old-style function definition [E0412]"]
6393 );
6394 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
6395 }
6396
6397 #[test]
6404 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
6405 let array = "int a[8] = { [3] 7 };\n";
6406 let member = "struct s { int x; } v = { x: 7 };\n";
6407 for source in [array, member] {
6408 let result = run(&options(), source);
6409 assert!(!result.failed(), "{:?}", result.messages);
6410 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
6411 }
6412
6413 let mut asked = options();
6414 asked.pedantic = true;
6415 assert_eq!(
6416 run(&asked, array).messages,
6417 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
6418 );
6419 assert_eq!(
6420 run(&asked, member).messages,
6421 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
6422 );
6423 }
6424
6425 #[test]
6432 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
6433 let text = ir(concat!(
6434 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
6435 "struct brim { char buf[9223372036854775807L]; };\n",
6436 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
6437 "unsigned long h = sizeof(struct huge_struct);\n",
6438 "unsigned long b = sizeof(struct brim);\n",
6439 "unsigned long y = sizeof(struct bitty);\n",
6440 ));
6441 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6442 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6443 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6444
6445 let mut opts = options();
6446 opts.emit = EmitKind::Ir;
6447 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6448 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6449 assert_eq!(run(&opts, over).messages, [message]);
6450 let array = "struct wide { short buf[1L << 62]; };\n";
6451 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6452 maximum object size '9223372036854775807' [E0537]";
6453 assert_eq!(run(&opts, array).messages[0], message);
6454 }
6455
6456 fn compile_bytes(source: &[u8]) -> Compiled {
6461 let mut opts = options();
6462 opts.emit = EmitKind::Ir;
6463 let mut fs = MemoryFileSystem::new();
6464 fs.insert("/main.c", source.to_vec());
6465 compile(&opts, "/main.c", &fs)
6466 }
6467
6468 #[test]
6475 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6476 let mut source = b"char s[] = \"a".to_vec();
6477 source.push(0xff);
6478 source.extend_from_slice(b"b\";\nchar c = '");
6479 source.push(0xff);
6480 source.extend_from_slice(b"';\n");
6481 let result = compile_bytes(&source);
6482 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6483 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6484 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6486
6487 let mut stray = b"int a".to_vec();
6488 stray.push(0xff);
6489 stray.extend_from_slice(b" = 1;\n");
6490 let result = compile_bytes(&stray);
6491 assert!(
6492 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6493 "{:?}",
6494 result.messages
6495 );
6496 }
6497
6498 #[test]
6499 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6500 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6501 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6502 let expected = "\
6503func @add(i32, i32) -> i32, linkage(external) {
6504block0(%0: i32, %1: i32):
6505 %2 = add.nsw %0, %1
6506 return %2
6507}
6508";
6509 assert!(text.contains(expected), "{text}");
6510 }
6511
6512 #[test]
6513 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6514 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6515 assert!(!text.contains("alloca"), "{text}");
6516 assert!(!text.contains("load"), "{text}");
6517 assert!(!text.contains("store"), "{text}");
6518 }
6519
6520 #[test]
6521 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6522 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6523 let expected = "\
6524block0:
6525 %0 = alloca, size 4, align 4
6526 %1 = iconst.i32 1
6527 store %1 -> %0, align 4, tbaa !1
6528 %2 = call @g(%0) : (ptr) -> i32
6529 return %2
6530";
6531 assert_eq!(text, expected);
6532 }
6533
6534 #[test]
6535 fn a_loop_carries_what_it_changes_as_block_parameters() {
6536 let text = body(
6539 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6540 return total;\n}\n",
6541 );
6542 assert!(!text.contains("alloca"), "{text}");
6543 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6544 assert!(text.contains("jump block1("), "{text}");
6545 }
6546
6547 #[test]
6548 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6549 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6550 assert!(text.contains("icmp slt %0, %1"), "{text}");
6551 assert!(!text.contains("zext"), "{text}");
6552 }
6553
6554 #[test]
6555 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6556 let text = body("int f(int a, int b) { return a && b; }\n");
6557 let expected = "\
6558block0(%0: i32, %1: i32):
6559 %2 = iconst.i32 0
6560 %3 = icmp ne %0, %2
6561 %4 = iconst.i1 0
6562 br_if %3, block1, block2(%4)
6563
6564block1:
6565 %5 = iconst.i32 0
6566 %6 = icmp ne %1, %5
6567 jump block2(%6)
6568
6569block2(%7: i1):
6570 %8 = zext.i32 %7
6571 return %8
6572";
6573 assert_eq!(text, expected);
6574 }
6575
6576 #[test]
6577 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6578 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6579 assert!(!text.contains("block3"), "{text}");
6582 assert!(!text.contains("iconst.i32 3"), "{text}");
6583 }
6584
6585 #[test]
6586 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6587 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6588 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6589 assert!(body("int f(void) { }\n").contains("unreachable"));
6590 }
6591
6592 #[test]
6593 fn a_structure_is_copied_rather_than_held_in_a_value() {
6594 let text = body(
6595 "struct point { int x, y; };\n\
6596 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6597 );
6598 assert!(text.contains("memcpy"), "{text}");
6599 }
6600
6601 #[test]
6602 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6603 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6604 assert!(text.contains("memset"), "{text}");
6605 }
6606
6607 #[test]
6608 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6609 let text = body(
6610 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6611 default: r = 4; } return r; }\n",
6612 );
6613 let expected = "\
6614block0(%0: i32):
6615 %1 = iconst.i32 0
6616 switch %0, block1, [1 => block2, 2 => block3(%1)]
6617
6618block1:
6619 %2 = iconst.i32 4
6620 jump block4(%2)
6621
6622block2:
6623 %3 = iconst.i32 1
6624 jump block3(%3)
6625
6626block3(%4: i32):
6627 %5 = iconst.i32 2
6628 %6 = add.nsw %4, %5
6629 jump block4(%6)
6630
6631block4(%7: i32):
6632 return %7
6633";
6634 assert_eq!(text, expected);
6635 }
6636
6637 #[test]
6638 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6639 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6642 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6643 assert!(text.contains("icmp ule"), "{text}");
6644 assert!(!text.contains("switch"), "{text}");
6645 }
6646
6647 #[test]
6648 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6649 let text = body(
6650 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6651 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6652 );
6653 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6656 assert!(text.contains("block5:\n jump block7("), "{text}");
6657 assert!(text.contains("block6:\n jump block8("), "{text}");
6658 }
6659
6660 #[test]
6661 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6662 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6663 }
6664
6665 #[test]
6666 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6667 let text = body(
6672 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6673 return n; }\n",
6674 );
6675 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6678 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6679 assert!(text.contains("block4:\n jump block3("), "{text}");
6680 }
6681
6682 #[test]
6683 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6684 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6687 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6688 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6689 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6690 }
6691
6692 #[test]
6693 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6694 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6695 assert!(!text.contains("alloca"), "{text}");
6699 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6700 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6701 }
6702
6703 #[test]
6704 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6705 let text =
6706 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6707 assert!(!text.contains("alloca"), "{text}");
6708 assert!(text.contains("block1(%2: i32):"), "{text}");
6709 assert!(text.contains("jump block1(%5)"), "{text}");
6710 }
6711
6712 #[test]
6713 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6714 assert_eq!(
6717 body("int f(int x) { return x; spare: return 0; }\n"),
6718 "block0(%0: i32):\n return %0\n"
6719 );
6720 }
6721
6722 #[test]
6723 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6724 let text = body(
6725 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6726 );
6727 assert_eq!(
6730 text,
6731 "\
6732block0(%0: ptr):
6733 %1 = load.i8 %0, align 1
6734 %2 = iconst.i8 3
6735 %3 = ashr %1, %2
6736 %4 = sext.i32 %3
6737 return %4
6738"
6739 );
6740 }
6741
6742 #[test]
6743 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6744 let text =
6748 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6749 assert_eq!(
6750 text,
6751 "\
6752block0(%0: ptr, %1: i32):
6753 %2 = iconst.i32 16777215
6754 %3 = and %1, %2
6755 %4 = trunc.i16 %3
6756 store %4 -> %0, align 2
6757 %5 = iconst.i32 16
6758 %6 = lshr %3, %5
6759 %7 = trunc.i8 %6
6760 %8 = iconst.i64 2
6761 %9 = ptr_add %0, %8
6762 store %7 -> %9, align 1
6763 return
6764"
6765 );
6766 }
6767
6768 #[test]
6769 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6770 let text =
6771 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6772 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6775 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6776 }
6777
6778 #[test]
6779 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6780 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6783 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6784 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6785 }
6786
6787 #[test]
6788 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6789 let text = body(
6793 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6794 );
6795 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6796 }
6797
6798 #[test]
6799 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6800 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6803 assert!(
6804 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6805 "{text}"
6806 );
6807 }
6808
6809 #[test]
6810 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6811 let text = ir(concat!(
6816 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6817 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6818 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6819 "char s[2] = \"hi\";\n",
6820 ));
6821 assert!(
6822 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6823 "{text}"
6824 );
6825 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6826 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6827 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6830 }
6831
6832 #[test]
6833 fn a_definition_takes_a_parameter_it_left_unnamed() {
6834 let text = ir("int f(int a, int) { return a; }\n");
6838 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6839 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6840
6841 let text = ir("int g(int, int n) { return n; }\n");
6844 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6845 }
6846
6847 #[test]
6848 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6849 let text = body(concat!(
6854 "struct s { int f; int g; };\n",
6855 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6856 "{ *d = *e = a[0] = *c; }\n",
6857 ));
6858 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6859 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6860 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6861 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6862 }
6863
6864 #[test]
6865 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6866 let mut opts = options();
6871 opts.emit = EmitKind::Ir;
6872 let result = run(
6873 &opts,
6874 concat!(
6875 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6876 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6877 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6878 "const union u c = { { \"1234\", \"567\" } };\n",
6879 ),
6880 );
6881 let text = result.text();
6882 assert_eq!(
6883 result.messages,
6884 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6885 (5 chars into 3 available) [E0637]"]
6886 );
6887 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6888 assert!(
6889 text.contains(
6890 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6891 bytes \"9\\00\", zero 3 }"
6892 ),
6893 "{text}"
6894 );
6895 assert!(
6898 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6899 "{text}"
6900 );
6901 }
6902
6903 #[test]
6904 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6905 let text = body(concat!(
6909 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6910 "void g(struct v *);\n",
6911 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6912 ));
6913 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6914 }
6915
6916 #[test]
6917 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6918 let text = ir(concat!(
6923 "struct s { int x; };\n",
6924 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6925 "int n = (int){ 7 };\n",
6926 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6927 ));
6928 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6929 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6930 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6933 }
6934
6935 #[test]
6936 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6937 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6941 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6942 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6943 }
6944
6945 #[test]
6946 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6947 let text = ir("unsigned char foo[1][0];\n");
6951 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6952 }
6953
6954 #[test]
6955 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
6956 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
6959 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
6960 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
6961 }
6962
6963 #[test]
6964 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
6965 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
6969 assert!(
6970 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
6971 "{text}"
6972 );
6973 }
6974
6975 #[test]
6976 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
6977 let text = body(
6982 "\
6983struct s { int a, b; };
6984struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
6985",
6986 );
6987 assert!(text.contains("block3(%7: ptr)"), "{text}");
6989 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
6990 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
6991 }
6992
6993 #[test]
7001 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
7002 let text = body("int f(int i) { return ++i ?: 10; }\n");
7003 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
7004 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7005
7006 let text = body("long f(int i) { return ++i ?: 10L; }\n");
7009 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
7010 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7011
7012 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
7014 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
7015
7016 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
7019 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
7020 }
7021
7022 #[test]
7023 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
7024 let text = ir("\
7028struct pair { int a, b; };
7029struct pair make(int a, int b);
7030struct pair twice(struct pair p) { return make(p.a, p.b); }
7031");
7032 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
7033 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
7034 }
7035
7036 #[test]
7037 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7038 let text = ir("\
7042struct big { double v[8]; };
7043struct big grow(struct big b);
7044struct big twice(struct big b) { return grow(grow(b)); }
7045");
7046 assert!(
7047 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7048 "{text}"
7049 );
7050 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7051 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7054 }
7055
7056 #[test]
7057 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7058 let text = ir("\
7063struct big { double v[8]; };
7064struct pair { int a, b; };
7065int p(const char *, ...);
7066int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7067");
7068 assert!(
7069 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7070 "{text}"
7071 );
7072 }
7073
7074 #[test]
7075 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7076 let body = body(
7079 "\
7080struct pair { int a, b; };
7081struct pair make(int a, int b);
7082int second(void) { return make(1, 2).b; }
7083",
7084 );
7085 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7086 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7087 }
7088
7089 #[test]
7090 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7091 let source = "\
7095struct hfa { float x, y, z; };
7096int take(struct hfa h);
7097int give(struct hfa h) { return take(h); }
7098";
7099 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7100 let mut opts = options();
7101 opts.emit = EmitKind::Ir;
7102 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7103 let result = run(&opts, source);
7104 assert_eq!(result.messages, Vec::<String>::new());
7105 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7106 }
7107
7108 #[test]
7109 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7110 let source = "\
7113int use(int *);
7114void f(int n) {
7115 {
7116 int a[n];
7117 use(a);
7118 }
7119 use(0);
7120}
7121";
7122 let body = body(source);
7123 assert!(body.contains("mul.nsw"), "{body}");
7124 assert!(body.contains("stacksave"), "{body}");
7125 assert!(body.contains("alloca %"), "{body}");
7126 assert!(body.contains("stackrestore"), "{body}");
7127 }
7128
7129 #[test]
7130 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7131 let source = "\
7136int use(int *);
7137int f(int n) {
7138 {
7139 int a[n];
7140 if (use(a)) goto out;
7141 use(0);
7142 }
7143out:
7144 return 0;
7145}
7146";
7147 let body = body(source);
7148 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7150 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7151 assert!(after.starts_with(" %4\n jump block"), "{body}");
7152 }
7153
7154 #[test]
7155 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7156 let source = "\
7160int use(int *);
7161int f(int n) {
7162 int a[n];
7163again:
7164 if (use(a)) goto again;
7165 return 0;
7166}
7167";
7168 let body = body(source);
7169 assert!(body.contains("stacksave"), "{body}");
7170 assert!(!body.contains("stackrestore"), "{body}");
7171 }
7172
7173 #[test]
7174 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7175 let source = "\
7180int use(int *);
7181int f(int n) {
7182again:
7183 {
7184 int a[n];
7185 if (use(a)) goto again;
7186 }
7187 return 0;
7188}
7189";
7190 let body = body(source);
7191 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7192 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7193 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7194 }
7195
7196 #[test]
7197 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
7198 let source = "\
7204int f(void);
7205void t(void) {
7206 int count = 10;
7207 for (; count--;) {
7208 int b[f()];
7209 int i;
7210 for (i = 0; i < f(); i++) {
7211 b[i] = count;
7212 }
7213 }
7214}
7215";
7216 let body = body(source);
7217 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7221 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7222 let next = after.split("\n\n").next().expect("the block the restore is in");
7225 assert!(next.contains("jump block1("), "{body}");
7226 }
7227
7228 #[test]
7229 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
7230 let source = "\
7233unsigned long f(int n) {
7234 int a[n];
7235 n = 0;
7236 return sizeof a;
7237}
7238";
7239 let body = body(source);
7240 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
7242 }
7243
7244 #[test]
7245 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
7246 let source = "\
7249int use(int);
7250int f(int x) {
7251 return ({
7252 int t = use(x);
7253 t * t;
7254 });
7255}
7256";
7257 let expected = "\
7258block0(%0: i32):
7259 %1 = call @use(%0) : (i32) -> i32
7260 %2 = mul.nsw %1, %1
7261 return %2
7262";
7263 assert_eq!(body(source), expected);
7264 }
7265
7266 #[test]
7267 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
7268 let source = "int f(int x) { return ({ return x; 0; }); }\n";
7272 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
7273 }
7274
7275 #[test]
7276 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
7277 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
7281 let expected = "\
7282block0(%0: ptr):
7283 %1 = va_arg.f64 %0
7284 %2 = va_arg.f64 %0
7285 %3 = fadd %1, %2
7286 return %3
7287";
7288 assert_eq!(body(source), expected);
7289 }
7290
7291 #[test]
7292 fn one_that_reads_a_structure_answers_where_the_object_is() {
7293 let source = "\
7307struct s { int a; long b; };
7308long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
7309";
7310 let expected = "\
7311block0(%0: ptr):
7312 %1 = alloca, size 16, align 16
7313 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
7314 memcpy %1, %2, size 16, align 8
7315 %3 = iconst.i64 8
7316 %4 = ptr_add %1, %3
7317 %5 = load.i64 %4, align 8, tbaa !1
7318 return %5
7319";
7320 assert_eq!(body(source), expected);
7321 }
7322
7323 #[test]
7327 fn the_classification_says_which_registers_the_object_arrived_in() {
7328 let source = "\
7329struct s { double a; double b; };
7330double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
7331";
7332 assert!(
7333 body(source)
7334 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
7335 "{}",
7336 body(source)
7337 );
7338
7339 let big = "\
7340struct s { long a[4]; };
7341long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
7342";
7343 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
7344 }
7345
7346 #[test]
7347 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
7348 let source = "\
7352int f(int c) {
7353 void *p = c ? &&one : &&two;
7354 goto *p;
7355one:
7356 return 1;
7357two:
7358 return 2;
7359}
7360";
7361 let expected = "\
7362block0(%0: i32):
7363 %1 = iconst.i32 0
7364 %2 = icmp ne %0, %1
7365 br_if %2, block1, block2
7366
7367block1:
7368 %3 = block_addr block3
7369 jump block4(%3)
7370
7371block2:
7372 %4 = block_addr block5
7373 jump block4(%4)
7374
7375block3:
7376 %5 = iconst.i32 1
7377 return %5
7378
7379block4(%6: ptr):
7380 indirect_br %6, block3, block5
7381
7382block5:
7383 %7 = iconst.i32 2
7384 return %7
7385";
7386 assert_eq!(body(source), expected);
7387 }
7388
7389 #[test]
7390 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
7391 let source = "void **next(void);
7394void f(void) { goto *next(); }
7395";
7396 let expected = "\
7397block0:
7398 %0 = call @next() : () -> ptr
7399 unreachable
7400";
7401 assert_eq!(body(source), expected);
7402 }
7403
7404 #[test]
7405 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
7406 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
7409 let expected = "\
7410block0:
7411 inline_asm.volatile \"mfence\", \"\", \"memory\"()
7412 return
7413";
7414 assert_eq!(body(source), expected);
7415 }
7416
7417 #[test]
7418 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
7419 let source = "\
7422int f(int x, int y) {
7423 int r;
7424 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
7425 return r + y;
7426}
7427";
7428 let expected = "\
7429block0(%0: i32, %1: i32):
7430 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
7431 %4 = add.nsw %2, %3
7432 return %4
7433";
7434 assert_eq!(body(source), expected);
7435 }
7436
7437 #[test]
7438 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
7439 let source = "\
7444struct pair { int a, b; };
7445int f(int x) {
7446 int slot = x;
7447 struct pair p = { x, x };
7448 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7449 return slot + p.a;
7450}
7451";
7452 let text = body(source);
7453 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7454 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7455 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7456 }
7457
7458 #[test]
7459 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7460 let source = "\
7465int f(int x) {
7466 int r = 7;
7467 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7468 return r;
7469away:
7470 return r;
7471}
7472";
7473 let expected = "\
7474block0(%0: i32):
7475 %1 = iconst.i32 7
7476 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7477
7478block1:
7479 return %2
7480
7481block2:
7482 return %1
7483";
7484 assert_eq!(body(source), expected);
7485 }
7486
7487 #[test]
7488 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7489 let mut opts = options();
7493 opts.emit = EmitKind::Ir;
7494 for (source, expected) in [
7495 (
7496 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7497 "output operand constraint lacks '='",
7498 ),
7499 (
7500 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7501 "lvalue required in 'asm' statement",
7502 ),
7503 (
7504 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7505 "read-only variable 'g' used as 'asm' output",
7506 ),
7507 (
7508 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7509 "input operand constraint contains '='",
7510 ),
7511 (
7512 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7513 "memory input 0 is not directly addressable",
7514 ),
7515 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7516 (
7517 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7518 "duplicate asm operand name 'a'",
7519 ),
7520 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7521 ] {
7522 let result = run(&opts, source);
7523 assert!(result.failed(), "expected this to be reported:\n{source}");
7524 assert!(
7525 result.messages.iter().any(|m| m.contains(expected)),
7526 "{expected}\n{:?}",
7527 result.messages
7528 );
7529 }
7530 }
7531
7532 #[test]
7537 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7538 let text = ir(concat!(
7539 "__asm__(\n",
7540 " \".section .rodata\\n\"\n",
7541 " \".globl first\\n\"\n",
7542 " \".balign 8\\n\"\n",
7543 " \"first:\\n\"\n",
7544 " \".long 1\\n\"\n",
7545 " \".long 2\\n\"\n",
7546 " \".globl last\\n\"\n",
7547 " \"last:\\n\"\n",
7548 " \".quad last - first\\n\");\n",
7549 "extern const int first[];\n",
7550 "extern const long last;\n",
7551 ));
7552 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7553 assert!(text.contains("global @last : i64 = 8"), "{text}");
7554 }
7555
7556 #[test]
7560 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7561 let text = ir(concat!(
7562 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7563 "extern int counter;\n",
7564 "int read(void) { return counter; }\n",
7565 ));
7566 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7567 }
7568
7569 #[test]
7572 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7573 let mut opts = options();
7574 opts.emit = EmitKind::Ir;
7575 let mut fs = MemoryFileSystem::new();
7576 fs.insert(
7577 "/main.c",
7578 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7579 );
7580 fs.insert("seed", b"hi".to_vec());
7581 let result = compile(&opts, "/main.c", &fs);
7582 assert_eq!(result.messages, Vec::<String>::new());
7583 let text = result.text();
7584 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7585 }
7586
7587 #[test]
7590 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7591 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7592 assert!(
7593 messages
7594 .iter()
7595 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7596 "{messages:?}"
7597 );
7598 }
7599
7600 #[test]
7603 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7604 for source in [
7605 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7606 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7607 ] {
7608 let messages = errors(source);
7609 assert!(
7610 messages
7611 .iter()
7612 .any(|m| m.contains("not supported yet")
7613 && m.contains("in an `asm` at file scope")),
7614 "{source}\n{messages:?}"
7615 );
7616 }
7617 }
7618
7619 #[test]
7620 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7621 let mut opts = options();
7622 opts.emit = EmitKind::Ir;
7623 for source in [
7624 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7625 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7626 ] {
7627 let result = run(&opts, source);
7628 assert!(result.failed(), "expected this to be reported:\n{source}");
7629 assert!(
7630 result.messages.iter().any(|m| m.contains("not supported yet")),
7631 "{:?}",
7632 result.messages
7633 );
7634 }
7635 }
7636
7637 fn round_trip(source: &str) -> (String, String) {
7639 let printed = ir(source);
7640 let mut opts = options();
7641 opts.emit = EmitKind::Ir;
7642 let mut fs = MemoryFileSystem::new();
7643 fs.insert("/main.ir", printed.clone().into_bytes());
7644 let result = compile_ir(&opts, "/main.ir", &fs);
7645 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7646 (printed, result.text().to_owned())
7647 }
7648
7649 #[test]
7650 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7651 let (printed, again) = round_trip(
7655 "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",
7656 );
7657 assert_eq!(printed, again);
7658 }
7659
7660 #[test]
7661 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7662 let mut opts = options();
7663 opts.emit = EmitKind::Ir;
7664 let mut fs = MemoryFileSystem::new();
7665 let text = "\
7666; ModuleID = 'a.c'
7667; format 0
7668target triple = \"x86_64-unknown-linux-gnu\"
7669target datalayout = \"e-p:64:64-i64:64-S128\"
7670
7671func @f(), linkage(external) {
7672block0:
7673 frobnicate
7674}
7675";
7676 fs.insert("/main.ir", text.as_bytes().to_vec());
7677 let result = compile_ir(&opts, "/main.ir", &fs);
7678 assert!(result.failed());
7679 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7680 }
7681
7682 #[test]
7683 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7684 let mut opts = options();
7687 opts.emit = EmitKind::Ir;
7688 let mut fs = MemoryFileSystem::new();
7689 let text = "\
7690; ModuleID = 'a.c'
7691; format 0
7692target triple = \"x86_64-unknown-linux-gnu\"
7693target datalayout = \"e-p:64:64-i64:64-S128\"
7694
7695func @f(), linkage(external) {
7696block0:
7697 %0 = iconst.i32 1
7698 return %0
7699}
7700";
7701 fs.insert("/main.ir", text.as_bytes().to_vec());
7702 let result = compile_ir(&opts, "/main.ir", &fs);
7703 assert!(result.failed());
7704 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7705 }
7706
7707 #[test]
7708 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7709 let mut fs = MemoryFileSystem::new();
7711 fs.insert("/main.ir", Vec::new());
7712 let result = compile_ir(&options(), "/main.ir", &fs);
7713 assert!(result.failed());
7714 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7715 }
7716
7717 #[test]
7718 fn the_printed_ir_reads_back_as_the_same_module() {
7719 let text = ir("\
7722struct point { int x, y; };
7723static const char greeting[] = \"hi\";
7724int table[4] = { 1, 2, 3 };
7725int puts(const char *);
7726double half(double x) { return x / 2.0; }
7727int f(int n) {
7728 int total = 0;
7729 for (int i = 0; i < n; i++) {
7730 if (i == 3) continue;
7731 total += table[i];
7732 }
7733 switch (n) {
7734 case 0: total = 1;
7735 case 1: total++; break;
7736 default: total = -total;
7737 }
7738 struct point p = { total, 1 };
7739 int *q = &p.y;
7740 puts(greeting);
7741 return p.x + *q;
7742}
7743int dispatch(int c) {
7744 void *p = c ? &&one : &&two;
7745 goto *p;
7746one:
7747 return 1;
7748two:
7749 return 2;
7750}
7751int assembly(int x, int *p) {
7752 int r;
7753 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7754 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7755 return r;
7756away:
7757 return 0;
7758}
7759");
7760 let mut names = Interner::new();
7761 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7762 assert_eq!(rucc_ir::print(&module, &names), text);
7763 }
7764
7765 #[test]
7766 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7767 let mut opts = options();
7771 opts.emit = EmitKind::Object;
7772 opts.save_temps = rucc_session::SaveTemps::Object;
7773 let result = run(&opts, "#define N 2\nint a[N];\n");
7774 assert_eq!(result.messages, Vec::<String>::new());
7775 let text = result.temps.preprocessed.expect("the preprocessed text");
7776 assert!(text.contains("int a[2];"), "{text}");
7777 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7778 let asm = result.temps.assembly.expect("the assembly");
7779 assert!(asm.contains("a:"), "{asm}");
7780 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7781 }
7782
7783 #[test]
7784 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7785 let mut opts = options();
7788 opts.emit = EmitKind::Object;
7789 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7790 }
7791
7792 #[test]
7793 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7794 let mut opts = options();
7797 opts.emit = EmitKind::Ir;
7798 opts.save_temps = rucc_session::SaveTemps::Cwd;
7799 let result = run(&opts, "int a;\n");
7800 assert!(result.temps.preprocessed.is_some());
7801 assert_eq!(result.temps.assembly, None);
7802 }
7803}