1use std::path::Path;
14
15use rucc_base::Interner;
16use rucc_codegen::coverage::Fired;
17use rucc_codegen::elsewhere::Elsewhere;
18use rucc_codegen::lowering::Lowerings;
19use rucc_codegen::pipeline::{self, Machine, Recording};
20use rucc_codegen::pressure::Pressure;
21use rucc_diag::{Diagnostic, Severity, Span};
22use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
23use rucc_lex::{Convert, Keywords, PpToken, convert};
24use rucc_lower::Protector as LowerProtector;
25use rucc_sema::{Checker, Context as CheckContext};
26use rucc_session::{
27 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
28};
29use rucc_target::TargetInfo;
30use rucc_tuple::{Arch, ObjectFormat};
31
32use crate::preprocess::render;
33
34#[derive(Debug, Clone, PartialEq, Eq, Default)]
41pub enum Artifact {
42 #[default]
45 Nothing,
46 Text(String),
48 Object {
55 bytes: Vec<u8>,
57 defines: Vec<String>,
61 },
62}
63
64impl Artifact {
65 #[must_use]
67 pub fn bytes(&self) -> &[u8] {
68 match self {
69 Artifact::Nothing => &[],
70 Artifact::Text(text) => text.as_bytes(),
71 Artifact::Object { bytes, .. } => bytes,
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct Compiled {
79 pub artifact: Artifact,
81 pub messages: Vec<String>,
83 pub errors: u32,
85 pub fired: Fired,
91 pub pressure: Pressure,
96 pub lowerings: Lowerings,
101 pub dumps: Vec<rucc_opt::Dump>,
107 pub remarks: String,
113 pub deps: Vec<rucc_pp::Dependency>,
118 pub temps: Temps,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Default)]
134pub struct Temps {
135 pub preprocessed: Option<String>,
137 pub assembly: Option<String>,
139}
140
141impl Compiled {
142 #[must_use]
144 pub fn failed(&self) -> bool {
145 self.errors > 0
146 }
147
148 #[must_use]
153 pub fn text(&self) -> &str {
154 match &self.artifact {
155 Artifact::Text(text) => text,
156 _ => "",
157 }
158 }
159}
160
161#[must_use]
174pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
175 let mut sess = Session::new(opts.clone());
176 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
180 let mut diagnostics: Vec<Diagnostic> = Vec::new();
181 let mut fired = Fired::new();
183 let mut pressure = Pressure::new();
185 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
186 let mut dumps = Vec::new();
188 let mut remarks = String::new();
189 let mut temps = Temps::default();
191
192 let bytes = match fs.read(Path::new(name)) {
193 Ok(bytes) => bytes,
194 Err(e) => return failure(format!("{name}: {e}")),
195 };
196 let Ok(file) = sess.sources.add_shared(name, bytes, None) else {
197 return failure(format!("{name}: the source map has no room left for this file"));
198 };
199
200 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
204 let predef = rucc_pp::Predef::for_options(opts);
205 let expanded: Vec<PpToken> = {
206 let mut tokens = Vec::new();
207 {
212 let mut cx =
213 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
214 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
215 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
216 return failure(format!(
217 "{name}: the source map has no room for the built in macros"
218 ));
219 }
220 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
221 return failure(format!("{name}: the source map has no room for the command line"));
222 }
223 tokens.append(&mut pp.run(file, &mut cx));
224 }
225 if opts.save_temps.wanted() {
226 temps.preprocessed = Some(rucc_pp::print(
227 file,
228 &tokens,
229 pp.line_directives(),
230 &sess.sources,
231 &sess.interner,
232 rucc_pp::PrintOptions { line_markers: opts.line_markers },
233 ));
234 }
235 tokens.iter().map(|token| token.to_pp()).collect()
236 };
237 diagnostics.extend(pp.take_diagnostics());
238 let deps = pp.dependencies().to_vec();
241
242 let cx = Convert {
245 keywords: &keywords,
246 interner: &sess.interner,
247 target: &sess.target,
248 std: opts.std,
249 gnu: opts.gnu_extensions,
250 pedantic: opts.pedantic,
251 };
252 let (tokens, complaints) = convert(&expanded, &cx);
253 diagnostics.extend(complaints);
254
255 let parsed = rucc_parse::parse(
256 &tokens,
257 rucc_parse::Context {
258 interner: &sess.interner,
259 std: opts.std,
260 gnu: opts.gnu_extensions,
261 pedantic: opts.pedantic,
262 error_limit: opts.error_limit as usize,
263 },
264 );
265 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
266 diagnostics.extend(parsed.diagnostics);
267
268 let mut artifact = Artifact::Nothing;
269 let mut instrumented = Instrumented::default();
272 if !parse_failed {
273 let mut checker = Checker::new(
274 &parsed.ast,
275 CheckContext {
276 names: &sess.interner,
277 target: &sess.target,
278 std: opts.std,
279 gnu: opts.gnu_extensions,
280 pedantic: opts.pedantic,
281 permissive: opts.permissive,
282 gnu89_inline: opts.gnu89_inline,
283 error_limit: opts.error_limit as usize,
284 builtins: opts.builtins && opts.hosted,
287 no_builtin: &opts.no_builtin,
288 short_enums: opts.short_enums,
289 trapping_math: opts.trapping_math,
290 },
291 );
292 checker.check_unit();
293 let checked = checker.finish();
294 if !checked.failed() {
295 match opts.emit {
296 EmitKind::Tast => {
297 artifact = Artifact::Text(rucc_sema::print(
298 &checked.tast,
299 &checked.types,
300 &sess.interner,
301 ));
302 }
303 EmitKind::TypeGranules => {
307 artifact = Artifact::Text(rucc_types::granule_report(
308 &checked.types,
309 &sess.interner,
310 &sess.target,
311 ));
312 }
313 EmitKind::Ir
314 | EmitKind::MirFinal
315 | EmitKind::Asm
316 | EmitKind::Object
317 | EmitKind::Archive
318 | EmitKind::Executable
319 | EmitKind::SafetySummary => {
320 let mut read = |named: &str| {
325 fs.read(Path::new(named))
326 .map(|bytes| bytes.as_slice().to_vec())
327 .map_err(|why| why.to_string())
328 };
329 let mut lowered = rucc_lower::lower(
330 name,
331 rucc_lower::Context {
332 tast: &checked.tast,
333 types: &checked.types,
334 target: &sess.target,
335 names: &mut sess.interner,
336 visibility: match opts.visibility {
337 Visibility::Default => IrVisibility::Default,
338 Visibility::Hidden => IrVisibility::Hidden,
339 Visibility::Protected => IrVisibility::Protected,
340 },
341 protector: match opts.protector {
342 Protector::None => LowerProtector::None,
343 Protector::Buffers => LowerProtector::Buffers,
344 Protector::Strong => LowerProtector::Strong,
345 Protector::All => LowerProtector::All,
346 },
347 wrapping: rucc_lower::Wrapping {
348 signed: opts.wrapping.signed,
349 pointer: opts.wrapping.pointer,
350 trap: opts.wrapping.trap,
351 },
352 aliasing: opts.strict_aliasing,
353 padding: opts.padding == Padding::Ignored,
354 contract: match opts.fp_contract {
355 Contract::Off => FpContract::Off,
356 Contract::On => FpContract::On,
357 Contract::Fast => FpContract::Fast,
358 },
359 read: &mut read,
360 },
361 );
362 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
366 if !failed {
367 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
372 for error in errors {
373 diagnostics.push(internal(&format!("invalid IR, {error}")));
374 }
375 } else if let Err(complaints) =
376 instrument(&mut lowered.module, &mut sess.interner, opts)
377 .map(|done| instrumented = done)
378 {
379 diagnostics.extend(complaints);
380 } else if let Err(complaints) = optimize(
381 &mut lowered.module,
382 &sess.interner,
383 &sess.target,
384 opts,
385 name,
386 &mut dumps,
387 &mut remarks,
388 ) {
389 diagnostics.extend(complaints);
390 } else if opts.emit == EmitKind::SafetySummary {
391 artifact = Artifact::Text(
396 rucc_safety::summarize(
397 &lowered.module,
398 &sess.interner,
399 name,
400 opts.safety.as_str(),
401 instrumented.checks,
402 instrumented.interposed,
403 instrumented.crossings,
404 )
405 .render(),
406 );
407 } else if opts.emit == EmitKind::Ir {
408 artifact =
413 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
414 } else {
415 match generate(
418 &mut lowered.module,
419 &mut sess.interner,
420 &sess.target,
421 opts,
422 &mut Recording {
423 fired: &mut fired,
424 pressure: &mut pressure,
425 lowerings: &mut lowerings,
426 },
427 &mut temps.assembly,
428 ) {
429 Ok(made) => artifact = made,
430 Err(complaints) => diagnostics.extend(complaints),
431 }
432 }
433 }
434 diagnostics.extend(lowered.diagnostics);
435 }
436 _ => {}
437 }
438 }
439 diagnostics.extend(checked.diagnostics);
440 }
441
442 let mut messages = Vec::with_capacity(diagnostics.len());
443 let mut errors = 0;
444 for diag in &diagnostics {
445 if !opts.warnings && diag.severity == Severity::Warning {
449 continue;
450 }
451 if diag.severity.is_fatal()
452 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
453 {
454 errors += 1;
455 }
456 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
457 }
458 if errors > 0 {
459 artifact = Artifact::Nothing;
461 }
462 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
465}
466
467#[must_use]
477pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
478 let mut sess = Session::new(opts.clone());
479 if opts.emit != EmitKind::Ir {
480 return failure(format!(
481 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
482 the C in front of it became",
483 opts.emit.as_str()
484 ));
485 }
486 let bytes = match fs.read(Path::new(name)) {
487 Ok(bytes) => bytes,
488 Err(e) => return failure(format!("{name}: {e}")),
489 };
490 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
491 return failure(format!("{name}: this is not text, so it is not IR"));
492 };
493
494 let module = match rucc_ir::parse(text, &mut sess.interner) {
495 Ok(module) => module,
496 Err(error) => {
497 return failure(format!("{name}:{}: {}", error.line, error.message));
498 }
499 };
500 let mut diagnostics: Vec<Diagnostic> = Vec::new();
501 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
502 for error in errors {
503 diagnostics.push(invalid(&format!("invalid IR, {error}")));
504 }
505 }
506 let mut messages = Vec::with_capacity(diagnostics.len());
507 for diag in &diagnostics {
508 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
509 }
510 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
511 let artifact = if errors > 0 {
512 Artifact::Nothing
513 } else {
514 Artifact::Text(rucc_ir::print(&module, &sess.interner))
515 };
516 Compiled {
518 artifact,
519 messages,
520 errors,
521 fired: Fired::new(),
522 pressure: Pressure::new(),
523 lowerings: Lowerings::new(),
524 dumps: Vec::new(),
525 remarks: String::new(),
526 deps: Vec::new(),
527 temps: Temps::default(),
528 }
529}
530
531fn instrument(
554 module: &mut rucc_ir::Module,
555 names: &mut Interner,
556 opts: &Options,
557) -> Result<Instrumented, Vec<Diagnostic>> {
558 if !opts.safety.instruments() {
559 return Ok(Instrumented::default());
560 }
561 let checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
562 let interposed = rucc_safety::redirect(module, names);
567 let crossings = rucc_safety::witness(module, names);
570 match rucc_ir::verify(module, names) {
571 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
572 Err(errors) => Err(errors
573 .iter()
574 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
575 .collect()),
576 }
577}
578
579#[derive(Clone, Copy, Debug, Default)]
585struct Instrumented {
586 checks: rucc_safety::Counts,
588 interposed: usize,
590 crossings: rucc_safety::Sites,
592}
593
594fn optimize(
606 module: &mut rucc_ir::Module,
607 names: &Interner,
608 target: &TargetInfo,
609 opts: &Options,
610 file: &str,
611 dumps: &mut Vec<rucc_opt::Dump>,
612 remarks: &mut String,
613) -> Result<(), Vec<Diagnostic>> {
614 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
615 settings.interposition = match opts.interposition {
621 true => replaceable(target, opts),
622 false => IrPic::Executable,
623 };
624 settings.toggles.clone_from(&opts.passes);
625 settings.fuel = opts.pass_fuel.iter().cloned().collect();
626 settings.global_fuel = opts.pass_fuel_global;
627 settings.verify |= opts.verify_each;
628 for (on, spec) in &opts.pass_gates {
629 if let Err(why) = settings.gates.add(*on, spec) {
632 return Err(vec![internal(&why)]);
633 }
634 }
635 for spec in &opts.dump_ir {
636 if let Err(why) = settings.dumps.add(spec) {
639 return Err(vec![internal(&why)]);
640 }
641 }
642 let mut wants = rucc_opt::Wants::none();
643 for spec in &opts.opt_info {
644 if let Err(why) = wants.add(spec) {
647 return Err(vec![internal(&why)]);
648 }
649 }
650 let report = rucc_opt::run(module, names, &settings);
651 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
652 dumps.extend(report.dumps);
653 match report.broke.is_empty() {
654 true => Ok(()),
655 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
656 }
657}
658
659fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
693 match (target.tuple.os().object_format(), opts.pic) {
694 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
695 _ => IrPic::Executable,
696 }
697}
698
699fn generate(
700 module: &mut rucc_ir::Module,
701 names: &mut Interner,
702 target: &TargetInfo,
703 opts: &Options,
704 recording: &mut Recording<'_>,
705 assembly: &mut Option<String>,
706) -> Result<Artifact, Vec<Diagnostic>> {
707 let Some(machine) = Machine::for_target(target) else {
708 return Err(vec![unsupported(&format!(
709 "there is no back end for {} in this compiler yet, so there is nothing to generate",
710 target.tuple
711 ))]);
712 };
713 if opts.protector != Protector::None && machine.conv.guard.is_none() {
718 return Err(vec![unsupported(&format!(
719 "{} is not supported for {} yet, because the stack protector on that target is not \
720 the one this compiler writes",
721 opts.protector, target.tuple
722 ))]);
723 }
724 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
730 return Err(vec![unsupported(&format!(
731 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
732 for it there is not the note this compiler writes",
733 opts.control, target.tuple
734 ))]);
735 }
736 let profile = match machine.conv.trace {
742 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
743 None if opts.profile => {
744 return Err(vec![unsupported(&format!(
745 "-pg is not supported for {} yet, because the profiler's hook on that target is \
746 not the one this compiler calls",
747 target.tuple
748 ))]);
749 }
750 None => None,
751 };
752 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
757 return Err(vec![unsupported(&format!(
758 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
759 the room is there is not the section this compiler writes",
760 target.tuple
761 ))]);
762 }
763 let flags = pipeline::Flags {
764 frame_pointer: opts.frame_pointer,
765 red_zone: opts.red_zone,
766 stack_clash: opts.stack_clash,
767 landing: opts.control.branch(),
768 profile: match profile {
769 None => pipeline::Profile::No,
770 Some(true) => pipeline::Profile::Early,
771 Some(false) => pipeline::Profile::Late,
772 },
773 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
774 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
781 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
786 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
791 accurate: opts.cycle_accurate_model,
793 };
794
795 if opts.safety.instruments() {
804 rucc_opt::heap::annotate(module, names);
814 rucc_safety::handover::arrange(module);
821 rucc_safety::lower(module, names);
822 if let Err(errors) = rucc_ir::verify(module, names) {
823 return Err(errors
824 .iter()
825 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
826 .collect());
827 }
828 }
829
830 let elsewhere = Elsewhere::of(module, replaceable(target, opts));
838
839 let mut funcs = Vec::new();
840 let mut complaints = Vec::new();
841 for id in module.funcs() {
842 if module[id].is_declaration() {
843 continue;
844 }
845 match pipeline::compile_recording(
846 &mut module[id],
847 names,
848 &machine,
849 &elsewhere,
850 flags,
851 recording,
852 ) {
853 Ok(func) => funcs.push(func),
854 Err(why) => {
855 let name = names.resolve(module[id].name).to_owned();
856 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
859 let said = format!("cannot generate code for '{name}': {why}");
860 complaints.push(unsupported_at(&said, span));
861 }
862 }
863 }
864 if !complaints.is_empty() {
865 return Err(complaints);
866 }
867 let (globals, aliases) = match opts.emit {
873 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
874 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
875 rucc_asm::aliases(module, names).map_err(refused)?,
876 ),
877 _ => (rucc_asm::Globals::default(), Vec::new()),
878 };
879 let unwind = opts.unwinds();
883 match opts.emit {
884 EmitKind::Asm => {
885 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
886 .map(Artifact::Text)
887 .map_err(refused)
888 }
889 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
893 if opts.save_temps.wanted() {
894 let listing = rucc_asm::print(
895 &funcs,
896 &globals,
897 &aliases,
898 names,
899 target,
900 unwind,
901 output(opts, target),
902 );
903 *assembly = Some(listing.map_err(refused)?);
904 }
905 let text = rucc_asm::assemble(&funcs, names, target, unwind).map_err(refused)?;
906 let data = globals.image();
907 let bytes = rucc_object::write(&text, &data, &aliases, target, output(opts, target))
910 .map_err(wrote)?;
911 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
916 Ok(Artifact::Object { bytes, defines })
917 }
918 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
919 }
920}
921
922fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
934 let mut features = 0;
935 if target.tuple.arch() == Arch::X86_64 {
936 if opts.control.branch() {
937 features |= rucc_object::Property::IBT;
938 }
939 if opts.control.ret() {
940 features |= rucc_object::Property::SHSTK;
941 }
942 }
943 rucc_object::Output {
944 sections: rucc_object::Sections {
945 functions: opts.function_sections,
946 data: opts.data_sections,
947 },
948 property: rucc_object::Property { features },
949 }
950}
951
952fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
958 match why {
959 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
960 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
961 }
962}
963
964fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
970 match why {
971 rucc_asm::Error::Thread { .. } | rucc_asm::Error::IFunc { .. } => {
972 vec![unsupported(&why.to_string())]
973 }
974 _ => vec![internal(&why.to_string())],
975 }
976}
977
978fn unsupported(message: &str) -> Diagnostic {
984 unsupported_at(message, Span::DUMMY)
985}
986
987fn unsupported_at(message: &str, span: Span) -> Diagnostic {
993 Diagnostic::error(message.to_owned(), span)
994 .with_code("E0653")
995 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
996}
997
998fn invalid(message: &str) -> Diagnostic {
1000 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1001}
1002
1003fn internal(message: &str) -> Diagnostic {
1005 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1006 .with_code("E0652")
1007 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1008}
1009
1010fn failure(message: String) -> Compiled {
1013 Compiled {
1014 artifact: Artifact::Nothing,
1015 messages: vec![format!("rucc: error: {message}")],
1016 errors: 1,
1017 fired: Fired::new(),
1018 pressure: Pressure::new(),
1019 lowerings: Lowerings::new(),
1020 dumps: Vec::new(),
1021 remarks: String::new(),
1022 deps: Vec::new(),
1023 temps: Temps::default(),
1024 }
1025}
1026
1027#[cfg(test)]
1028mod tests {
1029 use rucc_session::{MemoryFileSystem, Std};
1030 use rucc_target::Triple;
1031
1032 use super::*;
1033
1034 fn options() -> Options {
1035 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1036 opts.emit = EmitKind::Tast;
1037 opts
1038 }
1039
1040 fn run(opts: &Options, source: &str) -> Compiled {
1041 let mut fs = MemoryFileSystem::new();
1042 fs.insert("/main.c", source.to_owned().into_bytes());
1043 compile(opts, "/main.c", &fs)
1044 }
1045
1046 fn freestanding() -> Options {
1050 let mut opts = options();
1051 opts.hosted = false;
1052 opts.search.push_system(rucc_session::runtime::DIR);
1053 opts
1054 }
1055
1056 fn shipped(source: &str) -> String {
1058 let result = run(&freestanding(), source);
1059 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1060 result.text().to_owned()
1061 }
1062
1063 fn tast(source: &str) -> String {
1065 let result = run(&options(), source);
1066 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1067 result.text().to_owned()
1068 }
1069
1070 #[test]
1071 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1072 let text = shipped(concat!(
1073 "#include <stdarg.h>\n",
1074 "int sum(int n, ...) {\n",
1075 " va_list ap, copy;\n",
1076 " va_start(ap, n);\n",
1077 " va_copy(copy, ap);\n",
1078 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1079 " va_end(ap);\n",
1080 " va_end(copy);\n",
1081 " return total;\n",
1082 "}\n",
1083 ));
1084 assert!(text.contains("va-start"), "{text}");
1085 assert!(text.contains("va-copy"), "{text}");
1086 assert!(text.contains("va-arg"), "{text}");
1087 assert!(text.contains("va-end"), "{text}");
1088 }
1089
1090 #[test]
1094 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1095 let text = shipped(concat!(
1096 "#define __need___va_list\n",
1097 "#include <stdarg.h>\n",
1098 "int vprint(const char *f, __gnuc_va_list ap);\n",
1099 "#ifdef va_start\n",
1100 "#error va_start should not be defined\n",
1101 "#endif\n",
1102 "#ifdef _VA_LIST_DEFINED\n",
1103 "#error va_list should not have been made\n",
1104 "#endif\n",
1105 ));
1106 assert!(text.contains("vprint"), "{text}");
1107 }
1108
1109 #[test]
1112 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1113 let text = shipped(concat!(
1114 "#define __need_size_t\n",
1115 "#include <stddef.h>\n",
1116 "#ifdef offsetof\n",
1117 "#error offsetof should not be defined yet\n",
1118 "#endif\n",
1119 "#define __need_ptrdiff_t\n",
1120 "#include <stddef.h>\n",
1121 "#include <stddef.h>\n",
1122 "size_t a;\n",
1123 "ptrdiff_t b;\n",
1124 "wchar_t c;\n",
1125 "max_align_t d;\n",
1126 "void *e = NULL;\n",
1127 "struct P { int x; long y; };\n",
1128 "size_t f = offsetof(struct P, y);\n",
1129 ));
1130 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1131 assert!(text.contains("decl #1 b : long"), "{text}");
1132 }
1133
1134 #[test]
1135 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1136 let text = shipped(concat!(
1137 "#include <limits.h>\n",
1138 "#include <float.h>\n",
1139 "int bits = CHAR_BIT;\n",
1140 "long big = LONG_MAX;\n",
1141 "int low = INT_MIN;\n",
1142 "int radix = FLT_RADIX;\n",
1143 "int digits = DBL_MANT_DIG;\n",
1144 ));
1145 assert!(text.contains("const 8 : int"), "{text}");
1146 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1147 assert!(text.contains("const 2 : int"), "{text}");
1148 assert!(text.contains("const 53 : int"), "{text}");
1149 }
1150
1151 #[test]
1155 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1156 let text = shipped(concat!(
1157 "#include <stdint.h>\n",
1158 "int64_t a = INT64_C(1);\n",
1159 "uint_least16_t b;\n",
1160 "intptr_t c;\n",
1161 "uintmax_t d = UINTMAX_MAX;\n",
1162 "int wide = sizeof(int_fast64_t);\n",
1163 ));
1164 assert!(text.contains("decl #0 a : long"), "{text}");
1165 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1166 assert!(text.contains("decl #2 c : long"), "{text}");
1167 }
1168
1169 #[test]
1180 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1181 let text = shipped(concat!(
1182 "#include <mmintrin.h>\n",
1183 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1184 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1185 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1186 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1187 "void done(void) { _mm_empty(); }\n",
1188 ));
1189 assert!(text.contains("add"), "{text}");
1190 assert!(text.contains("pack"), "{text}");
1191 assert!(text.contains("shift"), "{text}");
1192 }
1193
1194 #[test]
1199 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1200 let text = shipped(concat!(
1201 "#include <mm_malloc.h>\n",
1202 "void *get(void) { return _mm_malloc(64, 16); }\n",
1203 "void put(void *p) { _mm_free(p); }\n",
1204 ));
1205 assert!(text.contains("get"), "{text}");
1206 assert!(text.contains("put"), "{text}");
1207 }
1208
1209 #[test]
1221 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1222 let text = shipped(concat!(
1223 "#include <xmmintrin.h>\n",
1224 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1225 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1226 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1227 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1228 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1229 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1230 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1231 "void *room(void) { return _mm_malloc(64, 16); }\n",
1232 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1233 ));
1234 assert!(text.contains("add"), "{text}");
1235 assert!(text.contains("mask"), "{text}");
1236 assert!(text.contains("pick"), "{text}");
1237 assert!(text.contains("wide"), "{text}");
1238 }
1239
1240 #[test]
1247 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1248 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1249 for absent in [
1250 "_mm_sqrt_ps",
1251 "_mm_sqrt_ss",
1252 "_mm_rsqrt_ps",
1253 "_mm_rsqrt_ss",
1254 "_mm_getcsr",
1255 "_mm_setcsr",
1256 ] {
1257 let defined = text.contains(&format!("{absent}("));
1258 assert!(!defined, "{absent} is defined and the header says it is not");
1259 assert!(text.contains(absent), "{absent} is absent and unexplained");
1260 }
1261 }
1262
1263 #[test]
1264 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1265 let text = shipped(concat!(
1266 "#include <emmintrin.h>\n",
1267 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1268 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1269 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1270 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1271 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1272 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1273 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1274 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1275 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1276 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1277 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1278 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1279 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1280 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1281 ));
1282 assert!(text.contains("wide"), "{text}");
1283 assert!(text.contains("pack"), "{text}");
1284 assert!(text.contains("near"), "{text}");
1285 assert!(text.contains("half"), "{text}");
1286 }
1287
1288 #[test]
1292 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1293 let text = shipped(concat!(
1294 "#include <immintrin.h>\n",
1295 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1296 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1297 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1298 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1299 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1300 "}\n",
1301 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1302 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1303 ));
1304 assert!(text.contains("matching"), "{text}");
1305 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1306 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1307 }
1308
1309 #[test]
1313 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1314 let text = shipped(concat!(
1315 "#include <immintrin.h>\n",
1316 "#include <emmintrin.h>\n",
1317 "#include <immintrin.h>\n",
1318 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1319 ));
1320 assert!(text.contains("twice"), "{text}");
1321 }
1322
1323 #[test]
1327 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1328 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1329 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1330 let defined = text.contains(&format!("{absent}("));
1331 assert!(!defined, "{absent} is defined and the header says it is not");
1332 assert!(text.contains(absent), "{absent} is absent and unexplained");
1333 }
1334 }
1335
1336 #[test]
1337 fn the_three_formality_headers_still_have_to_work() {
1338 let text = shipped(concat!(
1339 "#include <stdbool.h>\n",
1340 "#include <stdalign.h>\n",
1341 "#include <iso646.h>\n",
1342 "#include <stdnoreturn.h>\n",
1343 "int t = true and not false;\n",
1344 "_Alignas(16) char buf[16];\n",
1345 "int a = alignof(long);\n",
1346 ));
1347 assert!(text.contains("decl #0 t : int"), "{text}");
1348 assert!(text.contains("const 8 : unsigned long"), "{text}");
1349 }
1350
1351 #[test]
1359 fn every_shipped_header_can_be_included_twice() {
1360 let once: String = rucc_session::runtime::names()
1361 .iter()
1362 .map(|name| format!("#include <{name}>\n"))
1363 .collect();
1364 let twice = once.repeat(2);
1365 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1366 }
1367
1368 #[test]
1369 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1370 let fs = MemoryFileSystem::new();
1371 let result = compile(&options(), "/nope.c", &fs);
1372 assert!(result.failed());
1373 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1374 assert!(result.text().is_empty());
1375 }
1376
1377 #[test]
1378 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1379 let text = tast("int x = 1;\n");
1380 let expected = "\
1381decl #0 x : int object external static defined
1382 init
1383 +0
1384 const 1 : int
1385";
1386 assert_eq!(text, expected);
1387 }
1388
1389 #[test]
1390 fn the_macros_are_expanded_before_anything_is_parsed() {
1391 let text = tast("#define N 2\nint a[N];\n");
1395 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1396 }
1397
1398 #[test]
1404 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1405 let text = tast(concat!(
1406 "#pragma pack(4)\n",
1407 "struct s { int a; };\n",
1408 "#pragma pack()\n",
1409 "int b;\n",
1410 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1411 ));
1412 assert!(text.contains("decl #0 b : int"), "{text}");
1413 assert!(text.contains("decl #1 c : int"), "{text}");
1414 }
1415
1416 #[test]
1424 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1425 tast(concat!(
1426 "struct A { char c; int i; } __attribute__((packed));\n",
1427 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1428 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1429 "struct B { char c; int i; } __attribute__((aligned));\n",
1432 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1433 "struct C { char c; int i __attribute__((packed)); };\n",
1434 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1435 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1436 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1437 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1438 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1439 "struct E { char c; _Alignas(8) int i; };\n",
1440 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1441 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1442 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1443 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1444 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1447 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1448 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1449 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1450 "struct I { [[gnu::packed]] char c; int i; };\n",
1453 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1454 "struct J { char c; [[gnu::packed]] int i; };\n",
1455 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1456 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1457 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1458 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1459 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1460 "union L { char c; int i; } __attribute__((packed));\n",
1461 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1462 "struct O { char c; int i; } __attribute__((__packed__));\n",
1466 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
1467 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
1468 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
1469 ));
1470 }
1471
1472 #[test]
1485 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
1486 let text = tast(concat!(
1487 "struct one { int x; };\n",
1488 "struct two { long y; };\n",
1489 "typedef union { struct one *a; struct two *b; void *any; }\n",
1490 " __attribute__((__transparent_union__)) arg;\n",
1491 "int takes(arg v);\n",
1492 "int f(struct one *p, struct two *q, char *c) {\n",
1493 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
1494 "}\n",
1495 "int takes(struct one *p);\n",
1497 "int (*as_a_member)(struct one *) = takes;\n",
1498 "int (*as_the_union)(arg) = takes;\n",
1499 ));
1500 assert!(text.contains("compound-literal"), "{text}");
1501 }
1502
1503 #[test]
1509 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
1510 let text = tast(concat!(
1511 "struct sockaddr { int family; };\n",
1512 "struct sockaddr_in { int family; int addr; };\n",
1513 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
1514 " addr_arg __attribute__((__transparent_union__));\n",
1515 "int bind_to(int fd, addr_arg where);\n",
1516 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
1517 ));
1518 assert!(text.contains("compound-literal"), "{text}");
1519 }
1520
1521 #[test]
1529 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
1530 let result = run(
1531 &options(),
1532 concat!(
1533 "union wider { int small; double large; } __attribute__((transparent_union));\n",
1534 "struct plain { int x; } __attribute__((transparent_union));\n",
1535 ),
1536 );
1537 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
1538 assert!(!result.failed(), "{:?}", result.messages);
1539 for message in &result.messages {
1540 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
1541 }
1542 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
1543 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
1544 }
1545
1546 #[test]
1555 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
1556 let packed = body(concat!(
1557 "struct P { char c; int v; } __attribute__((packed));\n",
1558 "int f(struct P *p) { return p->v; }\n",
1559 ));
1560 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
1561 let plain = body(concat!(
1563 "struct P { char c; int v; };\n",
1564 "int f(struct P *p) { return p->v; }\n",
1565 ));
1566 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
1567 }
1568
1569 #[test]
1576 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
1577 let stepped = body(concat!(
1578 "struct P { char c; int v[4]; } __attribute__((packed));\n",
1579 "int f(struct P *p, int i) { return p->v[i]; }\n",
1580 ));
1581 assert!(stepped.contains(", align 1,"), "{stepped}");
1582 assert!(!stepped.contains(", align 4,"), "{stepped}");
1583 let nested = body(concat!(
1584 "struct Inner { int v; };\n",
1585 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
1586 "int f(struct P *p) { return p->in.v; }\n",
1587 ));
1588 assert!(nested.contains(", align 1,"), "{nested}");
1589 assert!(!nested.contains(", align 4,"), "{nested}");
1590 }
1591
1592 #[test]
1601 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
1602 tast(concat!(
1603 "int v __attribute__((aligned(64)));\n",
1604 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
1605 "__attribute__((aligned(32))) int w;\n",
1608 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
1609 "[[gnu::aligned(16)]] int x;\n",
1610 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
1611 "int y __attribute__((aligned(2)));\n",
1614 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
1615 "void f(void) { int a __attribute__((aligned(128)));\n",
1617 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
1618 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1621 "void g(void) __attribute__((aligned(256)));\n",
1624 "void g(void) {}\n",
1625 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
1626 ));
1627 }
1628
1629 #[test]
1633 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
1634 let text = asm(concat!(
1635 "int v __attribute__((aligned(64)));\n",
1636 "void g(void) __attribute__((aligned(256)));\n",
1637 "void g(void) {}\n",
1638 "void plain(void) {}\n",
1639 ));
1640 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
1641 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1642 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
1643 }
1644
1645 #[test]
1654 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
1655 tast(concat!(
1656 "typedef int L __attribute__((aligned(2)));\n",
1657 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
1658 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
1659 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
1661 "struct T { char c; L x; };\n",
1662 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
1663 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
1664 "typedef int H __attribute__((aligned(16)));\n",
1666 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
1667 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
1668 "struct U { char c; H x; };\n",
1669 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
1670 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
1671 "typedef L M __attribute__((aligned(8)));\n",
1674 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
1675 "typedef L N;\n",
1678 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
1679 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1681 ));
1682 let text = asm(concat!(
1683 "typedef int L __attribute__((aligned(2)));\n",
1684 "typedef int H __attribute__((aligned(16)));\n",
1685 "L low;\n",
1686 "H high;\n",
1687 ));
1688 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
1689 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
1690 }
1691
1692 #[test]
1700 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
1701 tast(concat!(
1702 "typedef int __attribute__((vector_size(16))) v4si;\n",
1703 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
1704 "typedef char __attribute__((vector_size(16))) v16qi;\n",
1705 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
1706 "typedef int __attribute__((vector_size(4))) v1si;\n",
1709 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
1710 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
1712 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
1713 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
1714 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
1715 "v4si g;\n",
1718 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
1719 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
1720 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
1723 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
1725 ));
1726 }
1727
1728 #[test]
1738 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
1739 tast(concat!(
1740 "typedef int __attribute__((vector_size(8))) v2si;\n",
1741 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
1742 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
1743 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
1745 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
1746 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
1749 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
1750 ));
1751 }
1752
1753 #[test]
1761 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
1762 let result = run(
1763 &options(),
1764 concat!(
1765 "typedef int __attribute__((vector_size(16))) v4si;\n",
1766 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
1767 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
1768 " v4si v = { 1, 2, 3, 4 };\n",
1769 " v[0] = n;\n",
1770 " v[1] += n;\n",
1771 " v[2]++;\n",
1772 " *&v[3] = n;\n",
1773 " v4ui shifted = a >> b;\n",
1775 " shifted <<= b;\n",
1776 " *out = v + (v4si)shifted + (1 << b);\n",
1779 "}\n",
1780 "void refused(const v4si c) {\n",
1783 " c[0] = 1;\n",
1784 "}\n",
1785 ),
1786 );
1787 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
1788 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
1789 }
1790
1791 #[test]
1798 fn a_record_that_asks_for_the_other_byte_order_is_refused_rather_than_laid_out_in_this_one() {
1799 let opts = options();
1800 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
1801 assert_eq!(
1802 run(&opts, big).messages,
1803 ["/main.c:1:36: error: 'scalar_storage_order' is not implemented yet [E0688]\n\
1804 /main.c:1:36: note: every scalar in this record would be read in the wrong byte \
1805 order"]
1806 );
1807
1808 let armoured =
1809 "struct s { int i; } __attribute__((__scalar_storage_order__(\"little-endian\")));\n";
1810 let messages = run(&opts, armoured).messages;
1811 assert!(messages[0].contains("[E0688]"), "{messages:?}");
1812
1813 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
1816 assert!(run(&opts, front).messages[0].contains("[E0688]"), "{front}");
1817 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
1818 assert!(run(&opts, standard).messages[0].contains("[E0688]"), "{standard}");
1819 }
1820
1821 #[test]
1831 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
1832 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
1834 assert_eq!(
1835 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
1836 1
1837 );
1838 assert_eq!(
1839 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
1840 1
1841 );
1842 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
1843 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
1845 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
1846 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
1848 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
1849 }
1850
1851 fn bit_field_byte(record: &str) -> u64 {
1853 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
1854 let body = body(&source);
1855 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
1856 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
1857 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
1858 }
1859
1860 #[test]
1866 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
1867 tast(concat!(
1868 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
1869 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
1870 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
1871 "struct b { char c; __attribute__((packed)) int i; };\n",
1872 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
1873 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
1874 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
1875 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
1876 ));
1877 }
1878
1879 #[test]
1885 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
1886 tast(concat!(
1887 "#pragma pack(1)\n",
1888 "struct A { char c; int i; };\n",
1889 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1890 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1891 "#pragma pack()\n",
1892 "struct B { char c; int i; };\n",
1893 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
1894 "#pragma pack(2)\n",
1895 "struct C { char c; int i; double d; };\n",
1896 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
1897 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
1898 "struct K { char c; int i __attribute__((aligned(8))); };\n",
1900 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
1901 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
1902 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
1904 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
1905 "#pragma pack()\n",
1906 "#pragma pack(push, 1)\n",
1907 "struct D { char c; short s; };\n",
1908 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
1909 "#pragma pack(pop)\n",
1910 "struct E { char c; short s; };\n",
1911 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
1912 "struct H { char c;\n",
1914 "#pragma pack(1)\n",
1915 " int i; };\n",
1916 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
1917 "#pragma pack(1)\n",
1918 "struct I { char c;\n",
1919 "#pragma pack()\n",
1920 " int i; };\n",
1921 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1922 "#pragma pack()\n",
1923 "#pragma pack(push, 8)\n",
1925 "#pragma pack(push, 1)\n",
1926 "struct P { char c; int i; };\n",
1927 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
1928 "#pragma pack(pop)\n",
1929 "struct Q { char c; int i; };\n",
1930 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
1931 "#pragma pack(pop)\n",
1932 "#pragma pack(16)\n",
1934 "struct R { char c; int i; };\n",
1935 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
1936 "#pragma pack()\n",
1937 "#pragma pack(1)\n",
1938 "struct S { char c; int i : 5; int j : 20; };\n",
1939 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
1940 "union T { char c; int i; };\n",
1941 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
1942 "#pragma pack()\n",
1943 ));
1944 }
1945
1946 #[test]
1950 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
1951 let result = run(
1952 &options(),
1953 concat!(
1954 "#pragma pack 4\n",
1955 "#pragma pack(pop)\n",
1956 "#pragma pack(3)\n",
1957 "#pragma pack(1) junk\n",
1958 "#pragma pack(push, 1\n",
1959 "#pragma pack(x)\n",
1960 "#pragma pack(0)\n",
1963 "#pragma pack(push)\n",
1964 "struct s { char c; int i; };\n",
1965 "#pragma pack(pop)\n",
1966 "#pragma pack(pop, foo)\n",
1967 ),
1968 );
1969 let expected = [
1970 "missing `(` after `#pragma pack` - ignored",
1971 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
1972 "alignment must be a small power of two, not 3",
1973 "junk at end of `#pragma pack`",
1974 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
1975 "unknown action `x` for `#pragma pack` - ignored",
1976 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
1977 ];
1978 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
1979 for (message, want) in result.messages.iter().zip(expected) {
1980 assert!(message.contains(want), "expected {want:?} in {message:?}");
1981 }
1982 }
1983
1984 #[test]
1988 fn the_wide_integer_answers_to_all_three_of_its_names() {
1989 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
1990 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
1991 assert!(text.contains("decl #1 b : __int128"), "{text}");
1992 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
1993 }
1994
1995 #[test]
1996 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
1997 let text = tast("long f(int a, long b) { return a + b; }\n");
2001 assert!(text.contains("convert arithmetic"), "{text}");
2002 }
2003
2004 #[test]
2005 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2006 for source in [
2007 "#error stop\n",
2008 "int f(void) { return 1 + ; }\n",
2009 "int f(void) { return undeclared; }\n",
2010 ] {
2011 let result = run(&options(), source);
2012 assert!(result.failed(), "expected this to fail:\n{source}");
2013 assert!(
2014 result.text().is_empty(),
2015 "a file that did not compile wrote a tree:\n{source}"
2016 );
2017 }
2018 }
2019
2020 #[test]
2021 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2022 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2026 assert_eq!(result.errors, 1, "{:?}", result.messages);
2027 }
2028
2029 #[test]
2030 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2031 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2035 assert_eq!(result.errors, 1, "{:?}", result.messages);
2036 }
2037
2038 #[test]
2039 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2040 let source = "int f(void) { char c = 300; return c; }\n";
2041 let plain = run(&options(), source);
2042 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2043 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2044 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2045
2046 let mut opts = options();
2047 opts.warnings_are_errors = true;
2048 let strict = run(&opts, source);
2049 assert!(strict.failed());
2050 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2051 for message in &strict.messages {
2052 assert!(!message.contains("warning:"), "{message}");
2053 }
2054 }
2055
2056 #[test]
2057 fn w_drops_the_warning_before_werror_can_promote_it() {
2058 let source = "int f(void) { char c = 300; return c; }\n";
2059 let mut opts = options();
2060 opts.warnings = false;
2061 let quiet = run(&opts, source);
2062 assert_eq!(quiet.messages, Vec::<String>::new());
2063 assert_eq!(quiet.errors, 0);
2064 assert!(!quiet.text().is_empty(), "and the file still compiles");
2065
2066 opts.warnings_are_errors = true;
2069 let both = run(&opts, source);
2070 assert_eq!(both.messages, Vec::<String>::new());
2071 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2072 }
2073
2074 #[test]
2075 fn the_dialect_reaches_the_keywords_and_the_checking() {
2076 let source = "typeof(1) x;\n";
2079 let mut opts = options();
2080 opts.std = Std::C23;
2081 opts.gnu_extensions = false;
2082 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2083
2084 opts.std = Std::C17;
2085 assert!(run(&opts, source).failed());
2086 }
2087
2088 #[test]
2089 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2090 let mut opts = options();
2091 opts.emit = EmitKind::Object;
2092 let result = run(&opts, "int x = 1;\n");
2093 assert!(!result.failed(), "{:?}", result.messages);
2094 assert!(result.text().is_empty());
2095 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2098 }
2099
2100 fn mir(source: &str) -> String {
2102 let mut opts = options();
2103 opts.emit = EmitKind::MirFinal;
2104 let result = run(&opts, source);
2105 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2106 result.text().to_owned()
2107 }
2108
2109 #[test]
2115 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2116 let text = mir("int add(int a, int b) { return a + b; }\n");
2117 assert!(text.starts_with("mfunc @add {"), "{text}");
2118 assert!(text.contains("x64.add_rr_32"), "{text}");
2119 assert!(text.contains("x64.ret"), "{text}");
2120 assert!(!text.contains('%'), "{text}");
2123 }
2124
2125 #[test]
2127 fn a_function_with_no_body_produces_no_machine_function() {
2128 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2129 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2130 assert!(text.contains("mfunc @f {"), "{text}");
2131 assert!(text.contains("x64.call"), "{text}");
2132 }
2133
2134 #[test]
2136 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2137 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2138 let first = text.find("mfunc @a").expect("the first function");
2139 let second = text.find("mfunc @b").expect("the second function");
2140 assert!(first < second, "{text}");
2141 }
2142
2143 #[test]
2145 fn the_target_decides_which_convention_the_generated_code_follows() {
2146 let mut opts = options();
2147 opts.emit = EmitKind::MirFinal;
2148 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2149 assert!(linux.contains("$rdi"), "{linux}");
2150
2151 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2152 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2153 assert!(windows.contains("$rcx"), "{windows}");
2154 assert!(!windows.contains("$rdi"), "{windows}");
2155 }
2156
2157 #[test]
2159 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2160 let mut opts = options();
2161 opts.emit = EmitKind::MirFinal;
2162 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2163 let result = run(&opts, "int f(int a) { return a; }\n");
2164 assert!(result.failed());
2165 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2166 assert!(result.text().is_empty());
2167 }
2168
2169 #[test]
2176 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2177 let mut opts = options();
2178 opts.emit = EmitKind::MirFinal;
2179 let source = "void a(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n\
2180 void b(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n";
2181 let result = run(&opts, source);
2182 assert!(result.failed());
2183 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2184 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2185 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2186 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2187 assert!(result.text().is_empty());
2188 }
2189
2190 #[test]
2198 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2199 let mut opts = options();
2200 opts.emit = EmitKind::MirFinal;
2201 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2202 let plain = run(&opts, source);
2203 assert!(!plain.failed(), "{:?}", plain.messages);
2204 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2205
2206 opts.stack_clash = true;
2207 let result = run(&opts, source);
2208 assert!(!result.failed(), "{:?}", result.messages);
2209 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2210 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2211 }
2212
2213 #[test]
2227 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
2228 let mut opts = options();
2229 opts.emit = EmitKind::MirFinal;
2230 let source =
2231 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
2232 let result = run(&opts, source);
2233 assert!(result.failed());
2234 assert!(
2235 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
2236 "{result:?}"
2237 );
2238 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
2239 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
2240 }
2241
2242 #[test]
2244 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
2245 let mut opts = options();
2246 opts.emit = EmitKind::MirFinal;
2247 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
2248 let result = run(&opts, source);
2249 assert!(result.failed());
2250 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
2251 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
2252 assert!(!note.contains("spec/17-milestones.md"), "{note}");
2253 }
2254
2255 #[test]
2257 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
2258 let source = "int f(int a) { return a; }\n";
2259 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
2260
2261 let mut opts = options();
2262 opts.emit = EmitKind::MirFinal;
2263 opts.frame_pointer = true;
2264 let kept = run(&opts, source).text().to_owned();
2265 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
2266 }
2267
2268 fn asm(source: &str) -> String {
2270 let mut opts = options();
2271 opts.emit = EmitKind::Asm;
2272 let result = run(&opts, source);
2273 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2274 result.text().to_owned()
2275 }
2276
2277 #[test]
2284 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
2285 let text = asm("int add(int a, int b) { return a + b; }\n");
2286 assert!(text.contains("\t.globl\tadd\n"), "{text}");
2287 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
2288 assert!(text.contains("\nadd:\n"), "{text}");
2289 assert!(text.contains("\taddl\t"), "{text}");
2290 assert!(text.contains("\tret\n"), "{text}");
2291 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
2292 assert!(text.contains(".note.GNU-stack"), "{text}");
2295 }
2296
2297 #[test]
2303 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
2304 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
2305 assert!(text.contains("\tcall\t*%"), "{text}");
2306 assert!(text.contains("\tcall\tg\n"), "{text}");
2307 assert!(text.contains("%rdi"), "{text}");
2311 }
2312
2313 #[test]
2317 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
2318 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
2319 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
2320 }
2321
2322 #[test]
2331 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
2332 let arms = "return 1; return 2;";
2333 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
2334 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
2335 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
2336 assert!(
2337 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2338 "{operator}: {text}"
2339 );
2340 assert!(!text.contains("\tset"), "{operator}: {text}");
2341 assert!(!text.contains("\ttest"), "{operator}: {text}");
2342 }
2343 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
2344 for (operator, jump) in unsigned {
2345 let source =
2346 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
2347 let text = asm(&source);
2348 assert!(
2349 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2350 "{operator}: {text}"
2351 );
2352 }
2353
2354 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
2357 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
2358 }
2359
2360 #[test]
2366 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
2367 let text = asm("int f(int a, int b) { return a < b; }\n");
2368 assert!(text.contains("\tsetl\t"), "{text}");
2369 }
2370
2371 fn optimized(source: &str) -> String {
2373 let mut opts = options();
2374 opts.emit = EmitKind::Asm;
2375 opts.opt_level = rucc_session::OptLevel::O2;
2376 let result = run(&opts, source);
2377 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2378 result.text().to_owned()
2379 }
2380
2381 #[test]
2391 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
2392 let arms: String =
2393 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
2394 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2395 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
2396 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
2397 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
2398 }
2399
2400 #[test]
2407 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
2408 let arms: String = (0..16)
2409 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
2410 .collect::<Vec<_>>()
2411 .join(" ");
2412 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2413 assert!(text.matches("\tcmp").count() > 1, "{text}");
2414 }
2415
2416 #[test]
2418 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2419 let text = asm("long f(void *p) { return (long)p; }\n");
2420 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2425 let mnemonic = line.split_whitespace().next().unwrap_or("");
2426 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2427 }
2428 }
2429
2430 #[test]
2434 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2435 let six = "long a, long b, long c, long d, long e, long f";
2436 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2437
2438 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2445 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2446
2447 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2451 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2452 let eight =
2453 "double a, double b, double c, double d, double e, double f, double g, double h";
2454 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2455 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2456 }
2457
2458 #[test]
2461 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2462 let six = "1, 2, 3, 4, 5, 6";
2463 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2464 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2465
2466 assert!(text.contains("\tmovq\t%"), "{text}");
2467 assert!(text.contains(", (%rsp)\n"), "{text}");
2468 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2469 assert!(text.contains("\tsubq\t$"), "{text}");
2471
2472 let narrow = "int g(int, int, int, int, int, int, int);\n";
2474 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2475 assert!(text.contains("\tmovl\t%"), "{text}");
2476 assert!(text.contains(", (%rsp)\n"), "{text}");
2477 }
2478
2479 #[test]
2482 fn a_variadic_call_counts_registers_and_not_arguments() {
2483 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2484 let decl = "int g(int, ...);\n";
2485 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2486
2487 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2488 assert!(text.contains("\tmovsd\t%"), "{text}");
2489 assert!(text.contains(", (%rsp)\n"), "{text}");
2490 }
2491
2492 #[test]
2497 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2498 let body =
2499 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2500 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2501
2502 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2505 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2506 assert!(!text.contains(", 0(%r"), "{text}");
2507 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2510 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2511
2512 assert!(text.contains("\tsubq\t$"), "{text}");
2514 }
2515
2516 #[test]
2519 fn va_start_writes_the_four_fields_the_psabi_describes() {
2520 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2521 let params = "int a, int b, int c, double d";
2522 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2523
2524 assert!(text.contains(" movl $24, "), "{text}");
2528 assert!(text.contains(" movl $64, "), "{text}");
2529 assert!(text.contains(", 8(%r"), "{text}");
2533 assert!(text.contains(", 16(%r"), "{text}");
2534 let frame: u32 = text
2535 .lines()
2536 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
2537 .expect("a variadic function takes a frame for the save area");
2538 let above = |line: &str| {
2539 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
2540 Some(at > frame)
2541 };
2542 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
2543 }
2544
2545 #[test]
2548 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
2549 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
2550 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
2551 let text = asm(&ints);
2552
2553 assert!(text.contains("$40, "), "{text}");
2556 assert!(text.contains(" cmpl "), "{text}");
2557 assert!(text.contains(" ja "), "{text}");
2561
2562 let arg = "__builtin_va_arg(ap, double)";
2563 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
2564 assert!(text.contains("$160, "), "the last vector slot: {text}");
2565 }
2566
2567 #[test]
2570 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
2571 let decl = "struct pair { long a, b; };\n";
2572 let body = "struct pair p = *q; return p.a + p.b;";
2573 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
2574
2575 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
2576 assert!(!text.contains("\tcall"), "{text}");
2577 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
2579 }
2580
2581 #[test]
2584 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
2585 let decl = "struct bytes { char a[8]; };\n";
2586 let body = "struct bytes p = *q; return p.a[0];";
2587 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
2588
2589 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
2591 }
2592
2593 #[test]
2596 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
2597 let decl = "struct wide { long a, b, c; };\n";
2598 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
2599
2600 assert!(!text.contains("memset"), "nothing calls the library: {text}");
2601 assert!(text.contains("\tmovq\t$0, ") || text.contains("$0, %"), "the zero: {text}");
2602 }
2603
2604 #[test]
2607 fn a_copy_too_large_to_unroll_calls_the_runtime() {
2608 let decl = "struct huge { char a[4096]; };\n";
2609 let mut opts = options();
2610 opts.emit = EmitKind::Asm;
2611 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
2612 let result = run(&opts, &source);
2613 assert!(!result.failed(), "{:?}", result.messages);
2614 let text = result.text();
2615 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
2616 assert!(text.contains("4096"), "the size travels: {text}");
2619 }
2620
2621 #[test]
2624 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
2625 let six = "long a, long b, long c, long d, long e, long f";
2626 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
2627 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
2628
2629 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
2633 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
2634 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
2635 }
2636
2637 #[test]
2639 fn the_target_decides_how_the_assembly_is_spelled() {
2640 let mut opts = options();
2641 opts.emit = EmitKind::Asm;
2642 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
2643 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
2644 assert!(text.contains("__TEXT,__text"), "{text}");
2645 assert!(text.contains("\n_f:\n"), "{text}");
2646 assert!(!text.contains(".note.GNU-stack"), "{text}");
2647 }
2648
2649 fn obj(source: &str) -> Vec<u8> {
2651 let mut opts = options();
2652 opts.emit = EmitKind::Object;
2653 let result = run(&opts, source);
2654 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2655 match result.artifact {
2656 Artifact::Object { bytes, .. } => bytes,
2657 other => panic!("expected an object, got {other:?}"),
2658 }
2659 }
2660
2661 #[test]
2667 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
2668 let bytes = obj("int add(int a, int b) { return a + b; }\n");
2669 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
2670 let text = asm("int add(int a, int b) { return a + b; }\n");
2671 assert!(
2672 text.contains("\taddl\t"),
2673 "and the listing of it is the same instructions:\n{text}"
2674 );
2675 }
2676
2677 #[test]
2679 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
2680 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
2681 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
2682 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
2683 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
2684 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
2687 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
2688 assert!(!text.contains(".globl\thidden"), "{text}");
2689 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2692 }
2693
2694 #[test]
2701 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
2702 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
2703 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
2704 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
2705
2706 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
2709 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
2710
2711 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
2714 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
2715
2716 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
2718 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
2719 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
2720 }
2721
2722 #[test]
2724 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
2725 let text = asm("const char *f(void) { return \"hi\"; }\n");
2726 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
2727 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2728 let label = text
2729 .lines()
2730 .find(|line| line.starts_with(".Lstr"))
2731 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
2732 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
2733 }
2734
2735 #[test]
2737 fn an_address_in_an_initializer_is_left_to_the_linker() {
2738 let source = "int counter;\nint *p = &counter;\n";
2739 let text = asm(source);
2740 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
2741 let bytes = obj(source);
2744 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
2745 }
2746
2747 #[test]
2756 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
2757 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
2760 struct m { void (*x)(void); void (*y)(void); };\n\
2761 const struct m t = { a, b };\n");
2762 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
2763 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
2764
2765 let text =
2768 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
2769 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
2770
2771 let text = asm("const int fixed = 7;\n");
2773 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2774 }
2775
2776 #[test]
2783 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
2784 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
2785 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
2788 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
2789 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
2792 assert!(text.contains("%fs:0"), "{text}");
2793 }
2794
2795 #[test]
2801 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
2802 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
2803 assert!(text.contains("movq\t%fs:0, "), "{text}");
2804 assert!(!text.contains("GOTTPOFF"), "{text}");
2806 }
2807
2808 #[test]
2819 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
2820 for (locality, wanted) in
2821 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
2822 {
2823 let source =
2824 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
2825 let text = asm(&source);
2826 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
2827 }
2828 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
2830 assert!(text.contains("\tprefetcht0\t"), "{text}");
2831 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
2834 assert!(text.contains("\tprefetcht0\t"), "{text}");
2835 assert!(!text.contains("prefetchw"), "{text}");
2836 }
2837
2838 #[test]
2849 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
2850 let text = asm("void stop(void) { __builtin_trap(); }\n");
2851 assert!(text.contains("\tud2\n"), "{text}");
2852 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
2853
2854 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
2855 assert!(text.contains("\tud2\n"), "{text}");
2856 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
2857 }
2858
2859 #[test]
2871 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
2872 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
2873 assert!(!text.contains("assume_aligned"), "{text}");
2874 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
2875
2876 let source = "unsigned long width(void);\n\
2877 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
2878 let text = asm(source);
2879 assert!(!text.contains("assume_aligned"), "{text}");
2880 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
2881 }
2882
2883 #[test]
2893 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
2894 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
2895 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
2896 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
2897 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
2898
2899 let walk = |depth: u32| {
2900 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
2901 asm(&source).matches("movq\t(%r").count()
2902 };
2903 assert_eq!(walk(1), 1, "one link is one load");
2904 assert_eq!(walk(3), 3, "three links are three loads");
2905 }
2906
2907 #[test]
2917 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
2918 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
2919 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
2920 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
2921 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
2922
2923 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
2924 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
2925 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
2926 }
2927
2928 #[test]
2939 fn a_depth_that_is_not_a_small_constant_is_refused() {
2940 let mut opts = options();
2941 opts.emit = EmitKind::Ir;
2942 for source in [
2943 "void *up(int n) { return __builtin_return_address(n); }\n",
2944 "void *up(void) { return __builtin_frame_address(1000); }\n",
2945 ] {
2946 let messages = run(&opts, source).messages;
2947 let named = messages.iter().any(|m| m.contains("E0705"));
2948 assert!(named, "expected a refusal in {messages:?}");
2949 }
2950 }
2951
2952 #[test]
2964 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
2965 let text =
2966 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
2967 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
2968 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
2969 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
2970
2971 let plain = concat!(
2974 "extern void *alloca(__SIZE_TYPE__);\n",
2975 "void use(void *p);\n",
2976 "void f(unsigned long n) { use(alloca(n)); }\n",
2977 );
2978 let text = asm(plain);
2979 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
2980 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
2981
2982 let own = concat!(
2985 "static void *alloca(unsigned long n) { return 0; }\n",
2986 "void *f(unsigned long n) { return alloca(n); }\n",
2987 );
2988 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
2989 }
2990
2991 #[test]
3001 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3002 let inner = "{ use(__builtin_alloca(n)); }";
3003 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3004 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3005 let text = asm(&source);
3006 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3010 let taking = line.contains("subq");
3011 let leaving = line.contains("%rbp");
3012 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3013 }
3014 }
3015 }
3016
3017 #[test]
3019 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3020 let source = "int callee(void); int g(void) { return callee(); }\n";
3024 let bytes = obj(source);
3025 assert!(
3026 bytes.windows(7).any(|w| w == b"callee\0"),
3027 "the object has to name the callee for the linker to find it"
3028 );
3029 let text = asm(source);
3030 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3031 }
3032
3033 #[test]
3039 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3040 let mut opts = options();
3041 opts.emit = EmitKind::Executable;
3043 let result = run(&opts, "int main(void) { return 0; }\n");
3044 assert_eq!(result.messages, Vec::<String>::new());
3045 match result.artifact {
3046 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3047 other => panic!("expected an object, got {other:?}"),
3048 }
3049 }
3050
3051 #[test]
3053 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3054 let mut opts = options();
3055 opts.emit = EmitKind::Object;
3056 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3057 let result = run(&opts, "int f(void) { return 0; }\n");
3058 assert!(result.failed(), "an object nobody can read is worse than a message");
3059 assert!(
3060 result.messages.iter().any(|m| m.contains("no object writer")),
3061 "{:?}",
3062 result.messages
3063 );
3064 }
3065
3066 fn ir(source: &str) -> String {
3068 let mut opts = options();
3069 opts.emit = EmitKind::Ir;
3070 let result = run(&opts, source);
3071 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3072 result.text().to_owned()
3073 }
3074
3075 fn errors(source: &str) -> Vec<String> {
3077 let mut opts = options();
3078 opts.emit = EmitKind::Ir;
3079 let result = run(&opts, source);
3080 assert!(result.failed(), "expected this to be refused:\n{source}");
3081 result.messages
3082 }
3083
3084 fn body(source: &str) -> String {
3086 let text = ir(source);
3087 let (_, rest) = text.split_once("{\n").expect("a function definition");
3088 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3089 body.to_owned()
3090 }
3091
3092 #[test]
3100 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3101 let source = "inline int f(int x) { return x + 1; }\n";
3102 let with = |flag: bool| {
3103 let mut opts = options();
3104 opts.emit = EmitKind::Ir;
3105 opts.gnu89_inline = flag;
3106 let result = run(&opts, source);
3107 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3108 result.text().to_owned()
3109 };
3110
3111 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3114
3115 assert!(with(true).contains("block0"), "a body: {}", with(true));
3118 }
3119
3120 #[test]
3127 fn an_access_through_a_type_names_the_type_it_went_through() {
3128 let source = "\
3129struct s { int a; float b; };\n\
3130union u { int i; float f; };\n\
3131int scalar(int *p) { return *p; }\n\
3132float member(struct s *p) { p->a = 1; return p->b; }\n\
3133int element(int *a, long i) { return a[i]; }\n\
3134float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3135 let text = ir(source);
3136 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3137 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3138 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3139 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3142 assert_eq!(named, 6, "six accesses: {text}");
3143 }
3144
3145 #[test]
3152 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3153 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3154 let mut opts = options();
3155 opts.emit = EmitKind::Ir;
3156 opts.strict_aliasing = false;
3157 let result = run(&opts, source);
3158 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3159 let text = result.text().to_owned();
3160 assert!(!text.contains("tbaa"), "not even the root: {text}");
3161 }
3162
3163 #[test]
3171 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3172 let mut opts = options();
3173 opts.emit = EmitKind::Ir;
3174 opts.std = Std::C89;
3175 let compiled = |source: &str| {
3176 let result = run(&opts, source);
3177 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3178 result.text().to_owned()
3179 };
3180
3181 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3182 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3183 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3184
3185 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3187 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3188 }
3189
3190 #[test]
3198 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3199 let mut opts = options();
3200 opts.emit = EmitKind::Ir;
3201 opts.std = Std::C89;
3202 let compiled = |source: &str| {
3203 let result = run(&opts, source);
3204 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3205 result.text().to_owned()
3206 };
3207
3208 let text = compiled("int f(void) { return g(); }\n");
3210 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3211 assert!(text.contains("i32"), "and it gives back an int: {text}");
3212
3213 let text = compiled("int f(char c) { return g(c); }\n");
3216 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3217
3218 let mut opts = options();
3221 opts.std = Std::C89;
3222 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3223 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3224 }
3225
3226 #[test]
3236 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3237 let mut opts = options();
3238 opts.emit = EmitKind::Ir;
3239 opts.std = Std::C89;
3240 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3241 .text()
3242 .to_owned();
3243 assert!(text.contains("func @f()"), "the caller is there: {text}");
3244 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3245 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3246 }
3247
3248 #[test]
3256 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3257 let mut opts = options();
3258 opts.emit = EmitKind::Ir;
3259 opts.std = Std::C89;
3260 let compiled = |source: &str| run(&opts, source).text().to_owned();
3261
3262 let text = compiled("f (c) unsigned char c; { return c; }\n");
3263 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3264 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3265 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3266
3267 let text = compiled("f (s) short s; { return s; }\n");
3269 assert!(text.contains("trunc.i16"), "cut down: {text}");
3270 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3271
3272 let text = compiled("f (x) float x; { return x * 2; }\n");
3275 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3276 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3277
3278 let text = compiled("int f(unsigned char c) { return c; }\n");
3281 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3282 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3283 }
3284
3285 #[test]
3294 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3295 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3297 let cases = [
3298 ("static counted;\n", ["", "error", "warning", "error"]),
3299 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3300 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3301 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3302 (
3303 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3304 ["warning", "error", "warning", "error"],
3305 ),
3306 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3307 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3308 ];
3309
3310 for (source, wanted) in cases {
3311 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3312 let mut opts = options();
3313 opts.std = std;
3314 opts.permissive = permissive;
3315 let said = run(&opts, source).messages.join("\n");
3316 let severity = if said.contains(": error: ") {
3317 "error"
3318 } else if said.contains(": warning: ") {
3319 "warning"
3320 } else {
3321 ""
3322 };
3323 let how = if permissive { " -fpermissive" } else { "" };
3324 assert_eq!(
3325 severity,
3326 wanted,
3327 "under -std={}{how}, {source} was answered with `{said}`",
3328 std.as_str()
3329 );
3330 if wanted.is_empty() {
3331 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3332 }
3333 }
3334 }
3335 }
3336
3337 #[test]
3346 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3347 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3348 let cases = [
3349 (
3350 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3351 "first argument to 'va_arg' not of type 'va_list'",
3352 ["error", "error", "error", "error"],
3353 ),
3354 (
3355 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3356 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3357 ["warning", "error", "warning", "error"],
3358 ),
3359 (
3360 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3361 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3362 cast",
3363 ["warning", "error", "warning", "error"],
3364 ),
3365 (
3366 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3367 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3368 ["warning", "error", "warning", "error"],
3369 ),
3370 ];
3371
3372 for (source, message, wanted) in cases {
3373 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3374 let mut opts = options();
3375 opts.std = std;
3376 opts.permissive = permissive;
3377 let said = run(&opts, source).messages.join("\n");
3378 let how = if permissive { " -fpermissive" } else { "" };
3379 assert!(
3380 said.contains(&format!(": {wanted}: {message}")),
3381 "under -std={}{how}, {source} was answered with `{said}`",
3382 std.as_str()
3383 );
3384 }
3385 }
3386 }
3387
3388 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3390 let mut opts = options();
3391 opts.emit = EmitKind::Ir;
3392 opts.safety = tier;
3393 let result = run(&opts, source);
3394 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3395 result.text().to_owned()
3396 }
3397
3398 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3399
3400 fn padded_ir(padding: Padding, source: &str) -> String {
3402 let mut opts = options();
3403 opts.emit = EmitKind::Ir;
3404 opts.safety = rucc_session::Safety::Detect;
3405 opts.padding = padding;
3406 let result = run(&opts, source);
3407 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3408 result.text().to_owned()
3409 }
3410
3411 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3412 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3413
3414 #[test]
3415 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3416 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3420 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3421 }
3422
3423 #[test]
3424 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3425 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3428 assert!(!text.contains("owns"), "{text}");
3429 }
3430
3431 #[test]
3432 fn a_member_of_a_union_owns_nothing_after_it() {
3433 let text = padded_ir(
3437 Padding::Ignored,
3438 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3439 );
3440 assert!(!text.contains("owns"), "{text}");
3441 }
3442
3443 #[test]
3444 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3445 let text = padded_ir(
3450 Padding::Ignored,
3451 "struct inner { char c; };\n\
3452 struct outer { struct inner in; int x; };\n\
3453 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3454 );
3455 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3456 }
3457
3458 #[test]
3459 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3460 let text = ir(READS_THROUGH_A_POINTER);
3464 assert!(!text.contains("check_"), "{text}");
3465 assert!(!text.contains("cap_of"), "{text}");
3466 }
3467
3468 #[test]
3469 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3470 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3471 assert!(text.contains("cap_of"), "{text}");
3472 assert!(text.contains("check_bounds"), "{text}");
3473 assert!(text.contains("check_live"), "{text}");
3474 assert!(text.contains("check_deriv"), "{text}");
3476 assert!(text.contains("check_type"), "{text}");
3478 }
3479
3480 #[test]
3481 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3482 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3486 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3487 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3488 }
3489 }
3490
3491 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3493 let mut opts = options();
3494 opts.emit = EmitKind::SafetySummary;
3495 opts.safety = tier;
3496 let result = run(&opts, source);
3497 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3498 result.text().to_owned()
3499 }
3500
3501 #[test]
3502 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3503 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3504 assert!(text.contains("\"tier\": \"detect\""), "{text}");
3505 assert!(
3507 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
3508 "{text}"
3509 );
3510 assert!(
3511 text.contains(
3512 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
3513 ),
3514 "{text}"
3515 );
3516 }
3517
3518 #[test]
3519 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
3520 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
3524 assert!(text.contains("\"tier\": \"off\""), "{text}");
3525 assert!(
3526 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
3527 "{text}"
3528 );
3529 }
3530
3531 #[test]
3532 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
3533 let text = summary(
3534 rucc_session::Safety::Detect,
3535 "void *memcpy(void *, const void *, unsigned long);\n\
3536 int puts(const char *);\n\
3537 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
3538 );
3539 assert!(text.contains("\"interposed\": 1"), "{text}");
3540 assert!(text.contains("\"puts\""), "{text}");
3541 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
3545 }
3546
3547 #[test]
3548 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3549 let text = summary(
3553 rucc_session::Safety::Detect,
3554 "void *notes_open(void);\n\
3555 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3556 );
3557 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3558 assert!(text.contains("\"notes_open\""), "{text}");
3559 }
3560
3561 #[test]
3562 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3563 let text = summary(
3566 rucc_session::Safety::Detect,
3567 "static int len(const char *p) { return p ? 1 : 0; }\n\
3568 int f(void) { return len(\"x\"); }\n",
3569 );
3570 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3571 }
3572
3573 fn granules(source: &str) -> String {
3575 let mut opts = options();
3576 opts.emit = EmitKind::TypeGranules;
3577 let result = run(&opts, source);
3578 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3579 result.text().to_owned()
3580 }
3581
3582 #[test]
3583 fn the_granule_report_names_every_record_and_both_keyings() {
3584 let text = granules(
3585 "struct hot { char *p; int a; int b; };\n\
3586 int f(struct hot *h) { return h->a; }\n",
3587 );
3588 assert!(text.contains("struct hot"), "{text}");
3589 assert!(text.contains("every type distinct"), "{text}");
3592 assert!(text.contains("every pointer one type"), "{text}");
3593 assert!(text.contains("budget"), "{text}");
3594 }
3595
3596 #[test]
3597 fn a_record_nothing_uses_is_still_measured() {
3598 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3601 assert!(text.contains("struct unused"), "{text}");
3602 }
3603
3604 #[test]
3605 fn the_granule_report_stops_before_anything_is_lowered() {
3606 let text = granules(
3610 "struct wide { long double d; };\n\
3611 long double f(long double x) { return x * x; }\n",
3612 );
3613 assert!(text.contains("struct wide"), "{text}");
3614 }
3615
3616 #[test]
3617 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
3618 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
3621 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
3622 }
3623
3624 #[test]
3625 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
3626 let text = summary(
3627 rucc_session::Safety::Detect,
3628 "unsigned long f(int *p) { return (unsigned long) p; }\n",
3629 );
3630 assert!(text.contains("\"exposed\": 1"), "{text}");
3631 }
3632
3633 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
3635 let mut opts = options();
3636 opts.emit = EmitKind::Asm;
3637 opts.safety = tier;
3638 let result = run(&opts, source);
3639 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3640 result.text().to_owned()
3641 }
3642
3643 #[test]
3644 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
3645 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3646 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
3647 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
3648 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
3649 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
3650 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
3651 }
3652
3653 #[test]
3654 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
3655 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3659 let section = format!("\t.section\t{},", rucc_safety::SECTION);
3660 assert_eq!(text.matches(§ion).count(), 5, "{text}");
3661 for index in 0..5 {
3662 let name = format!("__rucc_safety_desc_{index}");
3663 assert!(text.contains(&format!("{name}:\n")), "{text}");
3666 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
3667 }
3668 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
3669 }
3670
3671 #[test]
3679 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
3680 let text = ir(concat!(
3681 "int g;\n",
3682 "int a = __builtin_constant_p(1);\n",
3683 "int b = __builtin_constant_p(g);\n",
3684 "int c = __builtin_constant_p(\"abc\");\n",
3685 "int d = __builtin_constant_p(&g);\n",
3686 "int e = __builtin_constant_p(1.5);\n",
3687 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
3688 ));
3689 assert!(text.contains("global @a : i32 = 1,"), "{text}");
3690 assert!(text.contains("global @b : i32 = 0,"), "{text}");
3691 assert!(text.contains("global @c : i32 = 1,"), "{text}");
3692 assert!(text.contains("global @d : i32 = 0,"), "{text}");
3693 assert!(text.contains("global @e : i32 = 1,"), "{text}");
3694 assert!(text.contains("global @h : i32 = 11,"), "{text}");
3695 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
3696
3697 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
3701 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
3702 }
3703
3704 #[test]
3713 fn a_call_to_a_library_builtin_reaches_the_library_function() {
3714 let text = body("void f(void) { __builtin_abort(); }\n");
3715 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
3716
3717 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
3720 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
3721 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
3722 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3723 }
3724
3725 #[test]
3738 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
3739 let text = ir(concat!(
3740 "char d[8];\n",
3741 "void f(const char *s, unsigned long n) {\n",
3742 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3743 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
3744 " __builtin___memset_chk(d, 0, n, 8);\n",
3745 "}\n",
3746 ));
3747 assert!(text.contains("call @__memcpy_chk("), "{text}");
3748 assert!(text.contains("call @__strcpy_chk("), "{text}");
3749 assert!(text.contains("call @__memset_chk("), "{text}");
3750 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
3751 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3752 }
3753
3754 #[test]
3762 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
3763 let text = ir(concat!(
3764 "extern char *p;\n",
3765 "char d[8];\n",
3766 "void f(const char *s, unsigned long n) {\n",
3767 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3768 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3769 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
3770 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3771 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
3772 "}\n",
3773 ));
3774
3775 assert!(
3777 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
3778 "{text}"
3779 );
3780
3781 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3784 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
3785 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3786
3787 assert!(text.contains("call @__sprintf_chk("), "{text}");
3790
3791 let asm = asm(concat!(
3794 "void f(char *p, const char *s, unsigned long n) {\n",
3795 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3796 "}\n",
3797 ));
3798 assert!(asm.contains("call\tmemcpy"), "{asm}");
3799 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
3800 }
3801
3802 #[test]
3810 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
3811 let text = ir(concat!(
3812 "char d[64];\n",
3813 "int f(const char *fmt, ...) {\n",
3814 " __builtin_va_list ap;\n",
3815 " __builtin_va_start(ap, fmt);\n",
3816 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
3817 " __builtin_va_end(ap);\n",
3818 " return n;\n",
3819 "}\n",
3820 ));
3821 assert!(text.contains("call @__vsprintf_chk("), "{text}");
3822 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
3823 }
3824
3825 #[test]
3836 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
3837 let text = body(concat!(
3838 "long long llabs(long long);\n",
3839 "long long f(long long x) { return llabs(x); }\n",
3840 ));
3841 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
3842 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
3843 assert!(text.contains("%3 = xor %0, %2"), "{text}");
3844 assert!(text.contains("%4 = sub %3, %2"), "{text}");
3845 assert!(!text.contains("call"), "the call does not happen:\n{text}");
3846
3847 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
3850 assert!(text.contains("iconst.i32 31"), "{text}");
3851 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
3852 assert!(text.contains("iconst.i64 63"), "{text}");
3853
3854 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
3857 assert!(!text.contains("call"), "{text}");
3858
3859 let text = ir(concat!(
3861 "long long llabs(long long b);\n",
3862 "long long g(long long x) { return llabs(x); }\n",
3863 "long long llabs(long long b) { return 7; }\n",
3864 ));
3865 assert!(!text.contains("call @llabs"), "{text}");
3866 }
3867
3868 #[test]
3875 fn a_byte_swap_is_arithmetic_and_not_a_call() {
3876 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
3877 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
3878
3879 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
3882 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
3883 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
3884 }
3885
3886 #[test]
3892 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
3893 for (name, ty, width) in [
3894 ("__builtin_bswap16", "unsigned short", "i16"),
3895 ("__builtin_bswap32", "unsigned", "i32"),
3896 ("__builtin_bswap64", "unsigned long long", "i64"),
3897 ] {
3898 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
3899 let text = body(&source);
3900 assert_eq!(
3901 text,
3902 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
3903 "{name}"
3904 );
3905 }
3906 }
3907
3908 #[test]
3915 fn the_bit_counts_are_instructions_and_not_calls() {
3916 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
3917 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
3918
3919 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
3920 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
3921
3922 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
3923 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
3924 }
3925
3926 #[test]
3935 fn the_bit_counts_ask_about_the_width_their_name_says() {
3936 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
3937 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
3938 assert!(text.contains("%1 = ctlz %0"), "{text}");
3939 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
3940
3941 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
3944 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
3945 assert!(text.contains("ctlz %1"), "and counted there: {text}");
3946
3947 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
3948 assert!(text.contains("%1 = ctpop %0"), "{text}");
3949 assert!(!text.contains("call"), "{text}");
3950 }
3951
3952 #[test]
3957 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
3958 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
3959 assert!(text.contains("%1 = ctpop %0"), "{text}");
3960 assert!(text.contains("iconst.i32 1"), "{text}");
3961 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
3962 }
3963
3964 #[test]
3970 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
3971 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
3972 assert!(text.contains("%1 = cttz %0"), "{text}");
3973 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
3974 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
3975 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
3976 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
3977 assert!(!text.contains("br_if"), "no branch: {text}");
3978 }
3979
3980 #[test]
3990 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
3991 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
3992 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
3993 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
3994 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
3995 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
3996 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
3997 assert!(text.contains("%7 = ctlz %6"), "{text}");
3998 assert!(!text.contains("call"), "{text}");
3999 assert!(!text.contains("br_if"), "no branch: {text}");
4000 }
4001
4002 #[test]
4008 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4009 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4010 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4011 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4012 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4013
4014 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4015 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4016
4017 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4020 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4021 }
4022
4023 #[test]
4031 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4032 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4033 assert!(text.contains("iconst.i64 63"), "{text}");
4034 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4035 assert!(!text.contains("call"), "{text}");
4036
4037 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4038 assert!(text.contains("iconst.i64 63"), "{text}");
4039 assert!(!text.contains("call"), "{text}");
4040 }
4041
4042 #[test]
4050 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4051 let text =
4052 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4053 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4054 assert!(!text.contains("store"), "nothing is written: {text}");
4055 assert!(!text.contains("call"), "{text}");
4056
4057 let text =
4060 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4061 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4062 assert!(!text.contains("store"), "{text}");
4063
4064 let text = body(concat!(
4067 "int g(void);\n",
4068 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4069 ));
4070 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4071 }
4072
4073 #[test]
4083 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4084 let text =
4085 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4086 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4087 assert!(text.contains("store %3 -> %2"), "{text}");
4088 assert!(!text.contains("call"), "{text}");
4089
4090 let text =
4091 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4092 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4093
4094 let text =
4095 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4096 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4097
4098 let text = body(
4101 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4102 );
4103 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4104 }
4105
4106 #[test]
4114 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4115 let text = body(
4116 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4117 );
4118 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4119 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4120 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4121
4122 let text = body(
4125 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4126 );
4127 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4128 assert!(!text.contains("sext."), "{text}");
4129 assert!(!text.contains("zext.i64"), "{text}");
4131 }
4132
4133 #[test]
4141 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4142 let text =
4143 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4144 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4145 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4146 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4147 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4148 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4149 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4150 }
4151
4152 #[test]
4159 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4160 for name in ["add", "sub", "mul"] {
4161 let source = format!(
4162 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4163 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4164 );
4165 let mut opts = options();
4166 opts.emit = EmitKind::MirFinal;
4167 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4168 }
4169 }
4170
4171 #[test]
4174 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4175 let messages =
4176 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4177 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4178
4179 let messages =
4180 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4181 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4182 }
4183
4184 #[test]
4195 fn an_ordered_access_is_ordered_in_the_ir() {
4196 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4197 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4198
4199 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4200 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4201
4202 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4203 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4204
4205 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4206 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4207
4208 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4211 assert!(text.contains("trunc.i8 %1"), "{text}");
4212 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4213 }
4214
4215 #[test]
4224 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4225 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4226 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4227 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4228
4229 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4230 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4231 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4232
4233 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4234 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4235 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4236 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4237 }
4238
4239 #[test]
4249 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4250 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4251 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4252
4253 for weaker in ["1", "2", "3", "4"] {
4254 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4255 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4256 }
4257 }
4258
4259 #[test]
4265 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4266 let text =
4269 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4270 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4271 assert!(text.contains("return %3"), "the value it found: {text}");
4272
4273 let text =
4274 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4275 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4276 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4277
4278 let text = body(
4281 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4282 );
4283 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4284 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4285 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4286 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4287
4288 let text = body(
4291 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4292 );
4293 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4294 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4295 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4296 }
4297
4298 #[test]
4305 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4306 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4307 for (ty, suffix, reg) in widths {
4308 let source = format!(
4309 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4310 );
4311 let text = asm(&source);
4312 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4313 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4314 assert!(text.contains("sete\t"), "{ty}: {text}");
4315 }
4316 let source =
4317 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4318 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4319
4320 for order in ["0", "2", "3", "4", "5"] {
4324 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4325 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4326 let text = asm(&source);
4327 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4328 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4329 }
4330 }
4331
4332 #[test]
4344 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4345 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4346 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4347 assert!(text.contains("return %2"), "the value that was there: {text}");
4348
4349 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4350 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4351 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4352
4353 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4354 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4355 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4356
4357 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4359 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4360
4361 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4364 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4365
4366 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4367 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4368
4369 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4372 assert!(text.contains("release"), "{text}");
4373 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4374
4375 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4379 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4380 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4381
4382 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4385 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4386
4387 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4388 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4389 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4390
4391 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4394 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4395 assert!(text.contains("%3 = and %2, %1"), "{text}");
4396 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4397 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4398 }
4399
4400 #[test]
4411 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4412 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4413 for (ty, suffix, reg) in widths {
4414 for (name, call, insn) in [
4415 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4416 ("or", "__sync_fetch_and_or(p, v)", "or"),
4417 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4418 ] {
4419 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4420 let text = asm(&source);
4421 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4422 assert!(
4423 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4424 "{ty} {name}: {text}"
4425 );
4426 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4427 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4429 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4430 }
4431 }
4432 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4433 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4434
4435 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4439 assert!(text.contains("cmpxchgl\t"), "{text}");
4440 assert!(text.contains("andl\t"), "{text}");
4441 assert!(text.contains("notl\t"), "{text}");
4442 }
4443
4444 #[test]
4453 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4454 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4455 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4456 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4457
4458 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4459 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4460 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4461
4462 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4465 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4466 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4467 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4468 }
4469
4470 #[test]
4481 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4482 for pointer in ["char", "int", "void"] {
4483 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4484 let text = body(&source);
4485 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4486 assert!(
4487 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4488 "{pointer}: {text}"
4489 );
4490 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4491
4492 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4493 let text = body(&source);
4494 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4495 }
4496
4497 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4500 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4501 assert!(text.contains("setne\t"), "{text}");
4502 }
4503
4504 #[test]
4512 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4513 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4514 for (ty, suffix, reg) in widths {
4515 let source =
4516 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4517 let text = asm(&source);
4518 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4519 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4520
4521 let source =
4522 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4523 let text = asm(&source);
4524 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4525 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4526 }
4527 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4528 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4529
4530 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4533 let text = asm(source);
4534 assert!(text.contains("negl\t"), "{text}");
4535 assert!(text.contains("xaddl\t"), "{text}");
4536
4537 for order in ["0", "2", "3", "4", "5"] {
4540 let source =
4541 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4542 let text = asm(&source);
4543 assert!(text.contains("xaddl\t"), "{order}: {text}");
4544 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4545 }
4546
4547 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4551 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4552 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4557 assert!(text.contains("movl\t$0, %eax"), "{text}");
4558 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4559 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4560 }
4561
4562 #[test]
4574 fn the_lock_free_questions_are_answered_as_constants() {
4575 for size in ["1", "2", "4", "8"] {
4576 let source =
4577 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4578 let text = asm(&source);
4579 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4580 assert!(!text.contains("call"), "and is not a call: {text}");
4581 }
4582 for size in ["3", "16", "sizeof(long double)"] {
4583 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4584 let text = asm(&source);
4585 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
4586 assert!(!text.contains("call"), "and is not a call either: {text}");
4587 }
4588
4589 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
4593 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
4594 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
4595 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
4596 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
4597 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
4598 }
4599
4600 #[test]
4612 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
4613 let mut opts = options();
4614 opts.emit = EmitKind::Ir;
4615
4616 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
4617 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
4618 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
4619
4620 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
4621 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
4622 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
4623
4624 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
4625 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
4626 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
4627 }
4628
4629 #[test]
4641 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
4642 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
4643 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
4644 assert!(text.contains("shrq"), "with the value halved first: {text}");
4645 assert!(text.contains("addsd"), "and doubled after: {text}");
4646 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4647
4648 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
4649 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
4650 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
4651 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
4652 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4653 }
4654
4655 #[test]
4666 fn a_plain_name_the_program_took_is_the_programs_own_function() {
4667 let taken = concat!(
4668 "static long long llabs(long long b) { return 7; }\n",
4669 "long long f(long long x) { return llabs(x); }\n",
4670 );
4671 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
4672
4673 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
4674 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
4675
4676 let plain = concat!(
4677 "long long llabs(long long b);\n",
4678 "long long f(long long x) { return llabs(x); }\n",
4679 );
4680 let mut opts = options();
4681 opts.emit = EmitKind::Ir;
4682 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
4683
4684 opts.builtins = false;
4685 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
4686
4687 opts.builtins = true;
4688 opts.no_builtin = vec!["llabs".to_owned()];
4689 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
4690 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
4691 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
4692
4693 opts.no_builtin = Vec::new();
4696 opts.builtins = false;
4697 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
4698 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
4699 }
4700
4701 #[test]
4714 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
4715 let text = ir(concat!(
4716 "long a = __builtin_expect(7, 1);\n",
4717 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
4718 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
4719 ));
4720 assert!(text.contains("global @a : i64 = 7,"), "{text}");
4721 assert!(text.contains("global @b : i64 = 9,"), "{text}");
4722 assert!(text.contains("global @c : i64 = 8,"), "{text}");
4723 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
4724
4725 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
4728 assert!(text.contains("sext"), "{text}");
4729
4730 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
4734 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
4735 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
4736 assert_eq!(body(source), one);
4737
4738 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
4743 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
4744 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
4745 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
4746 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
4747 }
4748
4749 #[test]
4761 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
4762 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
4763 let text = ir(promised);
4764 assert!(text.contains(" unreachable_hint\n"), "{text}");
4765 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
4766
4767 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
4771 assert!(after.contains("return"), "{after}");
4772
4773 let text = asm(promised);
4776 let mine = text.split_once("\nf:\n").expect("a definition").1;
4777 let mine = mine.split_once("\t.size").expect("a definition").0;
4778 let plain = asm("int f(int x) { if (x) return 1; }\n");
4779 let plain = plain.split_once("\nf:\n").expect("a definition").1;
4780 let plain = plain.split_once("\t.size").expect("a definition").0;
4781 assert_eq!(mine, plain);
4782 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
4785 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
4786 assert!(!mine.contains("ud2"), "{mine}");
4787 }
4788
4789 #[test]
4796 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
4797 let mut opts = options();
4798 opts.emit = EmitKind::Ir;
4799 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
4800 assert!(
4801 messages.iter().any(|m| m.contains("__builtin_abort")),
4802 "expected the written name in {messages:?}"
4803 );
4804 }
4805
4806 #[test]
4814 fn a_builtin_nothing_lowers_is_refused_by_name() {
4815 let mut opts = options();
4816 opts.emit = EmitKind::Ir;
4817 let builtin = "__atomic_signal_fence";
4818 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
4819 let messages = run(&opts, &source).messages;
4820 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
4821 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
4822 }
4823
4824 #[test]
4833 fn what_is_refused_is_the_call_and_not_the_name() {
4834 let text = ir(concat!(
4835 "void __atomic_signal_fence(int order) { (void)order; }\n",
4836 "void f(void) { __atomic_signal_fence(5); }\n",
4837 ));
4838 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
4839 }
4840
4841 #[test]
4850 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
4851 let text = ir(concat!(
4852 "struct S { char a[8]; int n; char b[12]; };\n",
4853 "char g[32];\n",
4854 "struct S gs;\n",
4855 "unsigned long whole = __builtin_object_size(g, 0);\n",
4856 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
4857 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
4858 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
4859 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
4860 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
4861 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
4862 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
4863 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
4864 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
4865 ));
4866 for (name, size) in [
4867 ("whole", 32),
4868 ("moved", 28),
4869 ("back", 4),
4870 ("outer", 24),
4871 ("inner", 8),
4872 ("scalar", 4),
4873 ("after", 16),
4874 ("into", 10),
4875 ("text", 6),
4876 ("dyn", 12),
4877 ] {
4878 let said = format!("global @{name} : i64 = {size},");
4879 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
4880 }
4881 }
4882
4883 #[test]
4891 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
4892 let text = body(concat!(
4893 "struct S { char a[8]; int n; char b[12]; };\n",
4894 "unsigned long f(void) {\n",
4895 " char loc[20];\n",
4896 " struct S ls;\n",
4897 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
4898 "}\n",
4899 ));
4900 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
4901 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
4902 }
4903
4904 #[test]
4914 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
4915 let text = ir(concat!(
4916 "struct T { int n; char f[]; };\n",
4917 "extern char *p;\n",
4918 "extern struct T *t;\n",
4919 "unsigned long largest = __builtin_object_size(p, 0);\n",
4920 "unsigned long nearest = __builtin_object_size(p, 1);\n",
4921 "unsigned long least = __builtin_object_size(p, 2);\n",
4922 "unsigned long tight = __builtin_object_size(p, 3);\n",
4923 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
4924 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
4925 ));
4926 for name in ["largest", "nearest", "flex"] {
4927 let said = format!("global @{name} : i64 = -1,");
4931 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
4932 }
4933 for name in ["least", "tight"] {
4934 let said = format!("global @{name} : i64 = 0,");
4935 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
4936 }
4937 assert!(text.contains("global @says : i32 = 1,"), "{text}");
4938 }
4939
4940 #[test]
4947 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
4948 let text = body(concat!(
4949 "extern char *side(void);\n",
4950 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
4951 ));
4952 assert!(!text.contains("call"), "nothing is called: {text}");
4953 }
4954
4955 #[test]
4960 fn a_kind_that_is_not_one_of_the_four_is_refused() {
4961 for source in [
4962 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
4963 + "{ return __builtin_object_size(p, k); }\n",
4964 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
4965 .to_owned(),
4966 "extern char *p;\nunsigned long f(void) ".to_owned()
4967 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
4968 ] {
4969 let messages = errors(&source);
4970 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
4971 assert!(named, "expected a complaint about the kind in {messages:?}");
4972 }
4973 }
4974
4975 #[test]
4980 fn a_static_function_nothing_refers_to_is_not_emitted() {
4981 let text = ir("static int dropped(void) { return 1; }\n\
4982 static int kept(void) { return 2; }\n\
4983 int main(void) { return kept(); }\n");
4984 assert!(text.contains("func @kept"), "{text}");
4985 assert!(!text.contains("dropped"), "{text}");
4986 }
4987
4988 #[test]
4994 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
4995 let text = ir("static int ping(void);\n\
4996 static int pong(void) { return ping(); }\n\
4997 static int ping(void) { return pong(); }\n\
4998 int main(void) { return 0; }\n");
4999 assert!(!text.contains("ping"), "{text}");
5000 assert!(!text.contains("pong"), "{text}");
5001 }
5002
5003 #[test]
5009 fn naming_a_static_function_anywhere_keeps_it() {
5010 let text = ir("static int by_address(void) { return 1; }\n\
5011 static int in_an_image(void) { return 2; }\n\
5012 static int deeper(void) { return 3; }\n\
5013 static int reaches_deeper(void) { return deeper(); }\n\
5014 static int (*table[1])(void) = {in_an_image};\n\
5015 int main(void) {\n\
5016 int (*p)(void) = by_address;\n\
5017 return p() + table[0]() + reaches_deeper();\n\
5018 }\n");
5019 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5020 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5021 }
5022 }
5023
5024 #[test]
5030 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5031 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5032 let source = format!(
5033 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5034 int main(void) {{ return 0; }}\n"
5035 );
5036 let text = ir(&source);
5037 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5038 }
5039 }
5040
5041 #[test]
5044 fn a_function_anything_could_call_is_emitted_without_being_called() {
5045 let text =
5046 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5047 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5048 }
5049
5050 #[test]
5057 fn a_classification_c_has_an_operator_for_is_that_operator() {
5058 for (builtin, operator) in [
5059 ("__builtin_isgreater", "binary >"),
5060 ("__builtin_isgreaterequal", "binary >="),
5061 ("__builtin_isless", "binary <"),
5062 ("__builtin_islessequal", "binary <="),
5063 ] {
5064 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5065 let text = tast(&source);
5066 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5067 }
5068 }
5069
5070 #[test]
5079 fn the_classification_builtins_are_comparisons_and_not_calls() {
5080 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5081 assert_eq!(
5082 text,
5083 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5084 %2\n return %3\n"
5085 );
5086
5087 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5089 assert!(text.contains("fcmp one %0, %1"), "{text}");
5090
5091 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5092 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5093
5094 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5095 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5096 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5097 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5098 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5099 assert!(text.contains("%5 = or %3, %4"), "{text}");
5100
5101 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5104 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5105 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5106 assert!(text.contains("%5 = and %3, %4"), "{text}");
5107
5108 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5109 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5110 assert!(text.contains("icmp slt %1, %2"), "{text}");
5111
5112 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5115 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5116
5117 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5120 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5121 }
5122
5123 #[test]
5130 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5131 let text = ir(concat!(
5132 "int a = __builtin_isinff(1e300);\n",
5133 "int b = __builtin_isinf(1e300);\n",
5134 "int c = __builtin_isnan(0.0);\n",
5138 "int d = __builtin_signbit(-0.0);\n",
5139 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5140 ));
5141 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5142 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5143 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5144 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5145 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5146 }
5147
5148 #[test]
5150 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5151 let mut opts = options();
5152 opts.emit = EmitKind::Ir;
5153 let source = concat!(
5154 "int a(int x) { return __builtin_isnan(x); }\n",
5155 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5156 "int c(double x) { return __builtin_isnan(x, x); }\n",
5157 );
5158 let messages = run(&opts, source).messages;
5159 assert_eq!(
5160 messages,
5161 [
5162 "/main.c:1:23: error: non-floating-point argument in call to function \
5163 '__builtin_isnan' [E0685]",
5164 "/main.c:2:30: error: non-floating-point arguments in call to function \
5165 '__builtin_isunordered' [E0685]",
5166 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5167 ]
5168 );
5169 }
5170
5171 #[test]
5180 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5181 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5182 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5186 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5187 assert!(text.contains("%3 = and %1, %2"), "{text}");
5188 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5189 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5190 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5191 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5192 assert!(text.contains("%8 = and %6, %7"), "{text}");
5193
5194 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5198 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5199 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5200
5201 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5202 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5203 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5204 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5205
5206 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5207 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5208 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5209 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5213 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5214 assert!(!text.contains("call"), "{text}");
5215
5216 let text = body(concat!(
5219 "double g(void);\n",
5220 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5221 ));
5222 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5223 }
5224
5225 #[test]
5232 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5233 let text = ir(concat!(
5234 "int a = __builtin_isnormal(1.0);\n",
5235 "int b = __builtin_isnormal(0.0);\n",
5236 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5237 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5238 "int e = __builtin_isinf_sign(1.0);\n",
5239 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5240 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5241 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5242 ));
5243 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5244 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5245 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5246 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5247 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5248 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5249 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5250 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5251 }
5252
5253 #[test]
5259 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5260 let mut opts = options();
5261 opts.emit = EmitKind::Ir;
5262 let source = concat!(
5263 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5264 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5265 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5266 );
5267 let messages = run(&opts, source).messages;
5268 assert_eq!(
5269 messages,
5270 [
5271 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5272 '__builtin_fpclassify' [E0687]",
5273 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5274 [E0511]",
5275 "/main.c:3:23: error: non-floating-point argument in call to function \
5276 '__builtin_fpclassify' [E0685]",
5277 ]
5278 );
5279 }
5280
5281 #[test]
5289 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5290 let text = ir(concat!(
5291 "double a = __builtin_inf();\n",
5292 "float b = __builtin_huge_valf();\n",
5293 "long double c = __builtin_infl();\n",
5294 "double d = __builtin_huge_val();\n",
5295 ));
5296 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5297 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5298 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5299 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5300 assert!(!text.contains("call"), "{text}");
5301 }
5302
5303 #[test]
5312 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5313 let text = ir(concat!(
5314 "double a = __builtin_nan(\"\");\n",
5315 "double b = __builtin_nan(\"0x1\");\n",
5316 "double c = __builtin_nan(\"010\");\n",
5318 "double d = __builtin_nans(\"\");\n",
5319 "double e = __builtin_nans(\"0x1\");\n",
5320 "float f = __builtin_nanf(\"0x1\");\n",
5321 "float g = __builtin_nansf(\"\");\n",
5322 "long double h = __builtin_nansl(\"\");\n",
5323 ));
5324 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5325 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5326 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5327 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5328 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5329 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5330 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5331 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5332
5333 let text = ir(concat!(
5336 "double f(const char *p) { return __builtin_nan(p); }\n",
5337 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5338 ));
5339 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5340 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5341 }
5342
5343 #[test]
5351 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5352 let text = ir(concat!(
5353 "unsigned long a = __builtin_strlen(\"hello\");\n",
5354 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5355 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5356 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5357 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5358 ));
5359 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5360 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5361 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5362 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5363 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5364 assert!(!text.contains("call"), "{text}");
5365
5366 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5368 assert!(text.contains("call @strlen("), "{text}");
5369 }
5370
5371 #[test]
5378 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5379 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5380 assert!(text.contains("bitcast.i64 %0"), "{text}");
5381 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5382 assert!(text.contains("and %1, %2"), "{text}");
5383 assert!(text.contains("bitcast.f64 %3"), "{text}");
5384 assert!(!text.contains("call"), "{text}");
5385
5386 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5387 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5388 assert!(text.contains("%8 = or %4, %7"), "{text}");
5389 assert!(!text.contains("call"), "{text}");
5390
5391 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5394 assert!(text.contains("bitcast.i80 %0"), "{text}");
5395 assert!(text.contains("bitcast.f80"), "{text}");
5396
5397 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5400 assert!(text.contains("fpext.f64 %0"), "{text}");
5401 assert!(text.contains("bitcast.i64 %1"), "{text}");
5402 }
5403
5404 #[test]
5413 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5414 let text =
5415 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5416 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5417 assert!(!text.contains("call"), "{text}");
5418
5419 let text =
5420 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5421 assert!(text.contains("bitcast.i32 %0"), "{text}");
5422 assert!(!text.contains("call"), "{text}");
5423
5424 let text = body(concat!(
5425 "double copysign(double x, double y);\n",
5426 "double f(double x, double y) { return copysign(x, y); }\n",
5427 ));
5428 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5429 assert!(!text.contains("call"), "{text}");
5430
5431 let text = body(concat!(
5432 "float copysignf(float x, float y);\n",
5433 "float f(float x, float y) { return copysignf(x, y); }\n",
5434 ));
5435 assert!(!text.contains("call"), "{text}");
5436
5437 let text = ir(concat!(
5441 "long double fabsl(long double x);\n",
5442 "long double f(long double x) { return fabsl(x); }\n",
5443 ));
5444 assert!(text.contains("call @fabsl"), "{text}");
5445 }
5446
5447 #[test]
5455 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5456 let taken = concat!(
5457 "static double fabs(double b) { return 7; }\n",
5458 "double f(double x) { return fabs(x); }\n",
5459 );
5460 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5461
5462 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5463 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5464
5465 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
5466 let mut opts = options();
5467 opts.emit = EmitKind::Ir;
5468 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
5469
5470 opts.builtins = false;
5471 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
5472
5473 opts.builtins = true;
5474 opts.no_builtin = vec!["fabs".to_owned()];
5475 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
5476 let one = concat!(
5477 "double copysign(double a, double b);\n",
5478 "double f(double x) { return copysign(x, 1.0); }\n",
5479 );
5480 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
5481
5482 opts.no_builtin = Vec::new();
5484 opts.builtins = false;
5485 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
5486 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
5487 }
5488
5489 #[test]
5498 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
5499 let text = ir(concat!(
5500 "double a = __builtin_fabs(-3.5);\n",
5501 "double b = __builtin_copysign(1.0, -0.0);\n",
5502 "double c = __builtin_copysign(0.0, -2.0);\n",
5503 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
5505 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
5506 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
5507 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
5508 "long double i = __builtin_fabsl(-__builtin_infl());\n",
5509 ));
5510 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
5511 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
5512 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
5513 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
5514 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
5515 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
5516 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
5517 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5518 }
5519
5520 #[test]
5528 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
5529 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
5530 assert!(!text.contains("call"), "{text}");
5531 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
5532 assert!(!text.contains("call"), "{text}");
5533
5534 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
5537 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5538 assert!(!text.contains("call"), "{text}");
5539 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
5540 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
5541
5542 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
5545 assert_eq!(written, text, "the name and the operator are the same thing");
5546
5547 let text = body(concat!(
5549 "double creal(_Complex double z);\n",
5550 "double f(_Complex double z) { return creal(z); }\n",
5551 ));
5552 assert!(!text.contains("call"), "{text}");
5553 let text = body(concat!(
5554 "_Complex float conjf(_Complex float z);\n",
5555 "_Complex float f(_Complex float z) { return conjf(z); }\n",
5556 ));
5557 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5558 assert!(!text.contains("call"), "{text}");
5559
5560 let taken = concat!(
5563 "static double creal(_Complex double z) { return 7; }\n",
5564 "double f(_Complex double z) { return creal(z); }\n",
5565 );
5566 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
5567 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
5568 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
5569 let plain = concat!(
5570 "double cimag(_Complex double z);\n",
5571 "double f(_Complex double z) { return cimag(z); }\n",
5572 );
5573 let mut opts = options();
5574 opts.emit = EmitKind::Ir;
5575 opts.builtins = false;
5576 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
5577 opts.builtins = true;
5578 opts.no_builtin = vec!["cimag".to_owned()];
5579 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
5580
5581 let text = ir(concat!(
5583 "double a = __builtin_creal(1.5 + 2.5i);\n",
5584 "double b = __builtin_cimag(1.5 + 2.5i);\n",
5585 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
5586 ));
5587 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
5588 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
5589 assert!(
5590 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
5591 "the conjugate of a constant is the constant with the second half negated: {text}"
5592 );
5593 assert!(!text.contains("call"), "{text}");
5594 }
5595
5596 #[test]
5604 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
5605 let text = ir(concat!(
5606 "double a = __builtin_ceil(1.5);\n",
5607 "double b = __builtin_floor(1.5);\n",
5608 "double c = __builtin_trunc(-1.5);\n",
5609 "double d = __builtin_round(2.5);\n",
5612 "double e = __builtin_ceil(-0.5);\n",
5614 "double f = __builtin_fmax(1.0, 2.0);\n",
5615 "double g = __builtin_fmin(1.0, 2.0);\n",
5616 "float h = __builtin_ceilf(1.25f);\n",
5617 "double ceil(double x);\n",
5620 "double i = ceil(2.25);\n",
5621 ));
5622 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
5623 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
5624 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
5625 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
5626 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
5627 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
5628 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
5629 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
5630 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
5631 assert!(!text.contains("call"), "{text}");
5632 }
5633
5634 #[test]
5642 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
5643 let text = ir(concat!(
5644 "double f(double x) { return __builtin_ceil(x); }\n",
5645 "float g(float x) { return __builtin_floorf(x); }\n",
5646 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
5647 ));
5648 assert!(text.contains("call @ceil("), "{text}");
5649 assert!(text.contains("call @floorf("), "{text}");
5650 assert!(text.contains("call @fmax("), "{text}");
5651
5652 let text = ir(concat!(
5656 "double f(void) { return __builtin_rint(2.5); }\n",
5657 "double g(void) { return __builtin_nearbyint(2.5); }\n",
5658 ));
5659 assert!(text.contains("call @rint("), "{text}");
5660 assert!(text.contains("call @nearbyint("), "{text}");
5661
5662 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
5665 assert!(text.contains("call @fmin("), "{text}");
5666
5667 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
5670 let mut opts = options();
5671 opts.emit = EmitKind::Ir;
5672 opts.no_builtin = vec!["ceil".to_owned()];
5673 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
5674 }
5675
5676 #[test]
5683 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
5684 let text = ir(concat!(
5685 "constexpr int side = 4;\n",
5686 "constexpr int wider = side + 1;\n",
5687 "constexpr double half = 1.5;\n",
5688 "struct point { int x; int y; };\n",
5689 "constexpr struct point origin = { 5, 6 };\n",
5690 "int square[side * side];\n",
5691 "int rectangle[wider];\n",
5692 "int rounded[(int)half * 2];\n",
5693 "int across[origin.y];\n",
5694 "enum named { four = side };\n",
5695 "int e = four;\n",
5696 ));
5697 assert!(text.contains("global @square : bytes 64 ="), "{text}");
5698 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
5699 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
5700 assert!(text.contains("global @across : bytes 24 ="), "{text}");
5701 assert!(text.contains("global @e : i32 = 4,"), "{text}");
5702
5703 let mut opts = options();
5706 opts.emit = EmitKind::Ir;
5707 let konst = "const int n = 1;\nint a[n];\n";
5708 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
5709 assert_eq!(run(&opts, konst).messages, [message]);
5710
5711 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
5713 assert_eq!(run(&opts, subscript).messages, [message]);
5714
5715 let address = "constexpr int c = 3;\nint *p = &c;\n";
5717 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
5718 pointer target type [E0514]";
5719 assert_eq!(run(&opts, address).messages, [warning]);
5720 }
5721
5722 #[test]
5731 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
5732 let mut opts = options();
5735 opts.std = Std::C17;
5736 let source = concat!(
5737 "int add(a, b)\n",
5738 "int a;\n",
5739 "int b;\n",
5740 "{ return a + b; }\n",
5741 "int promoted(c)\n",
5742 "char c;\n",
5743 "{ return c; }\n",
5744 "int narrow(char);\n",
5745 "int narrow(c)\n",
5746 "char c;\n",
5747 "{ return c; }\n",
5748 "int first(a)\n",
5749 "int a[4];\n",
5750 "{ return a[0]; }\n",
5751 );
5752 let result = run(&opts, source);
5753 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5754 let text = result.text();
5755 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
5756 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
5757 assert!(text.contains("c : char object automatic defined"), "{text}");
5759 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
5760 assert!(text.contains("first : int(int *) function external defined"), "{text}");
5762 }
5763
5764 #[test]
5771 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
5772 let mut opts = options();
5773 opts.std = Std::C17;
5774 for (source, message) in [
5775 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
5776 (
5777 "int f(a)\nint a;\nint b;\n{ return a; }\n",
5778 "3:5: error: declaration for parameter 'b' but no such parameter",
5779 ),
5780 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
5781 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
5782 (
5783 "int f(a)\nstatic int a;\n{ return a; }\n",
5784 "2:12: error: storage class specified for parameter 'a'",
5785 ),
5786 (
5787 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
5788 "2:7: error: argument 'a' doesn't match prototype",
5789 ),
5790 ] {
5791 let result = run(&opts, source);
5792 assert!(result.failed(), "expected this to fail:\n{source}");
5793 assert!(result.messages[0].contains(message), "{:?}", result.messages);
5794 }
5795
5796 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
5799 let mut older = options();
5800 older.std = Std::C89;
5801 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
5802 let result = run(&opts, implicit);
5803 assert!(
5804 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
5805 "{:?}",
5806 result.messages
5807 );
5808
5809 let mut newer = options();
5813 newer.std = Std::C23;
5814 let plain = "int f(a)\nint a;\n{ return a; }\n";
5815 let result = run(&newer, plain);
5816 assert!(!result.failed(), "{:?}", result.messages);
5817 assert_eq!(
5818 result.messages,
5819 ["/main.c:1:5: warning: old-style function definition [E0412]"]
5820 );
5821 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
5822 }
5823
5824 #[test]
5831 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
5832 let array = "int a[8] = { [3] 7 };\n";
5833 let member = "struct s { int x; } v = { x: 7 };\n";
5834 for source in [array, member] {
5835 let result = run(&options(), source);
5836 assert!(!result.failed(), "{:?}", result.messages);
5837 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
5838 }
5839
5840 let mut asked = options();
5841 asked.pedantic = true;
5842 assert_eq!(
5843 run(&asked, array).messages,
5844 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
5845 );
5846 assert_eq!(
5847 run(&asked, member).messages,
5848 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
5849 );
5850 }
5851
5852 #[test]
5859 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
5860 let text = ir(concat!(
5861 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
5862 "struct brim { char buf[9223372036854775807L]; };\n",
5863 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
5864 "unsigned long h = sizeof(struct huge_struct);\n",
5865 "unsigned long b = sizeof(struct brim);\n",
5866 "unsigned long y = sizeof(struct bitty);\n",
5867 ));
5868 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
5869 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
5870 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
5871
5872 let mut opts = options();
5873 opts.emit = EmitKind::Ir;
5874 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
5875 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
5876 assert_eq!(run(&opts, over).messages, [message]);
5877 let array = "struct wide { short buf[1L << 62]; };\n";
5878 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
5879 maximum object size '9223372036854775807' [E0537]";
5880 assert_eq!(run(&opts, array).messages[0], message);
5881 }
5882
5883 fn compile_bytes(source: &[u8]) -> Compiled {
5888 let mut opts = options();
5889 opts.emit = EmitKind::Ir;
5890 let mut fs = MemoryFileSystem::new();
5891 fs.insert("/main.c", source.to_vec());
5892 compile(&opts, "/main.c", &fs)
5893 }
5894
5895 #[test]
5902 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
5903 let mut source = b"char s[] = \"a".to_vec();
5904 source.push(0xff);
5905 source.extend_from_slice(b"b\";\nchar c = '");
5906 source.push(0xff);
5907 source.extend_from_slice(b"';\n");
5908 let result = compile_bytes(&source);
5909 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
5910 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
5911 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
5913
5914 let mut stray = b"int a".to_vec();
5915 stray.push(0xff);
5916 stray.extend_from_slice(b" = 1;\n");
5917 let result = compile_bytes(&stray);
5918 assert!(
5919 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
5920 "{:?}",
5921 result.messages
5922 );
5923 }
5924
5925 #[test]
5926 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
5927 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
5928 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
5929 let expected = "\
5930func @add(i32, i32) -> i32, linkage(external) {
5931block0(%0: i32, %1: i32):
5932 %2 = add.nsw %0, %1
5933 return %2
5934}
5935";
5936 assert!(text.contains(expected), "{text}");
5937 }
5938
5939 #[test]
5940 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
5941 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
5942 assert!(!text.contains("alloca"), "{text}");
5943 assert!(!text.contains("load"), "{text}");
5944 assert!(!text.contains("store"), "{text}");
5945 }
5946
5947 #[test]
5948 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
5949 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
5950 let expected = "\
5951block0:
5952 %0 = alloca, size 4, align 4
5953 %1 = iconst.i32 1
5954 store %1 -> %0, align 4, tbaa !1
5955 %2 = call @g(%0) : (ptr) -> i32
5956 return %2
5957";
5958 assert_eq!(text, expected);
5959 }
5960
5961 #[test]
5962 fn a_loop_carries_what_it_changes_as_block_parameters() {
5963 let text = body(
5966 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
5967 return total;\n}\n",
5968 );
5969 assert!(!text.contains("alloca"), "{text}");
5970 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
5971 assert!(text.contains("jump block1("), "{text}");
5972 }
5973
5974 #[test]
5975 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
5976 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
5977 assert!(text.contains("icmp slt %0, %1"), "{text}");
5978 assert!(!text.contains("zext"), "{text}");
5979 }
5980
5981 #[test]
5982 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
5983 let text = body("int f(int a, int b) { return a && b; }\n");
5984 let expected = "\
5985block0(%0: i32, %1: i32):
5986 %2 = iconst.i32 0
5987 %3 = icmp ne %0, %2
5988 %4 = iconst.i1 0
5989 br_if %3, block1, block2(%4)
5990
5991block1:
5992 %5 = iconst.i32 0
5993 %6 = icmp ne %1, %5
5994 jump block2(%6)
5995
5996block2(%7: i1):
5997 %8 = zext.i32 %7
5998 return %8
5999";
6000 assert_eq!(text, expected);
6001 }
6002
6003 #[test]
6004 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6005 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6006 assert!(!text.contains("block3"), "{text}");
6009 assert!(!text.contains("iconst.i32 3"), "{text}");
6010 }
6011
6012 #[test]
6013 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6014 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6015 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6016 assert!(body("int f(void) { }\n").contains("unreachable"));
6017 }
6018
6019 #[test]
6020 fn a_structure_is_copied_rather_than_held_in_a_value() {
6021 let text = body(
6022 "struct point { int x, y; };\n\
6023 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6024 );
6025 assert!(text.contains("memcpy"), "{text}");
6026 }
6027
6028 #[test]
6029 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6030 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6031 assert!(text.contains("memset"), "{text}");
6032 }
6033
6034 #[test]
6035 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6036 let text = body(
6037 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6038 default: r = 4; } return r; }\n",
6039 );
6040 let expected = "\
6041block0(%0: i32):
6042 %1 = iconst.i32 0
6043 switch %0, block1, [1 => block2, 2 => block3(%1)]
6044
6045block1:
6046 %2 = iconst.i32 4
6047 jump block4(%2)
6048
6049block2:
6050 %3 = iconst.i32 1
6051 jump block3(%3)
6052
6053block3(%4: i32):
6054 %5 = iconst.i32 2
6055 %6 = add.nsw %4, %5
6056 jump block4(%6)
6057
6058block4(%7: i32):
6059 return %7
6060";
6061 assert_eq!(text, expected);
6062 }
6063
6064 #[test]
6065 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6066 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6069 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6070 assert!(text.contains("icmp ule"), "{text}");
6071 assert!(!text.contains("switch"), "{text}");
6072 }
6073
6074 #[test]
6075 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6076 let text = body(
6077 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6078 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6079 );
6080 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6083 assert!(text.contains("block5:\n jump block7("), "{text}");
6084 assert!(text.contains("block6:\n jump block8("), "{text}");
6085 }
6086
6087 #[test]
6088 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6089 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6090 }
6091
6092 #[test]
6093 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6094 let text = body(
6099 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6100 return n; }\n",
6101 );
6102 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6105 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6106 assert!(text.contains("block4:\n jump block3("), "{text}");
6107 }
6108
6109 #[test]
6110 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6111 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6114 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6115 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6116 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6117 }
6118
6119 #[test]
6120 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6121 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6122 assert!(!text.contains("alloca"), "{text}");
6126 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6127 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6128 }
6129
6130 #[test]
6131 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6132 let text =
6133 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6134 assert!(!text.contains("alloca"), "{text}");
6135 assert!(text.contains("block1(%2: i32):"), "{text}");
6136 assert!(text.contains("jump block1(%5)"), "{text}");
6137 }
6138
6139 #[test]
6140 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6141 assert_eq!(
6144 body("int f(int x) { return x; spare: return 0; }\n"),
6145 "block0(%0: i32):\n return %0\n"
6146 );
6147 }
6148
6149 #[test]
6150 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6151 let text = body(
6152 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6153 );
6154 assert_eq!(
6157 text,
6158 "\
6159block0(%0: ptr):
6160 %1 = load.i8 %0, align 1
6161 %2 = iconst.i8 3
6162 %3 = ashr %1, %2
6163 %4 = sext.i32 %3
6164 return %4
6165"
6166 );
6167 }
6168
6169 #[test]
6170 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6171 let text =
6175 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6176 assert_eq!(
6177 text,
6178 "\
6179block0(%0: ptr, %1: i32):
6180 %2 = iconst.i32 16777215
6181 %3 = and %1, %2
6182 %4 = trunc.i16 %3
6183 store %4 -> %0, align 2
6184 %5 = iconst.i32 16
6185 %6 = lshr %3, %5
6186 %7 = trunc.i8 %6
6187 %8 = iconst.i64 2
6188 %9 = ptr_add %0, %8
6189 store %7 -> %9, align 1
6190 return
6191"
6192 );
6193 }
6194
6195 #[test]
6196 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6197 let text =
6198 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6199 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6202 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6203 }
6204
6205 #[test]
6206 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6207 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6210 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6211 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6212 }
6213
6214 #[test]
6215 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6216 let text = body(
6220 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6221 );
6222 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6223 }
6224
6225 #[test]
6226 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6227 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6230 assert!(
6231 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6232 "{text}"
6233 );
6234 }
6235
6236 #[test]
6237 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6238 let text = ir(concat!(
6243 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6244 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6245 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6246 "char s[2] = \"hi\";\n",
6247 ));
6248 assert!(
6249 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6250 "{text}"
6251 );
6252 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6253 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6254 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6257 }
6258
6259 #[test]
6260 fn a_definition_takes_a_parameter_it_left_unnamed() {
6261 let text = ir("int f(int a, int) { return a; }\n");
6265 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6266 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6267
6268 let text = ir("int g(int, int n) { return n; }\n");
6271 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6272 }
6273
6274 #[test]
6275 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6276 let text = body(concat!(
6281 "struct s { int f; int g; };\n",
6282 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6283 "{ *d = *e = a[0] = *c; }\n",
6284 ));
6285 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6286 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6287 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6288 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6289 }
6290
6291 #[test]
6292 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6293 let mut opts = options();
6298 opts.emit = EmitKind::Ir;
6299 let result = run(
6300 &opts,
6301 concat!(
6302 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6303 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6304 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6305 "const union u c = { { \"1234\", \"567\" } };\n",
6306 ),
6307 );
6308 let text = result.text();
6309 assert_eq!(
6310 result.messages,
6311 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6312 (5 chars into 3 available) [E0637]"]
6313 );
6314 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6315 assert!(
6316 text.contains(
6317 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6318 bytes \"9\\00\", zero 3 }"
6319 ),
6320 "{text}"
6321 );
6322 assert!(
6325 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6326 "{text}"
6327 );
6328 }
6329
6330 #[test]
6331 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6332 let text = body(concat!(
6336 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6337 "void g(struct v *);\n",
6338 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6339 ));
6340 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6341 }
6342
6343 #[test]
6344 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6345 let text = ir(concat!(
6350 "struct s { int x; };\n",
6351 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6352 "int n = (int){ 7 };\n",
6353 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6354 ));
6355 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6356 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6357 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6360 }
6361
6362 #[test]
6363 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6364 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6368 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6369 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6370 }
6371
6372 #[test]
6373 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6374 let text = ir("unsigned char foo[1][0];\n");
6378 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6379 }
6380
6381 #[test]
6382 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
6383 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
6386 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
6387 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
6388 }
6389
6390 #[test]
6391 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
6392 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
6396 assert!(
6397 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
6398 "{text}"
6399 );
6400 }
6401
6402 #[test]
6403 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
6404 let text = body(
6409 "\
6410struct s { int a, b; };
6411struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
6412",
6413 );
6414 assert!(text.contains("block3(%7: ptr)"), "{text}");
6416 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
6417 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
6418 }
6419
6420 #[test]
6428 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
6429 let text = body("int f(int i) { return ++i ?: 10; }\n");
6430 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
6431 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6432
6433 let text = body("long f(int i) { return ++i ?: 10L; }\n");
6436 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
6437 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6438
6439 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
6441 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
6442
6443 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
6446 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
6447 }
6448
6449 #[test]
6450 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
6451 let text = ir("\
6455struct pair { int a, b; };
6456struct pair make(int a, int b);
6457struct pair twice(struct pair p) { return make(p.a, p.b); }
6458");
6459 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
6460 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
6461 }
6462
6463 #[test]
6464 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
6465 let text = ir("\
6469struct big { double v[8]; };
6470struct big grow(struct big b);
6471struct big twice(struct big b) { return grow(grow(b)); }
6472");
6473 assert!(
6474 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
6475 "{text}"
6476 );
6477 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
6478 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
6481 }
6482
6483 #[test]
6484 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
6485 let text = ir("\
6490struct big { double v[8]; };
6491struct pair { int a, b; };
6492int p(const char *, ...);
6493int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
6494");
6495 assert!(
6496 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
6497 "{text}"
6498 );
6499 }
6500
6501 #[test]
6502 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
6503 let body = body(
6506 "\
6507struct pair { int a, b; };
6508struct pair make(int a, int b);
6509int second(void) { return make(1, 2).b; }
6510",
6511 );
6512 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
6513 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
6514 }
6515
6516 #[test]
6517 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
6518 let source = "\
6522struct hfa { float x, y, z; };
6523int take(struct hfa h);
6524int give(struct hfa h) { return take(h); }
6525";
6526 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
6527 let mut opts = options();
6528 opts.emit = EmitKind::Ir;
6529 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
6530 let result = run(&opts, source);
6531 assert_eq!(result.messages, Vec::<String>::new());
6532 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
6533 }
6534
6535 #[test]
6536 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
6537 let source = "\
6540int use(int *);
6541void f(int n) {
6542 {
6543 int a[n];
6544 use(a);
6545 }
6546 use(0);
6547}
6548";
6549 let body = body(source);
6550 assert!(body.contains("mul.nsw"), "{body}");
6551 assert!(body.contains("stacksave"), "{body}");
6552 assert!(body.contains("alloca %"), "{body}");
6553 assert!(body.contains("stackrestore"), "{body}");
6554 }
6555
6556 #[test]
6557 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
6558 let source = "\
6563int use(int *);
6564int f(int n) {
6565 {
6566 int a[n];
6567 if (use(a)) goto out;
6568 use(0);
6569 }
6570out:
6571 return 0;
6572}
6573";
6574 let body = body(source);
6575 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
6577 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6578 assert!(after.starts_with(" %4\n jump block"), "{body}");
6579 }
6580
6581 #[test]
6582 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
6583 let source = "\
6587int use(int *);
6588int f(int n) {
6589 int a[n];
6590again:
6591 if (use(a)) goto again;
6592 return 0;
6593}
6594";
6595 let body = body(source);
6596 assert!(body.contains("stacksave"), "{body}");
6597 assert!(!body.contains("stackrestore"), "{body}");
6598 }
6599
6600 #[test]
6601 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
6602 let source = "\
6607int use(int *);
6608int f(int n) {
6609again:
6610 {
6611 int a[n];
6612 if (use(a)) goto again;
6613 }
6614 return 0;
6615}
6616";
6617 let body = body(source);
6618 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6619 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6620 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
6621 }
6622
6623 #[test]
6624 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
6625 let source = "\
6631int f(void);
6632void t(void) {
6633 int count = 10;
6634 for (; count--;) {
6635 int b[f()];
6636 int i;
6637 for (i = 0; i < f(); i++) {
6638 b[i] = count;
6639 }
6640 }
6641}
6642";
6643 let body = body(source);
6644 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6648 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6649 let next = after.split("\n\n").next().expect("the block the restore is in");
6652 assert!(next.contains("jump block1("), "{body}");
6653 }
6654
6655 #[test]
6656 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
6657 let source = "\
6660unsigned long f(int n) {
6661 int a[n];
6662 n = 0;
6663 return sizeof a;
6664}
6665";
6666 let body = body(source);
6667 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
6669 }
6670
6671 #[test]
6672 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
6673 let source = "\
6676int use(int);
6677int f(int x) {
6678 return ({
6679 int t = use(x);
6680 t * t;
6681 });
6682}
6683";
6684 let expected = "\
6685block0(%0: i32):
6686 %1 = call @use(%0) : (i32) -> i32
6687 %2 = mul.nsw %1, %1
6688 return %2
6689";
6690 assert_eq!(body(source), expected);
6691 }
6692
6693 #[test]
6694 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
6695 let source = "int f(int x) { return ({ return x; 0; }); }\n";
6699 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
6700 }
6701
6702 #[test]
6703 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
6704 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
6708 let expected = "\
6709block0(%0: ptr):
6710 %1 = va_arg.f64 %0
6711 %2 = va_arg.f64 %0
6712 %3 = fadd %1, %2
6713 return %3
6714";
6715 assert_eq!(body(source), expected);
6716 }
6717
6718 #[test]
6719 fn one_that_reads_a_structure_answers_where_the_object_is() {
6720 let source = "\
6734struct s { int a; long b; };
6735long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
6736";
6737 let expected = "\
6738block0(%0: ptr):
6739 %1 = alloca, size 16, align 16
6740 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
6741 memcpy %1, %2, size 16, align 8
6742 %3 = iconst.i64 8
6743 %4 = ptr_add %1, %3
6744 %5 = load.i64 %4, align 8, tbaa !1
6745 return %5
6746";
6747 assert_eq!(body(source), expected);
6748 }
6749
6750 #[test]
6754 fn the_classification_says_which_registers_the_object_arrived_in() {
6755 let source = "\
6756struct s { double a; double b; };
6757double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
6758";
6759 assert!(
6760 body(source)
6761 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
6762 "{}",
6763 body(source)
6764 );
6765
6766 let big = "\
6767struct s { long a[4]; };
6768long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
6769";
6770 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
6771 }
6772
6773 #[test]
6774 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
6775 let source = "\
6779int f(int c) {
6780 void *p = c ? &&one : &&two;
6781 goto *p;
6782one:
6783 return 1;
6784two:
6785 return 2;
6786}
6787";
6788 let expected = "\
6789block0(%0: i32):
6790 %1 = iconst.i32 0
6791 %2 = icmp ne %0, %1
6792 br_if %2, block1, block2
6793
6794block1:
6795 %3 = block_addr block3
6796 jump block4(%3)
6797
6798block2:
6799 %4 = block_addr block5
6800 jump block4(%4)
6801
6802block3:
6803 %5 = iconst.i32 1
6804 return %5
6805
6806block4(%6: ptr):
6807 indirect_br %6, block3, block5
6808
6809block5:
6810 %7 = iconst.i32 2
6811 return %7
6812";
6813 assert_eq!(body(source), expected);
6814 }
6815
6816 #[test]
6817 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
6818 let source = "void **next(void);
6821void f(void) { goto *next(); }
6822";
6823 let expected = "\
6824block0:
6825 %0 = call @next() : () -> ptr
6826 unreachable
6827";
6828 assert_eq!(body(source), expected);
6829 }
6830
6831 #[test]
6832 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
6833 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
6836 let expected = "\
6837block0:
6838 inline_asm.volatile \"mfence\", \"\", \"memory\"()
6839 return
6840";
6841 assert_eq!(body(source), expected);
6842 }
6843
6844 #[test]
6845 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
6846 let source = "\
6849int f(int x, int y) {
6850 int r;
6851 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
6852 return r + y;
6853}
6854";
6855 let expected = "\
6856block0(%0: i32, %1: i32):
6857 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
6858 %4 = add.nsw %2, %3
6859 return %4
6860";
6861 assert_eq!(body(source), expected);
6862 }
6863
6864 #[test]
6865 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
6866 let source = "\
6871struct pair { int a, b; };
6872int f(int x) {
6873 int slot = x;
6874 struct pair p = { x, x };
6875 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
6876 return slot + p.a;
6877}
6878";
6879 let text = body(source);
6880 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
6881 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
6882 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
6883 }
6884
6885 #[test]
6886 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
6887 let source = "\
6892int f(int x) {
6893 int r = 7;
6894 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
6895 return r;
6896away:
6897 return r;
6898}
6899";
6900 let expected = "\
6901block0(%0: i32):
6902 %1 = iconst.i32 7
6903 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
6904
6905block1:
6906 return %2
6907
6908block2:
6909 return %1
6910";
6911 assert_eq!(body(source), expected);
6912 }
6913
6914 #[test]
6915 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
6916 let mut opts = options();
6920 opts.emit = EmitKind::Ir;
6921 for (source, expected) in [
6922 (
6923 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
6924 "output operand constraint lacks '='",
6925 ),
6926 (
6927 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
6928 "lvalue required in 'asm' statement",
6929 ),
6930 (
6931 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
6932 "read-only variable 'g' used as 'asm' output",
6933 ),
6934 (
6935 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
6936 "input operand constraint contains '='",
6937 ),
6938 (
6939 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
6940 "memory input 0 is not directly addressable",
6941 ),
6942 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
6943 (
6944 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
6945 "duplicate asm operand name 'a'",
6946 ),
6947 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
6948 ] {
6949 let result = run(&opts, source);
6950 assert!(result.failed(), "expected this to be reported:\n{source}");
6951 assert!(
6952 result.messages.iter().any(|m| m.contains(expected)),
6953 "{expected}\n{:?}",
6954 result.messages
6955 );
6956 }
6957 }
6958
6959 #[test]
6964 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
6965 let text = ir(concat!(
6966 "__asm__(\n",
6967 " \".section .rodata\\n\"\n",
6968 " \".globl first\\n\"\n",
6969 " \".balign 8\\n\"\n",
6970 " \"first:\\n\"\n",
6971 " \".long 1\\n\"\n",
6972 " \".long 2\\n\"\n",
6973 " \".globl last\\n\"\n",
6974 " \"last:\\n\"\n",
6975 " \".quad last - first\\n\");\n",
6976 "extern const int first[];\n",
6977 "extern const long last;\n",
6978 ));
6979 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
6980 assert!(text.contains("global @last : i64 = 8"), "{text}");
6981 }
6982
6983 #[test]
6987 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
6988 let text = ir(concat!(
6989 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
6990 "extern int counter;\n",
6991 "int read(void) { return counter; }\n",
6992 ));
6993 assert!(text.contains("global @counter : i32 = 7"), "{text}");
6994 }
6995
6996 #[test]
6999 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7000 let mut opts = options();
7001 opts.emit = EmitKind::Ir;
7002 let mut fs = MemoryFileSystem::new();
7003 fs.insert(
7004 "/main.c",
7005 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7006 );
7007 fs.insert("seed", b"hi".to_vec());
7008 let result = compile(&opts, "/main.c", &fs);
7009 assert_eq!(result.messages, Vec::<String>::new());
7010 let text = result.text();
7011 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7012 }
7013
7014 #[test]
7017 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7018 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7019 assert!(
7020 messages
7021 .iter()
7022 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7023 "{messages:?}"
7024 );
7025 }
7026
7027 #[test]
7030 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7031 for source in [
7032 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7033 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7034 ] {
7035 let messages = errors(source);
7036 assert!(
7037 messages
7038 .iter()
7039 .any(|m| m.contains("not supported yet")
7040 && m.contains("in an `asm` at file scope")),
7041 "{source}\n{messages:?}"
7042 );
7043 }
7044 }
7045
7046 #[test]
7047 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7048 let mut opts = options();
7049 opts.emit = EmitKind::Ir;
7050 for source in [
7051 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7052 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7053 ] {
7054 let result = run(&opts, source);
7055 assert!(result.failed(), "expected this to be reported:\n{source}");
7056 assert!(
7057 result.messages.iter().any(|m| m.contains("not supported yet")),
7058 "{:?}",
7059 result.messages
7060 );
7061 }
7062 }
7063
7064 fn round_trip(source: &str) -> (String, String) {
7066 let printed = ir(source);
7067 let mut opts = options();
7068 opts.emit = EmitKind::Ir;
7069 let mut fs = MemoryFileSystem::new();
7070 fs.insert("/main.ir", printed.clone().into_bytes());
7071 let result = compile_ir(&opts, "/main.ir", &fs);
7072 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7073 (printed, result.text().to_owned())
7074 }
7075
7076 #[test]
7077 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7078 let (printed, again) = round_trip(
7082 "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",
7083 );
7084 assert_eq!(printed, again);
7085 }
7086
7087 #[test]
7088 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7089 let mut opts = options();
7090 opts.emit = EmitKind::Ir;
7091 let mut fs = MemoryFileSystem::new();
7092 let text = "\
7093; ModuleID = 'a.c'
7094; format 0
7095target triple = \"x86_64-unknown-linux-gnu\"
7096target datalayout = \"e-p:64:64-i64:64-S128\"
7097
7098func @f(), linkage(external) {
7099block0:
7100 frobnicate
7101}
7102";
7103 fs.insert("/main.ir", text.as_bytes().to_vec());
7104 let result = compile_ir(&opts, "/main.ir", &fs);
7105 assert!(result.failed());
7106 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7107 }
7108
7109 #[test]
7110 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7111 let mut opts = options();
7114 opts.emit = EmitKind::Ir;
7115 let mut fs = MemoryFileSystem::new();
7116 let text = "\
7117; ModuleID = 'a.c'
7118; format 0
7119target triple = \"x86_64-unknown-linux-gnu\"
7120target datalayout = \"e-p:64:64-i64:64-S128\"
7121
7122func @f(), linkage(external) {
7123block0:
7124 %0 = iconst.i32 1
7125 return %0
7126}
7127";
7128 fs.insert("/main.ir", text.as_bytes().to_vec());
7129 let result = compile_ir(&opts, "/main.ir", &fs);
7130 assert!(result.failed());
7131 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7132 }
7133
7134 #[test]
7135 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7136 let mut fs = MemoryFileSystem::new();
7138 fs.insert("/main.ir", Vec::new());
7139 let result = compile_ir(&options(), "/main.ir", &fs);
7140 assert!(result.failed());
7141 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7142 }
7143
7144 #[test]
7145 fn the_printed_ir_reads_back_as_the_same_module() {
7146 let text = ir("\
7149struct point { int x, y; };
7150static const char greeting[] = \"hi\";
7151int table[4] = { 1, 2, 3 };
7152int puts(const char *);
7153double half(double x) { return x / 2.0; }
7154int f(int n) {
7155 int total = 0;
7156 for (int i = 0; i < n; i++) {
7157 if (i == 3) continue;
7158 total += table[i];
7159 }
7160 switch (n) {
7161 case 0: total = 1;
7162 case 1: total++; break;
7163 default: total = -total;
7164 }
7165 struct point p = { total, 1 };
7166 int *q = &p.y;
7167 puts(greeting);
7168 return p.x + *q;
7169}
7170int dispatch(int c) {
7171 void *p = c ? &&one : &&two;
7172 goto *p;
7173one:
7174 return 1;
7175two:
7176 return 2;
7177}
7178int assembly(int x, int *p) {
7179 int r;
7180 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7181 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7182 return r;
7183away:
7184 return 0;
7185}
7186");
7187 let mut names = Interner::new();
7188 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7189 assert_eq!(rucc_ir::print(&module, &names), text);
7190 }
7191
7192 #[test]
7193 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7194 let mut opts = options();
7198 opts.emit = EmitKind::Object;
7199 opts.save_temps = rucc_session::SaveTemps::Object;
7200 let result = run(&opts, "#define N 2\nint a[N];\n");
7201 assert_eq!(result.messages, Vec::<String>::new());
7202 let text = result.temps.preprocessed.expect("the preprocessed text");
7203 assert!(text.contains("int a[2];"), "{text}");
7204 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7205 let asm = result.temps.assembly.expect("the assembly");
7206 assert!(asm.contains("a:"), "{asm}");
7207 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7208 }
7209
7210 #[test]
7211 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7212 let mut opts = options();
7215 opts.emit = EmitKind::Object;
7216 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7217 }
7218
7219 #[test]
7220 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7221 let mut opts = options();
7224 opts.emit = EmitKind::Ir;
7225 opts.save_temps = rucc_session::SaveTemps::Cwd;
7226 let result = run(&opts, "int a;\n");
7227 assert!(result.temps.preprocessed.is_some());
7228 assert_eq!(result.temps.assembly, None);
7229 }
7230}