1use std::path::Path;
14
15use rucc_base::Interner;
16use rucc_codegen::coverage::Fired;
17use rucc_codegen::elsewhere::Elsewhere;
18use rucc_codegen::lowering::Lowerings;
19use rucc_codegen::pipeline::{self, Machine, Recording};
20use rucc_codegen::pressure::Pressure;
21use rucc_diag::{Diagnostic, Severity, Span};
22use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
23use rucc_lex::{Convert, Keywords, PpToken, convert};
24use rucc_lower::Protector as LowerProtector;
25use rucc_sema::{Checker, Context as CheckContext};
26use rucc_session::{
27 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
28};
29use rucc_target::TargetInfo;
30use rucc_tuple::{Arch, ObjectFormat};
31
32use crate::preprocess::render;
33
34#[derive(Debug, Clone, PartialEq, Eq, Default)]
41pub enum Artifact {
42 #[default]
45 Nothing,
46 Text(String),
48 Object {
55 bytes: Vec<u8>,
57 defines: Vec<String>,
61 },
62}
63
64impl Artifact {
65 #[must_use]
67 pub fn bytes(&self) -> &[u8] {
68 match self {
69 Artifact::Nothing => &[],
70 Artifact::Text(text) => text.as_bytes(),
71 Artifact::Object { bytes, .. } => bytes,
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct Compiled {
79 pub artifact: Artifact,
81 pub messages: Vec<String>,
83 pub errors: u32,
85 pub fired: Fired,
91 pub pressure: Pressure,
96 pub lowerings: Lowerings,
101 pub dumps: Vec<rucc_opt::Dump>,
107 pub remarks: String,
113 pub deps: Vec<rucc_pp::Dependency>,
118 pub temps: Temps,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Default)]
134pub struct Temps {
135 pub preprocessed: Option<String>,
137 pub assembly: Option<String>,
139}
140
141impl Compiled {
142 #[must_use]
144 pub fn failed(&self) -> bool {
145 self.errors > 0
146 }
147
148 #[must_use]
153 pub fn text(&self) -> &str {
154 match &self.artifact {
155 Artifact::Text(text) => text,
156 _ => "",
157 }
158 }
159}
160
161#[must_use]
174pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
175 let mut sess = Session::new(opts.clone());
176 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
180 let mut diagnostics: Vec<Diagnostic> = Vec::new();
181 let mut fired = Fired::new();
183 let mut pressure = Pressure::new();
185 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
186 let mut dumps = Vec::new();
188 let mut remarks = String::new();
189 let mut temps = Temps::default();
191
192 let bytes = match fs.read(Path::new(name)) {
193 Ok(bytes) => bytes,
194 Err(e) => return failure(format!("{name}: {e}")),
195 };
196 let Ok(file) = sess.sources.add_shared(name, bytes, None) else {
197 return failure(format!("{name}: the source map has no room left for this file"));
198 };
199
200 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
204 let predef = rucc_pp::Predef::for_options(opts);
205 let expanded: Vec<PpToken> = {
206 let mut tokens = Vec::new();
207 {
212 let mut cx =
213 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
214 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
215 cx.pedantic = opts.pedantic;
216 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
217 return failure(format!(
218 "{name}: the source map has no room for the built in macros"
219 ));
220 }
221 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
222 return failure(format!("{name}: the source map has no room for the command line"));
223 }
224 tokens.append(&mut pp.run(file, &mut cx));
225 }
226 if opts.save_temps.wanted() {
227 temps.preprocessed = Some(rucc_pp::print(
228 file,
229 &tokens,
230 pp.line_directives(),
231 &sess.sources,
232 &sess.interner,
233 rucc_pp::PrintOptions { line_markers: opts.line_markers },
234 ));
235 }
236 tokens.iter().map(|token| token.to_pp()).collect()
237 };
238 diagnostics.extend(pp.take_diagnostics());
239 let deps = pp.dependencies().to_vec();
242
243 let cx = Convert {
246 keywords: &keywords,
247 interner: &sess.interner,
248 target: &sess.target,
249 std: opts.std,
250 gnu: opts.gnu_extensions,
251 pedantic: opts.pedantic,
252 };
253 let (tokens, complaints) = convert(&expanded, &cx);
254 diagnostics.extend(complaints);
255
256 let parsed = rucc_parse::parse(
257 &tokens,
258 rucc_parse::Context {
259 interner: &sess.interner,
260 std: opts.std,
261 gnu: opts.gnu_extensions,
262 pedantic: opts.pedantic,
263 error_limit: opts.error_limit as usize,
264 },
265 );
266 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
267 diagnostics.extend(parsed.diagnostics);
268
269 let mut artifact = Artifact::Nothing;
270 let mut instrumented = Instrumented::default();
273 if !parse_failed {
274 let mut checker = Checker::new(
275 &parsed.ast,
276 CheckContext {
277 names: &sess.interner,
278 target: &sess.target,
279 std: opts.std,
280 gnu: opts.gnu_extensions,
281 pedantic: opts.pedantic,
282 permissive: opts.permissive,
283 gnu89_inline: opts.gnu89_inline,
284 error_limit: opts.error_limit as usize,
285 builtins: opts.builtins && opts.hosted,
288 no_builtin: &opts.no_builtin,
289 short_enums: opts.short_enums,
290 ms_extensions: sess.ms_extensions(),
291 trapping_math: opts.trapping_math,
292 },
293 );
294 checker.check_unit();
295 let checked = checker.finish();
296 if !checked.failed() {
297 match opts.emit {
298 EmitKind::Tast => {
299 artifact = Artifact::Text(rucc_sema::print(
300 &checked.tast,
301 &checked.types,
302 &sess.interner,
303 ));
304 }
305 EmitKind::TypeGranules => {
309 artifact = Artifact::Text(rucc_types::granule_report(
310 &checked.types,
311 &sess.interner,
312 &sess.target,
313 ));
314 }
315 EmitKind::Ir
316 | EmitKind::MirFinal
317 | EmitKind::Asm
318 | EmitKind::Object
319 | EmitKind::Archive
320 | EmitKind::Executable
321 | EmitKind::SafetySummary => {
322 let mut read = |named: &str| {
327 fs.read(Path::new(named))
328 .map(|bytes| bytes.as_slice().to_vec())
329 .map_err(|why| why.to_string())
330 };
331 let mut lowered = rucc_lower::lower(
332 name,
333 rucc_lower::Context {
334 tast: &checked.tast,
335 types: &checked.types,
336 target: &sess.target,
337 names: &mut sess.interner,
338 visibility: match opts.visibility {
339 Visibility::Default => IrVisibility::Default,
340 Visibility::Hidden => IrVisibility::Hidden,
341 Visibility::Protected => IrVisibility::Protected,
342 },
343 protector: match opts.protector {
344 Protector::None => LowerProtector::None,
345 Protector::Buffers => LowerProtector::Buffers,
346 Protector::Strong => LowerProtector::Strong,
347 Protector::All => LowerProtector::All,
348 },
349 wrapping: rucc_lower::Wrapping {
350 signed: opts.wrapping.signed,
351 pointer: opts.wrapping.pointer,
352 trap: opts.wrapping.trap,
353 },
354 aliasing: opts.strict_aliasing,
355 padding: opts.padding == Padding::Ignored,
356 contract: match opts.fp_contract {
357 Contract::Off => FpContract::Off,
358 Contract::On => FpContract::On,
359 Contract::Fast => FpContract::Fast,
360 },
361 read: &mut read,
362 },
363 );
364 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
368 if !failed {
369 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
374 for error in errors {
375 diagnostics.push(internal(&format!("invalid IR, {error}")));
376 }
377 } else if let Err(complaints) =
378 instrument(&mut lowered.module, &mut sess.interner, opts)
379 .map(|done| instrumented = done)
380 {
381 diagnostics.extend(complaints);
382 } else if let Err(complaints) = optimize(
383 &mut lowered.module,
384 &sess.interner,
385 &sess.target,
386 opts,
387 name,
388 &mut dumps,
389 &mut remarks,
390 ) {
391 diagnostics.extend(complaints);
392 } else if opts.emit == EmitKind::SafetySummary {
393 artifact = Artifact::Text(
398 rucc_safety::summarize(
399 &lowered.module,
400 &sess.interner,
401 name,
402 opts.safety.as_str(),
403 instrumented.checks,
404 instrumented.interposed,
405 instrumented.crossings,
406 )
407 .render(),
408 );
409 } else if opts.emit == EmitKind::Ir {
410 artifact =
415 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
416 } else {
417 match generate(
420 &mut lowered.module,
421 &mut sess.interner,
422 &sess.target,
423 opts,
424 &mut Recording {
425 fired: &mut fired,
426 pressure: &mut pressure,
427 lowerings: &mut lowerings,
428 },
429 &mut temps.assembly,
430 ) {
431 Ok(made) => artifact = made,
432 Err(complaints) => diagnostics.extend(complaints),
433 }
434 }
435 }
436 diagnostics.extend(lowered.diagnostics);
437 }
438 _ => {}
439 }
440 }
441 diagnostics.extend(checked.diagnostics);
442 }
443
444 let mut messages = Vec::with_capacity(diagnostics.len());
445 let mut errors = 0;
446 for diag in &diagnostics {
447 if !opts.warnings && diag.severity == Severity::Warning {
451 continue;
452 }
453 if diag.severity.is_fatal()
454 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
455 {
456 errors += 1;
457 }
458 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
459 }
460 if errors > 0 {
461 artifact = Artifact::Nothing;
463 }
464 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
467}
468
469#[must_use]
479pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
480 let mut sess = Session::new(opts.clone());
481 if opts.emit != EmitKind::Ir {
482 return failure(format!(
483 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
484 the C in front of it became",
485 opts.emit.as_str()
486 ));
487 }
488 let bytes = match fs.read(Path::new(name)) {
489 Ok(bytes) => bytes,
490 Err(e) => return failure(format!("{name}: {e}")),
491 };
492 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
493 return failure(format!("{name}: this is not text, so it is not IR"));
494 };
495
496 let module = match rucc_ir::parse(text, &mut sess.interner) {
497 Ok(module) => module,
498 Err(error) => {
499 return failure(format!("{name}:{}: {}", error.line, error.message));
500 }
501 };
502 let mut diagnostics: Vec<Diagnostic> = Vec::new();
503 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
504 for error in errors {
505 diagnostics.push(invalid(&format!("invalid IR, {error}")));
506 }
507 }
508 let mut messages = Vec::with_capacity(diagnostics.len());
509 for diag in &diagnostics {
510 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
511 }
512 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
513 let artifact = if errors > 0 {
514 Artifact::Nothing
515 } else {
516 Artifact::Text(rucc_ir::print(&module, &sess.interner))
517 };
518 Compiled {
520 artifact,
521 messages,
522 errors,
523 fired: Fired::new(),
524 pressure: Pressure::new(),
525 lowerings: Lowerings::new(),
526 dumps: Vec::new(),
527 remarks: String::new(),
528 deps: Vec::new(),
529 temps: Temps::default(),
530 }
531}
532
533fn instrument(
556 module: &mut rucc_ir::Module,
557 names: &mut Interner,
558 opts: &Options,
559) -> Result<Instrumented, Vec<Diagnostic>> {
560 if !opts.safety.instruments() {
561 return Ok(Instrumented::default());
562 }
563 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
564 checks.freed = rucc_safety::ending::checks(module, names);
572 let interposed = rucc_safety::redirect(module, names);
577 let crossings = rucc_safety::witness(module, names);
580 match rucc_ir::verify(module, names) {
581 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
582 Err(errors) => Err(errors
583 .iter()
584 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
585 .collect()),
586 }
587}
588
589#[derive(Clone, Copy, Debug, Default)]
595struct Instrumented {
596 checks: rucc_safety::Counts,
598 interposed: usize,
600 crossings: rucc_safety::Sites,
602}
603
604fn optimize(
616 module: &mut rucc_ir::Module,
617 names: &Interner,
618 target: &TargetInfo,
619 opts: &Options,
620 file: &str,
621 dumps: &mut Vec<rucc_opt::Dump>,
622 remarks: &mut String,
623) -> Result<(), Vec<Diagnostic>> {
624 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
625 settings.interposition = match opts.interposition {
631 true => replaceable(target, opts),
632 false => IrPic::Executable,
633 };
634 settings.toggles.clone_from(&opts.passes);
635 settings.fuel = opts.pass_fuel.iter().cloned().collect();
636 settings.global_fuel = opts.pass_fuel_global;
637 settings.verify |= opts.verify_each;
638 for (on, spec) in &opts.pass_gates {
639 if let Err(why) = settings.gates.add(*on, spec) {
642 return Err(vec![internal(&why)]);
643 }
644 }
645 for spec in &opts.dump_ir {
646 if let Err(why) = settings.dumps.add(spec) {
649 return Err(vec![internal(&why)]);
650 }
651 }
652 let mut wants = rucc_opt::Wants::none();
653 for spec in &opts.opt_info {
654 if let Err(why) = wants.add(spec) {
657 return Err(vec![internal(&why)]);
658 }
659 }
660 let report = rucc_opt::run(module, names, &settings);
661 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
662 dumps.extend(report.dumps);
663 match report.broke.is_empty() {
664 true => Ok(()),
665 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
666 }
667}
668
669fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
703 match (target.tuple.os().object_format(), opts.pic) {
704 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
705 _ => IrPic::Executable,
706 }
707}
708
709fn generate(
710 module: &mut rucc_ir::Module,
711 names: &mut Interner,
712 target: &TargetInfo,
713 opts: &Options,
714 recording: &mut Recording<'_>,
715 assembly: &mut Option<String>,
716) -> Result<Artifact, Vec<Diagnostic>> {
717 let Some(machine) = Machine::for_target(target) else {
718 return Err(vec![unsupported(&format!(
719 "there is no back end for {} in this compiler yet, so there is nothing to generate",
720 target.tuple
721 ))]);
722 };
723 if opts.protector != Protector::None && machine.conv.guard.is_none() {
728 return Err(vec![unsupported(&format!(
729 "{} is not supported for {} yet, because the stack protector on that target is not \
730 the one this compiler writes",
731 opts.protector, target.tuple
732 ))]);
733 }
734 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
740 return Err(vec![unsupported(&format!(
741 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
742 for it there is not the note this compiler writes",
743 opts.control, target.tuple
744 ))]);
745 }
746 let profile = match machine.conv.trace {
752 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
753 None if opts.profile => {
754 return Err(vec![unsupported(&format!(
755 "-pg is not supported for {} yet, because the profiler's hook on that target is \
756 not the one this compiler calls",
757 target.tuple
758 ))]);
759 }
760 None => None,
761 };
762 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
767 return Err(vec![unsupported(&format!(
768 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
769 the room is there is not the section this compiler writes",
770 target.tuple
771 ))]);
772 }
773 let flags = pipeline::Flags {
774 frame_pointer: opts.frame_pointer,
775 red_zone: opts.red_zone,
776 stack_clash: opts.stack_clash,
777 landing: opts.control.branch(),
778 profile: match profile {
779 None => pipeline::Profile::No,
780 Some(true) => pipeline::Profile::Early,
781 Some(false) => pipeline::Profile::Late,
782 },
783 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
784 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
791 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
796 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
801 accurate: opts.cycle_accurate_model,
803 };
804
805 if opts.safety.instruments() {
814 rucc_opt::heap::annotate(module, names);
824 rucc_safety::handover::arrange(module);
831 rucc_safety::lower(module, names);
832 if let Err(errors) = rucc_ir::verify(module, names) {
833 return Err(errors
834 .iter()
835 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
836 .collect());
837 }
838 }
839
840 let elsewhere = Elsewhere::of(module, replaceable(target, opts));
848
849 let mut funcs = Vec::new();
850 let mut complaints = Vec::new();
851 for id in module.funcs() {
852 if module[id].is_declaration() {
853 continue;
854 }
855 match pipeline::compile_recording(
856 &mut module[id],
857 names,
858 &machine,
859 &elsewhere,
860 flags,
861 recording,
862 ) {
863 Ok(func) => funcs.push(func),
864 Err(why) => {
865 let name = names.resolve(module[id].name).to_owned();
866 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
869 let said = format!("cannot generate code for '{name}': {why}");
870 complaints.push(unsupported_at(&said, span));
871 }
872 }
873 }
874 if !complaints.is_empty() {
875 return Err(complaints);
876 }
877 let (globals, aliases) = match opts.emit {
883 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
884 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
885 rucc_asm::aliases(module, names).map_err(refused)?,
886 ),
887 _ => (rucc_asm::Globals::default(), Vec::new()),
888 };
889 let unwind = opts.unwinds();
893 match opts.emit {
894 EmitKind::Asm => {
895 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
896 .map(Artifact::Text)
897 .map_err(refused)
898 }
899 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
903 if opts.save_temps.wanted() {
904 let listing = rucc_asm::print(
905 &funcs,
906 &globals,
907 &aliases,
908 names,
909 target,
910 unwind,
911 output(opts, target),
912 );
913 *assembly = Some(listing.map_err(refused)?);
914 }
915 let text = rucc_asm::assemble(&funcs, names, target, unwind).map_err(refused)?;
916 let data = globals.image();
917 let bytes = rucc_object::write(&text, &data, &aliases, target, output(opts, target))
920 .map_err(wrote)?;
921 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
926 Ok(Artifact::Object { bytes, defines })
927 }
928 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
929 }
930}
931
932fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
944 let mut features = 0;
945 if target.tuple.arch() == Arch::X86_64 {
946 if opts.control.branch() {
947 features |= rucc_object::Property::IBT;
948 }
949 if opts.control.ret() {
950 features |= rucc_object::Property::SHSTK;
951 }
952 }
953 rucc_object::Output {
954 sections: rucc_object::Sections {
955 functions: opts.function_sections,
956 data: opts.data_sections,
957 },
958 property: rucc_object::Property { features },
959 }
960}
961
962fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
968 match why {
969 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
970 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
971 }
972}
973
974fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
981 match why {
982 rucc_asm::Error::Thread { .. }
983 | rucc_asm::Error::IFunc { .. }
984 | rucc_asm::Error::Frame { .. } => {
985 vec![unsupported(&why.to_string())]
986 }
987 _ => vec![internal(&why.to_string())],
988 }
989}
990
991fn unsupported(message: &str) -> Diagnostic {
997 unsupported_at(message, Span::DUMMY)
998}
999
1000fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1006 Diagnostic::error(message.to_owned(), span)
1007 .with_code("E0653")
1008 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1009}
1010
1011fn invalid(message: &str) -> Diagnostic {
1013 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1014}
1015
1016fn internal(message: &str) -> Diagnostic {
1018 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1019 .with_code("E0652")
1020 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1021}
1022
1023fn failure(message: String) -> Compiled {
1026 Compiled {
1027 artifact: Artifact::Nothing,
1028 messages: vec![format!("rucc: error: {message}")],
1029 errors: 1,
1030 fired: Fired::new(),
1031 pressure: Pressure::new(),
1032 lowerings: Lowerings::new(),
1033 dumps: Vec::new(),
1034 remarks: String::new(),
1035 deps: Vec::new(),
1036 temps: Temps::default(),
1037 }
1038}
1039
1040#[cfg(test)]
1041mod tests {
1042 use rucc_session::{MemoryFileSystem, Std};
1043 use rucc_target::Triple;
1044
1045 use super::*;
1046
1047 fn options() -> Options {
1048 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1049 opts.emit = EmitKind::Tast;
1050 opts
1051 }
1052
1053 fn run(opts: &Options, source: &str) -> Compiled {
1054 let mut fs = MemoryFileSystem::new();
1055 fs.insert("/main.c", source.to_owned().into_bytes());
1056 compile(opts, "/main.c", &fs)
1057 }
1058
1059 fn freestanding() -> Options {
1063 let mut opts = options();
1064 opts.hosted = false;
1065 opts.search.push_system(rucc_session::runtime::DIR);
1066 opts
1067 }
1068
1069 fn shipped(source: &str) -> String {
1071 let result = run(&freestanding(), source);
1072 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1073 result.text().to_owned()
1074 }
1075
1076 fn tast(source: &str) -> String {
1078 let result = run(&options(), source);
1079 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1080 result.text().to_owned()
1081 }
1082
1083 #[test]
1084 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1085 let text = shipped(concat!(
1086 "#include <stdarg.h>\n",
1087 "int sum(int n, ...) {\n",
1088 " va_list ap, copy;\n",
1089 " va_start(ap, n);\n",
1090 " va_copy(copy, ap);\n",
1091 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1092 " va_end(ap);\n",
1093 " va_end(copy);\n",
1094 " return total;\n",
1095 "}\n",
1096 ));
1097 assert!(text.contains("va-start"), "{text}");
1098 assert!(text.contains("va-copy"), "{text}");
1099 assert!(text.contains("va-arg"), "{text}");
1100 assert!(text.contains("va-end"), "{text}");
1101 }
1102
1103 #[test]
1107 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1108 let text = shipped(concat!(
1109 "#define __need___va_list\n",
1110 "#include <stdarg.h>\n",
1111 "int vprint(const char *f, __gnuc_va_list ap);\n",
1112 "#ifdef va_start\n",
1113 "#error va_start should not be defined\n",
1114 "#endif\n",
1115 "#ifdef _VA_LIST_DEFINED\n",
1116 "#error va_list should not have been made\n",
1117 "#endif\n",
1118 ));
1119 assert!(text.contains("vprint"), "{text}");
1120 }
1121
1122 #[test]
1125 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1126 let text = shipped(concat!(
1127 "#define __need_size_t\n",
1128 "#include <stddef.h>\n",
1129 "#ifdef offsetof\n",
1130 "#error offsetof should not be defined yet\n",
1131 "#endif\n",
1132 "#define __need_ptrdiff_t\n",
1133 "#include <stddef.h>\n",
1134 "#include <stddef.h>\n",
1135 "size_t a;\n",
1136 "ptrdiff_t b;\n",
1137 "wchar_t c;\n",
1138 "max_align_t d;\n",
1139 "void *e = NULL;\n",
1140 "struct P { int x; long y; };\n",
1141 "size_t f = offsetof(struct P, y);\n",
1142 ));
1143 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1144 assert!(text.contains("decl #1 b : long"), "{text}");
1145 }
1146
1147 #[test]
1148 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1149 let text = shipped(concat!(
1150 "#include <limits.h>\n",
1151 "#include <float.h>\n",
1152 "int bits = CHAR_BIT;\n",
1153 "long big = LONG_MAX;\n",
1154 "int low = INT_MIN;\n",
1155 "int radix = FLT_RADIX;\n",
1156 "int digits = DBL_MANT_DIG;\n",
1157 ));
1158 assert!(text.contains("const 8 : int"), "{text}");
1159 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1160 assert!(text.contains("const 2 : int"), "{text}");
1161 assert!(text.contains("const 53 : int"), "{text}");
1162 }
1163
1164 #[test]
1168 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1169 let text = shipped(concat!(
1170 "#include <stdint.h>\n",
1171 "int64_t a = INT64_C(1);\n",
1172 "uint_least16_t b;\n",
1173 "intptr_t c;\n",
1174 "uintmax_t d = UINTMAX_MAX;\n",
1175 "int wide = sizeof(int_fast64_t);\n",
1176 ));
1177 assert!(text.contains("decl #0 a : long"), "{text}");
1178 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1179 assert!(text.contains("decl #2 c : long"), "{text}");
1180 }
1181
1182 #[test]
1193 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1194 let text = shipped(concat!(
1195 "#include <mmintrin.h>\n",
1196 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1197 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1198 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1199 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1200 "void done(void) { _mm_empty(); }\n",
1201 ));
1202 assert!(text.contains("add"), "{text}");
1203 assert!(text.contains("pack"), "{text}");
1204 assert!(text.contains("shift"), "{text}");
1205 }
1206
1207 #[test]
1212 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1213 let text = shipped(concat!(
1214 "#include <mm_malloc.h>\n",
1215 "void *get(void) { return _mm_malloc(64, 16); }\n",
1216 "void put(void *p) { _mm_free(p); }\n",
1217 ));
1218 assert!(text.contains("get"), "{text}");
1219 assert!(text.contains("put"), "{text}");
1220 }
1221
1222 #[test]
1234 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1235 let text = shipped(concat!(
1236 "#include <xmmintrin.h>\n",
1237 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1238 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1239 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1240 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1241 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1242 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1243 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1244 "void *room(void) { return _mm_malloc(64, 16); }\n",
1245 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1246 ));
1247 assert!(text.contains("add"), "{text}");
1248 assert!(text.contains("mask"), "{text}");
1249 assert!(text.contains("pick"), "{text}");
1250 assert!(text.contains("wide"), "{text}");
1251 }
1252
1253 #[test]
1260 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1261 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1262 for absent in [
1263 "_mm_sqrt_ps",
1264 "_mm_sqrt_ss",
1265 "_mm_rsqrt_ps",
1266 "_mm_rsqrt_ss",
1267 "_mm_getcsr",
1268 "_mm_setcsr",
1269 ] {
1270 let defined = text.contains(&format!("{absent}("));
1271 assert!(!defined, "{absent} is defined and the header says it is not");
1272 assert!(text.contains(absent), "{absent} is absent and unexplained");
1273 }
1274 }
1275
1276 #[test]
1277 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1278 let text = shipped(concat!(
1279 "#include <emmintrin.h>\n",
1280 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1281 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1282 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1283 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1284 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1285 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1286 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1287 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1288 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1289 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1290 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1291 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1292 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1293 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1294 ));
1295 assert!(text.contains("wide"), "{text}");
1296 assert!(text.contains("pack"), "{text}");
1297 assert!(text.contains("near"), "{text}");
1298 assert!(text.contains("half"), "{text}");
1299 }
1300
1301 #[test]
1305 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1306 let text = shipped(concat!(
1307 "#include <immintrin.h>\n",
1308 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1309 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1310 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1311 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1312 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1313 "}\n",
1314 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1315 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1316 ));
1317 assert!(text.contains("matching"), "{text}");
1318 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1319 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1320 }
1321
1322 #[test]
1326 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1327 let text = shipped(concat!(
1328 "#include <x86intrin.h>\n",
1329 "void barriers(void *p) {\n",
1330 " _mm_lfence();\n",
1331 " _mm_sfence();\n",
1332 " _mm_mfence();\n",
1333 " _mm_pause();\n",
1334 " _mm_clflush(p);\n",
1335 "}\n",
1336 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1337 ));
1338 assert!(text.contains("barriers"), "{text}");
1339 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1340 }
1341
1342 #[test]
1346 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1347 let text = shipped(concat!(
1348 "#include <immintrin.h>\n",
1349 "#include <emmintrin.h>\n",
1350 "#include <immintrin.h>\n",
1351 "#include <x86intrin.h>\n",
1352 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1353 ));
1354 assert!(text.contains("twice"), "{text}");
1355 }
1356
1357 #[test]
1361 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1362 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1363 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1364 let defined = text.contains(&format!("{absent}("));
1365 assert!(!defined, "{absent} is defined and the header says it is not");
1366 assert!(text.contains(absent), "{absent} is absent and unexplained");
1367 }
1368 }
1369
1370 #[test]
1371 fn the_three_formality_headers_still_have_to_work() {
1372 let text = shipped(concat!(
1373 "#include <stdbool.h>\n",
1374 "#include <stdalign.h>\n",
1375 "#include <iso646.h>\n",
1376 "#include <stdnoreturn.h>\n",
1377 "int t = true and not false;\n",
1378 "_Alignas(16) char buf[16];\n",
1379 "int a = alignof(long);\n",
1380 ));
1381 assert!(text.contains("decl #0 t : int"), "{text}");
1382 assert!(text.contains("const 8 : unsigned long"), "{text}");
1383 }
1384
1385 #[test]
1393 fn every_shipped_header_can_be_included_twice() {
1394 let once: String = rucc_session::runtime::names()
1395 .iter()
1396 .map(|name| format!("#include <{name}>\n"))
1397 .collect();
1398 let twice = once.repeat(2);
1399 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1400 }
1401
1402 #[test]
1403 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1404 let fs = MemoryFileSystem::new();
1405 let result = compile(&options(), "/nope.c", &fs);
1406 assert!(result.failed());
1407 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1408 assert!(result.text().is_empty());
1409 }
1410
1411 #[test]
1412 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1413 let text = tast("int x = 1;\n");
1414 let expected = "\
1415decl #0 x : int object external static defined
1416 init
1417 +0
1418 const 1 : int
1419";
1420 assert_eq!(text, expected);
1421 }
1422
1423 #[test]
1424 fn the_macros_are_expanded_before_anything_is_parsed() {
1425 let text = tast("#define N 2\nint a[N];\n");
1429 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1430 }
1431
1432 #[test]
1438 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1439 let text = tast(concat!(
1440 "#pragma pack(4)\n",
1441 "struct s { int a; };\n",
1442 "#pragma pack()\n",
1443 "int b;\n",
1444 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1445 ));
1446 assert!(text.contains("decl #0 b : int"), "{text}");
1447 assert!(text.contains("decl #1 c : int"), "{text}");
1448 }
1449
1450 #[test]
1458 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1459 tast(concat!(
1460 "struct A { char c; int i; } __attribute__((packed));\n",
1461 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1462 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1463 "struct B { char c; int i; } __attribute__((aligned));\n",
1466 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1467 "struct C { char c; int i __attribute__((packed)); };\n",
1468 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1469 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1470 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1471 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1472 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1473 "struct E { char c; _Alignas(8) int i; };\n",
1474 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1475 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1476 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1477 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1478 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1481 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1482 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1483 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1484 "struct I { [[gnu::packed]] char c; int i; };\n",
1487 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1488 "struct J { char c; [[gnu::packed]] int i; };\n",
1489 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1490 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1491 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1492 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1493 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1494 "union L { char c; int i; } __attribute__((packed));\n",
1495 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1496 "struct O { char c; int i; } __attribute__((__packed__));\n",
1500 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
1501 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
1502 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
1503 ));
1504 }
1505
1506 #[test]
1519 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
1520 let text = tast(concat!(
1521 "struct one { int x; };\n",
1522 "struct two { long y; };\n",
1523 "typedef union { struct one *a; struct two *b; void *any; }\n",
1524 " __attribute__((__transparent_union__)) arg;\n",
1525 "int takes(arg v);\n",
1526 "int f(struct one *p, struct two *q, char *c) {\n",
1527 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
1528 "}\n",
1529 "int takes(struct one *p);\n",
1531 "int (*as_a_member)(struct one *) = takes;\n",
1532 "int (*as_the_union)(arg) = takes;\n",
1533 ));
1534 assert!(text.contains("compound-literal"), "{text}");
1535 }
1536
1537 #[test]
1543 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
1544 let text = tast(concat!(
1545 "struct sockaddr { int family; };\n",
1546 "struct sockaddr_in { int family; int addr; };\n",
1547 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
1548 " addr_arg __attribute__((__transparent_union__));\n",
1549 "int bind_to(int fd, addr_arg where);\n",
1550 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
1551 ));
1552 assert!(text.contains("compound-literal"), "{text}");
1553 }
1554
1555 #[test]
1563 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
1564 let result = run(
1565 &options(),
1566 concat!(
1567 "union wider { int small; double large; } __attribute__((transparent_union));\n",
1568 "struct plain { int x; } __attribute__((transparent_union));\n",
1569 ),
1570 );
1571 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
1572 assert!(!result.failed(), "{:?}", result.messages);
1573 for message in &result.messages {
1574 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
1575 }
1576 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
1577 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
1578 }
1579
1580 #[test]
1589 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
1590 let packed = body(concat!(
1591 "struct P { char c; int v; } __attribute__((packed));\n",
1592 "int f(struct P *p) { return p->v; }\n",
1593 ));
1594 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
1595 let plain = body(concat!(
1597 "struct P { char c; int v; };\n",
1598 "int f(struct P *p) { return p->v; }\n",
1599 ));
1600 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
1601 }
1602
1603 #[test]
1610 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
1611 let stepped = body(concat!(
1612 "struct P { char c; int v[4]; } __attribute__((packed));\n",
1613 "int f(struct P *p, int i) { return p->v[i]; }\n",
1614 ));
1615 assert!(stepped.contains(", align 1,"), "{stepped}");
1616 assert!(!stepped.contains(", align 4,"), "{stepped}");
1617 let nested = body(concat!(
1618 "struct Inner { int v; };\n",
1619 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
1620 "int f(struct P *p) { return p->in.v; }\n",
1621 ));
1622 assert!(nested.contains(", align 1,"), "{nested}");
1623 assert!(!nested.contains(", align 4,"), "{nested}");
1624 }
1625
1626 #[test]
1635 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
1636 tast(concat!(
1637 "int v __attribute__((aligned(64)));\n",
1638 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
1639 "__attribute__((aligned(32))) int w;\n",
1642 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
1643 "[[gnu::aligned(16)]] int x;\n",
1644 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
1645 "int y __attribute__((aligned(2)));\n",
1648 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
1649 "void f(void) { int a __attribute__((aligned(128)));\n",
1651 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
1652 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1655 "void g(void) __attribute__((aligned(256)));\n",
1658 "void g(void) {}\n",
1659 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
1660 ));
1661 }
1662
1663 #[test]
1667 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
1668 let text = asm(concat!(
1669 "int v __attribute__((aligned(64)));\n",
1670 "void g(void) __attribute__((aligned(256)));\n",
1671 "void g(void) {}\n",
1672 "void plain(void) {}\n",
1673 ));
1674 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
1675 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1676 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
1677 }
1678
1679 #[test]
1688 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
1689 tast(concat!(
1690 "typedef int L __attribute__((aligned(2)));\n",
1691 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
1692 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
1693 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
1695 "struct T { char c; L x; };\n",
1696 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
1697 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
1698 "typedef int H __attribute__((aligned(16)));\n",
1700 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
1701 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
1702 "struct U { char c; H x; };\n",
1703 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
1704 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
1705 "typedef L M __attribute__((aligned(8)));\n",
1708 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
1709 "typedef L N;\n",
1712 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
1713 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1715 ));
1716 let text = asm(concat!(
1717 "typedef int L __attribute__((aligned(2)));\n",
1718 "typedef int H __attribute__((aligned(16)));\n",
1719 "L low;\n",
1720 "H high;\n",
1721 ));
1722 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
1723 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
1724 }
1725
1726 #[test]
1734 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
1735 tast(concat!(
1736 "typedef int __attribute__((vector_size(16))) v4si;\n",
1737 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
1738 "typedef char __attribute__((vector_size(16))) v16qi;\n",
1739 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
1740 "typedef int __attribute__((vector_size(4))) v1si;\n",
1743 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
1744 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
1746 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
1747 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
1748 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
1749 "v4si g;\n",
1752 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
1753 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
1754 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
1757 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
1759 ));
1760 }
1761
1762 #[test]
1772 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
1773 tast(concat!(
1774 "typedef int __attribute__((vector_size(8))) v2si;\n",
1775 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
1776 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
1777 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
1779 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
1780 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
1783 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
1784 ));
1785 }
1786
1787 #[test]
1795 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
1796 let result = run(
1797 &options(),
1798 concat!(
1799 "typedef int __attribute__((vector_size(16))) v4si;\n",
1800 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
1801 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
1802 " v4si v = { 1, 2, 3, 4 };\n",
1803 " v[0] = n;\n",
1804 " v[1] += n;\n",
1805 " v[2]++;\n",
1806 " *&v[3] = n;\n",
1807 " v4ui shifted = a >> b;\n",
1809 " shifted <<= b;\n",
1810 " *out = v + (v4si)shifted + (1 << b);\n",
1813 "}\n",
1814 "void refused(const v4si c) {\n",
1817 " c[0] = 1;\n",
1818 "}\n",
1819 ),
1820 );
1821 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
1822 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
1823 }
1824
1825 #[test]
1832 fn a_record_that_asks_for_the_other_byte_order_is_refused_rather_than_laid_out_in_this_one() {
1833 let opts = options();
1834 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
1835 assert_eq!(
1836 run(&opts, big).messages,
1837 ["/main.c:1:36: error: 'scalar_storage_order' is not implemented yet [E0688]\n\
1838 /main.c:1:36: note: every scalar in this record would be read in the wrong byte \
1839 order"]
1840 );
1841
1842 let armoured =
1843 "struct s { int i; } __attribute__((__scalar_storage_order__(\"little-endian\")));\n";
1844 let messages = run(&opts, armoured).messages;
1845 assert!(messages[0].contains("[E0688]"), "{messages:?}");
1846
1847 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
1850 assert!(run(&opts, front).messages[0].contains("[E0688]"), "{front}");
1851 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
1852 assert!(run(&opts, standard).messages[0].contains("[E0688]"), "{standard}");
1853 }
1854
1855 #[test]
1865 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
1866 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
1868 assert_eq!(
1869 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
1870 1
1871 );
1872 assert_eq!(
1873 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
1874 1
1875 );
1876 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
1877 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
1879 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
1880 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
1882 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
1883 }
1884
1885 fn bit_field_byte(record: &str) -> u64 {
1887 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
1888 let body = body(&source);
1889 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
1890 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
1891 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
1892 }
1893
1894 #[test]
1900 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
1901 tast(concat!(
1902 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
1903 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
1904 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
1905 "struct b { char c; __attribute__((packed)) int i; };\n",
1906 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
1907 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
1908 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
1909 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
1910 ));
1911 }
1912
1913 #[test]
1919 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
1920 tast(concat!(
1921 "#pragma pack(1)\n",
1922 "struct A { char c; int i; };\n",
1923 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1924 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1925 "#pragma pack()\n",
1926 "struct B { char c; int i; };\n",
1927 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
1928 "#pragma pack(2)\n",
1929 "struct C { char c; int i; double d; };\n",
1930 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
1931 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
1932 "struct K { char c; int i __attribute__((aligned(8))); };\n",
1934 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
1935 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
1936 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
1938 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
1939 "#pragma pack()\n",
1940 "#pragma pack(push, 1)\n",
1941 "struct D { char c; short s; };\n",
1942 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
1943 "#pragma pack(pop)\n",
1944 "struct E { char c; short s; };\n",
1945 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
1946 "struct H { char c;\n",
1948 "#pragma pack(1)\n",
1949 " int i; };\n",
1950 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
1951 "#pragma pack(1)\n",
1952 "struct I { char c;\n",
1953 "#pragma pack()\n",
1954 " int i; };\n",
1955 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1956 "#pragma pack()\n",
1957 "#pragma pack(push, 8)\n",
1959 "#pragma pack(push, 1)\n",
1960 "struct P { char c; int i; };\n",
1961 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
1962 "#pragma pack(pop)\n",
1963 "struct Q { char c; int i; };\n",
1964 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
1965 "#pragma pack(pop)\n",
1966 "#pragma pack(16)\n",
1968 "struct R { char c; int i; };\n",
1969 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
1970 "#pragma pack()\n",
1971 "#pragma pack(1)\n",
1972 "struct S { char c; int i : 5; int j : 20; };\n",
1973 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
1974 "union T { char c; int i; };\n",
1975 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
1976 "#pragma pack()\n",
1977 ));
1978 }
1979
1980 #[test]
1984 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
1985 let result = run(
1986 &options(),
1987 concat!(
1988 "#pragma pack 4\n",
1989 "#pragma pack(pop)\n",
1990 "#pragma pack(3)\n",
1991 "#pragma pack(1) junk\n",
1992 "#pragma pack(push, 1\n",
1993 "#pragma pack(x)\n",
1994 "#pragma pack(0)\n",
1997 "#pragma pack(push)\n",
1998 "struct s { char c; int i; };\n",
1999 "#pragma pack(pop)\n",
2000 "#pragma pack(pop, foo)\n",
2001 ),
2002 );
2003 let expected = [
2004 "missing `(` after `#pragma pack` - ignored",
2005 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2006 "alignment must be a small power of two, not 3",
2007 "junk at end of `#pragma pack`",
2008 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2009 "unknown action `x` for `#pragma pack` - ignored",
2010 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2011 ];
2012 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2013 for (message, want) in result.messages.iter().zip(expected) {
2014 assert!(message.contains(want), "expected {want:?} in {message:?}");
2015 }
2016 }
2017
2018 #[test]
2025 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2026 let result = run(
2027 &options(),
2028 concat!(
2029 "#pragma pack(push, 1)\n",
2030 "#pragma pack(pop)\n",
2031 "#define API\n",
2032 "API const char version[] = \"3.53.4\";\n",
2033 "const char *get(void) { return version; }\n",
2034 ),
2035 );
2036 assert!(result.messages.is_empty(), "{:?}", result.messages);
2037 }
2038
2039 #[test]
2043 fn the_wide_integer_answers_to_all_three_of_its_names() {
2044 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2045 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2046 assert!(text.contains("decl #1 b : __int128"), "{text}");
2047 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2048 }
2049
2050 #[test]
2051 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2052 let text = tast("long f(int a, long b) { return a + b; }\n");
2056 assert!(text.contains("convert arithmetic"), "{text}");
2057 }
2058
2059 #[test]
2060 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2061 for source in [
2062 "#error stop\n",
2063 "int f(void) { return 1 + ; }\n",
2064 "int f(void) { return undeclared; }\n",
2065 ] {
2066 let result = run(&options(), source);
2067 assert!(result.failed(), "expected this to fail:\n{source}");
2068 assert!(
2069 result.text().is_empty(),
2070 "a file that did not compile wrote a tree:\n{source}"
2071 );
2072 }
2073 }
2074
2075 #[test]
2076 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2077 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2081 assert_eq!(result.errors, 1, "{:?}", result.messages);
2082 }
2083
2084 #[test]
2085 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2086 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2090 assert_eq!(result.errors, 1, "{:?}", result.messages);
2091 }
2092
2093 #[test]
2094 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2095 let source = "int f(void) { char c = 300; return c; }\n";
2096 let plain = run(&options(), source);
2097 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2098 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2099 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2100
2101 let mut opts = options();
2102 opts.warnings_are_errors = true;
2103 let strict = run(&opts, source);
2104 assert!(strict.failed());
2105 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2106 for message in &strict.messages {
2107 assert!(!message.contains("warning:"), "{message}");
2108 }
2109 }
2110
2111 #[test]
2112 fn w_drops_the_warning_before_werror_can_promote_it() {
2113 let source = "int f(void) { char c = 300; return c; }\n";
2114 let mut opts = options();
2115 opts.warnings = false;
2116 let quiet = run(&opts, source);
2117 assert_eq!(quiet.messages, Vec::<String>::new());
2118 assert_eq!(quiet.errors, 0);
2119 assert!(!quiet.text().is_empty(), "and the file still compiles");
2120
2121 opts.warnings_are_errors = true;
2124 let both = run(&opts, source);
2125 assert_eq!(both.messages, Vec::<String>::new());
2126 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2127 }
2128
2129 #[test]
2130 fn the_dialect_reaches_the_keywords_and_the_checking() {
2131 let source = "typeof(1) x;\n";
2134 let mut opts = options();
2135 opts.std = Std::C23;
2136 opts.gnu_extensions = false;
2137 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2138
2139 opts.std = Std::C17;
2140 assert!(run(&opts, source).failed());
2141 }
2142
2143 #[test]
2144 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2145 let mut opts = options();
2146 opts.emit = EmitKind::Object;
2147 let result = run(&opts, "int x = 1;\n");
2148 assert!(!result.failed(), "{:?}", result.messages);
2149 assert!(result.text().is_empty());
2150 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2153 }
2154
2155 fn mir(source: &str) -> String {
2157 let mut opts = options();
2158 opts.emit = EmitKind::MirFinal;
2159 let result = run(&opts, source);
2160 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2161 result.text().to_owned()
2162 }
2163
2164 #[test]
2170 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2171 let text = mir("int add(int a, int b) { return a + b; }\n");
2172 assert!(text.starts_with("mfunc @add {"), "{text}");
2173 assert!(text.contains("x64.add_rr_32"), "{text}");
2174 assert!(text.contains("x64.ret"), "{text}");
2175 assert!(!text.contains('%'), "{text}");
2178 }
2179
2180 #[test]
2182 fn a_function_with_no_body_produces_no_machine_function() {
2183 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2184 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2185 assert!(text.contains("mfunc @f {"), "{text}");
2186 assert!(text.contains("x64.call"), "{text}");
2187 }
2188
2189 #[test]
2191 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2192 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2193 let first = text.find("mfunc @a").expect("the first function");
2194 let second = text.find("mfunc @b").expect("the second function");
2195 assert!(first < second, "{text}");
2196 }
2197
2198 #[test]
2200 fn the_target_decides_which_convention_the_generated_code_follows() {
2201 let mut opts = options();
2202 opts.emit = EmitKind::MirFinal;
2203 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2204 assert!(linux.contains("$rdi"), "{linux}");
2205
2206 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2207 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2208 assert!(windows.contains("$rcx"), "{windows}");
2209 assert!(!windows.contains("$rdi"), "{windows}");
2210 }
2211
2212 #[test]
2220 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2221 let source = concat!(
2222 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2223 "int size(void) { return sizeof(struct S); }\n",
2224 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2225 );
2226
2227 let mut opts = options();
2228 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2229 let windows = run(&opts, source);
2230 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2231
2232 let linux = run(&options(), source);
2233 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2234 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2235
2236 let mut opts = options();
2239 opts.ms_extensions = Some(true);
2240 let asked = run(&opts, source);
2241 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2242 }
2243
2244 #[test]
2246 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2247 let mut opts = options();
2248 opts.emit = EmitKind::MirFinal;
2249 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2250 let result = run(&opts, "int f(int a) { return a; }\n");
2251 assert!(result.failed());
2252 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2253 assert!(result.text().is_empty());
2254 }
2255
2256 #[test]
2263 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2264 let mut opts = options();
2265 opts.emit = EmitKind::MirFinal;
2266 let source = "void a(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n\
2267 void b(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n";
2268 let result = run(&opts, source);
2269 assert!(result.failed());
2270 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2271 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2272 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2273 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2274 assert!(result.text().is_empty());
2275 }
2276
2277 #[test]
2285 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2286 let mut opts = options();
2287 opts.emit = EmitKind::MirFinal;
2288 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2289 let plain = run(&opts, source);
2290 assert!(!plain.failed(), "{:?}", plain.messages);
2291 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2292
2293 opts.stack_clash = true;
2294 let result = run(&opts, source);
2295 assert!(!result.failed(), "{:?}", result.messages);
2296 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2297 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2298 }
2299
2300 #[test]
2314 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
2315 let mut opts = options();
2316 opts.emit = EmitKind::MirFinal;
2317 let source =
2318 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
2319 let result = run(&opts, source);
2320 assert!(result.failed());
2321 assert!(
2322 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
2323 "{result:?}"
2324 );
2325 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
2326 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
2327 }
2328
2329 #[test]
2331 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
2332 let mut opts = options();
2333 opts.emit = EmitKind::MirFinal;
2334 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
2335 let result = run(&opts, source);
2336 assert!(result.failed());
2337 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
2338 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
2339 assert!(!note.contains("spec/17-milestones.md"), "{note}");
2340 }
2341
2342 #[test]
2344 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
2345 let source = "int f(int a) { return a; }\n";
2346 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
2347
2348 let mut opts = options();
2349 opts.emit = EmitKind::MirFinal;
2350 opts.frame_pointer = true;
2351 let kept = run(&opts, source).text().to_owned();
2352 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
2353 }
2354
2355 fn asm(source: &str) -> String {
2357 let mut opts = options();
2358 opts.emit = EmitKind::Asm;
2359 let result = run(&opts, source);
2360 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2361 result.text().to_owned()
2362 }
2363
2364 #[test]
2371 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
2372 let text = asm("int add(int a, int b) { return a + b; }\n");
2373 assert!(text.contains("\t.globl\tadd\n"), "{text}");
2374 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
2375 assert!(text.contains("\nadd:\n"), "{text}");
2376 assert!(text.contains("\taddl\t"), "{text}");
2377 assert!(text.contains("\tret\n"), "{text}");
2378 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
2379 assert!(text.contains(".note.GNU-stack"), "{text}");
2382 }
2383
2384 #[test]
2390 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
2391 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
2392 assert!(text.contains("\tcall\t*%"), "{text}");
2393 assert!(text.contains("\tcall\tg\n"), "{text}");
2394 assert!(text.contains("%rdi"), "{text}");
2398 }
2399
2400 #[test]
2404 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
2405 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
2406 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
2407 }
2408
2409 #[test]
2418 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
2419 let arms = "return 1; return 2;";
2420 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
2421 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
2422 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
2423 assert!(
2424 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2425 "{operator}: {text}"
2426 );
2427 assert!(!text.contains("\tset"), "{operator}: {text}");
2428 assert!(!text.contains("\ttest"), "{operator}: {text}");
2429 }
2430 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
2431 for (operator, jump) in unsigned {
2432 let source =
2433 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
2434 let text = asm(&source);
2435 assert!(
2436 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2437 "{operator}: {text}"
2438 );
2439 }
2440
2441 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
2444 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
2445 }
2446
2447 #[test]
2453 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
2454 let text = asm("int f(int a, int b) { return a < b; }\n");
2455 assert!(text.contains("\tsetl\t"), "{text}");
2456 }
2457
2458 fn optimized(source: &str) -> String {
2460 let mut opts = options();
2461 opts.emit = EmitKind::Asm;
2462 opts.opt_level = rucc_session::OptLevel::O2;
2463 let result = run(&opts, source);
2464 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2465 result.text().to_owned()
2466 }
2467
2468 #[test]
2478 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
2479 let arms: String =
2480 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
2481 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2482 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
2483 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
2484 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
2485 }
2486
2487 #[test]
2494 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
2495 let arms: String = (0..16)
2496 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
2497 .collect::<Vec<_>>()
2498 .join(" ");
2499 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2500 assert!(text.matches("\tcmp").count() > 1, "{text}");
2501 }
2502
2503 #[test]
2505 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2506 let text = asm("long f(void *p) { return (long)p; }\n");
2507 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2512 let mnemonic = line.split_whitespace().next().unwrap_or("");
2513 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2514 }
2515 }
2516
2517 #[test]
2521 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2522 let six = "long a, long b, long c, long d, long e, long f";
2523 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2524
2525 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2532 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2533
2534 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2538 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2539 let eight =
2540 "double a, double b, double c, double d, double e, double f, double g, double h";
2541 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2542 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2543 }
2544
2545 #[test]
2548 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2549 let six = "1, 2, 3, 4, 5, 6";
2550 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2551 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2552
2553 assert!(text.contains("\tmovq\t%"), "{text}");
2554 assert!(text.contains(", (%rsp)\n"), "{text}");
2555 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2556 assert!(text.contains("\tsubq\t$"), "{text}");
2558
2559 let narrow = "int g(int, int, int, int, int, int, int);\n";
2561 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2562 assert!(text.contains("\tmovl\t%"), "{text}");
2563 assert!(text.contains(", (%rsp)\n"), "{text}");
2564 }
2565
2566 #[test]
2569 fn a_variadic_call_counts_registers_and_not_arguments() {
2570 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2571 let decl = "int g(int, ...);\n";
2572 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2573
2574 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2575 assert!(text.contains("\tmovsd\t%"), "{text}");
2576 assert!(text.contains(", (%rsp)\n"), "{text}");
2577 }
2578
2579 #[test]
2584 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2585 let body =
2586 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2587 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2588
2589 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2592 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2593 assert!(!text.contains(", 0(%r"), "{text}");
2594 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2597 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2598
2599 assert!(text.contains("\tsubq\t$"), "{text}");
2601 }
2602
2603 #[test]
2606 fn va_start_writes_the_four_fields_the_psabi_describes() {
2607 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2608 let params = "int a, int b, int c, double d";
2609 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2610
2611 assert!(text.contains(" movl $24, "), "{text}");
2615 assert!(text.contains(" movl $64, "), "{text}");
2616 assert!(text.contains(", 8(%r"), "{text}");
2620 assert!(text.contains(", 16(%r"), "{text}");
2621 let frame: u32 = text
2622 .lines()
2623 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
2624 .expect("a variadic function takes a frame for the save area");
2625 let above = |line: &str| {
2626 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
2627 Some(at > frame)
2628 };
2629 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
2630 }
2631
2632 #[test]
2635 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
2636 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
2637 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
2638 let text = asm(&ints);
2639
2640 assert!(text.contains("$40, "), "{text}");
2643 assert!(text.contains(" cmpl "), "{text}");
2644 assert!(text.contains(" ja "), "{text}");
2648
2649 let arg = "__builtin_va_arg(ap, double)";
2650 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
2651 assert!(text.contains("$160, "), "the last vector slot: {text}");
2652 }
2653
2654 #[test]
2657 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
2658 let decl = "struct pair { long a, b; };\n";
2659 let body = "struct pair p = *q; return p.a + p.b;";
2660 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
2661
2662 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
2663 assert!(!text.contains("\tcall"), "{text}");
2664 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
2666 }
2667
2668 #[test]
2671 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
2672 let decl = "struct bytes { char a[8]; };\n";
2673 let body = "struct bytes p = *q; return p.a[0];";
2674 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
2675
2676 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
2678 }
2679
2680 #[test]
2683 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
2684 let decl = "struct wide { long a, b, c; };\n";
2685 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
2686
2687 assert!(!text.contains("memset"), "nothing calls the library: {text}");
2688 assert!(text.contains("\tmovq\t$0, ") || text.contains("$0, %"), "the zero: {text}");
2689 }
2690
2691 #[test]
2694 fn a_copy_too_large_to_unroll_calls_the_runtime() {
2695 let decl = "struct huge { char a[4096]; };\n";
2696 let mut opts = options();
2697 opts.emit = EmitKind::Asm;
2698 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
2699 let result = run(&opts, &source);
2700 assert!(!result.failed(), "{:?}", result.messages);
2701 let text = result.text();
2702 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
2703 assert!(text.contains("4096"), "the size travels: {text}");
2706 }
2707
2708 #[test]
2715 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
2716 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
2717 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
2718
2719 let copy = text.find("call\tmemcpy").expect("the copy");
2720 let call = text.find("call\ttake").expect("the call");
2721 assert!(copy < call, "the copy comes first: {text}");
2722 assert!(text.contains("leaq\t(%rsp), %rdi"), "the destination: {text}");
2725 assert!(text.contains("$4096, %edx"), "the size: {text}");
2726 }
2727
2728 #[test]
2731 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
2732 let six = "long a, long b, long c, long d, long e, long f";
2733 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
2734 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
2735
2736 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
2740 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
2741 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
2742 }
2743
2744 #[test]
2746 fn the_target_decides_how_the_assembly_is_spelled() {
2747 let mut opts = options();
2748 opts.emit = EmitKind::Asm;
2749 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
2750 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
2751 assert!(text.contains("__TEXT,__text"), "{text}");
2752 assert!(text.contains("\n_f:\n"), "{text}");
2753 assert!(!text.contains(".note.GNU-stack"), "{text}");
2754 }
2755
2756 fn obj(source: &str) -> Vec<u8> {
2758 let mut opts = options();
2759 opts.emit = EmitKind::Object;
2760 let result = run(&opts, source);
2761 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2762 match result.artifact {
2763 Artifact::Object { bytes, .. } => bytes,
2764 other => panic!("expected an object, got {other:?}"),
2765 }
2766 }
2767
2768 #[test]
2774 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
2775 let bytes = obj("int add(int a, int b) { return a + b; }\n");
2776 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
2777 let text = asm("int add(int a, int b) { return a + b; }\n");
2778 assert!(
2779 text.contains("\taddl\t"),
2780 "and the listing of it is the same instructions:\n{text}"
2781 );
2782 }
2783
2784 #[test]
2786 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
2787 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
2788 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
2789 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
2790 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
2791 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
2794 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
2795 assert!(!text.contains(".globl\thidden"), "{text}");
2796 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2799 }
2800
2801 #[test]
2808 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
2809 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
2810 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
2811 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
2812
2813 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
2816 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
2817
2818 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
2821 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
2822
2823 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
2825 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
2826 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
2827 }
2828
2829 #[test]
2831 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
2832 let text = asm("const char *f(void) { return \"hi\"; }\n");
2833 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
2834 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2835 let label = text
2836 .lines()
2837 .find(|line| line.starts_with(".Lstr"))
2838 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
2839 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
2840 }
2841
2842 #[test]
2844 fn an_address_in_an_initializer_is_left_to_the_linker() {
2845 let source = "int counter;\nint *p = &counter;\n";
2846 let text = asm(source);
2847 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
2848 let bytes = obj(source);
2851 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
2852 }
2853
2854 #[test]
2863 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
2864 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
2867 struct m { void (*x)(void); void (*y)(void); };\n\
2868 const struct m t = { a, b };\n");
2869 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
2870 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
2871
2872 let text =
2875 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
2876 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
2877
2878 let text = asm("const int fixed = 7;\n");
2880 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2881 }
2882
2883 #[test]
2890 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
2891 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
2892 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
2895 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
2896 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
2899 assert!(text.contains("%fs:0"), "{text}");
2900 }
2901
2902 #[test]
2908 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
2909 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
2910 assert!(text.contains("movq\t%fs:0, "), "{text}");
2911 assert!(!text.contains("GOTTPOFF"), "{text}");
2913 }
2914
2915 #[test]
2926 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
2927 for (locality, wanted) in
2928 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
2929 {
2930 let source =
2931 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
2932 let text = asm(&source);
2933 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
2934 }
2935 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
2937 assert!(text.contains("\tprefetcht0\t"), "{text}");
2938 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
2941 assert!(text.contains("\tprefetcht0\t"), "{text}");
2942 assert!(!text.contains("prefetchw"), "{text}");
2943 }
2944
2945 #[test]
2956 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
2957 let text = asm("void stop(void) { __builtin_trap(); }\n");
2958 assert!(text.contains("\tud2\n"), "{text}");
2959 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
2960
2961 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
2962 assert!(text.contains("\tud2\n"), "{text}");
2963 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
2964 }
2965
2966 #[test]
2978 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
2979 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
2980 assert!(!text.contains("assume_aligned"), "{text}");
2981 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
2982
2983 let source = "unsigned long width(void);\n\
2984 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
2985 let text = asm(source);
2986 assert!(!text.contains("assume_aligned"), "{text}");
2987 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
2988 }
2989
2990 #[test]
3000 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3001 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3002 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3003 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3004 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3005
3006 let walk = |depth: u32| {
3007 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3008 asm(&source).matches("movq\t(%r").count()
3009 };
3010 assert_eq!(walk(1), 1, "one link is one load");
3011 assert_eq!(walk(3), 3, "three links are three loads");
3012 }
3013
3014 #[test]
3024 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3025 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3026 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3027 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3028 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3029
3030 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3031 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3032 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3033 }
3034
3035 #[test]
3046 fn a_depth_that_is_not_a_small_constant_is_refused() {
3047 let mut opts = options();
3048 opts.emit = EmitKind::Ir;
3049 for source in [
3050 "void *up(int n) { return __builtin_return_address(n); }\n",
3051 "void *up(void) { return __builtin_frame_address(1000); }\n",
3052 ] {
3053 let messages = run(&opts, source).messages;
3054 let named = messages.iter().any(|m| m.contains("E0705"));
3055 assert!(named, "expected a refusal in {messages:?}");
3056 }
3057 }
3058
3059 #[test]
3071 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3072 let text =
3073 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3074 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
3075 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
3076 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3077
3078 let plain = concat!(
3081 "extern void *alloca(__SIZE_TYPE__);\n",
3082 "void use(void *p);\n",
3083 "void f(unsigned long n) { use(alloca(n)); }\n",
3084 );
3085 let text = asm(plain);
3086 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
3087 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
3088
3089 let own = concat!(
3092 "static void *alloca(unsigned long n) { return 0; }\n",
3093 "void *f(unsigned long n) { return alloca(n); }\n",
3094 );
3095 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3096 }
3097
3098 #[test]
3108 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3109 let inner = "{ use(__builtin_alloca(n)); }";
3110 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3111 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3112 let text = asm(&source);
3113 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3117 let taking = line.contains("subq");
3118 let leaving = line.contains("%rbp");
3119 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3120 }
3121 }
3122 }
3123
3124 #[test]
3126 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3127 let source = "int callee(void); int g(void) { return callee(); }\n";
3131 let bytes = obj(source);
3132 assert!(
3133 bytes.windows(7).any(|w| w == b"callee\0"),
3134 "the object has to name the callee for the linker to find it"
3135 );
3136 let text = asm(source);
3137 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3138 }
3139
3140 #[test]
3146 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3147 let mut opts = options();
3148 opts.emit = EmitKind::Executable;
3150 let result = run(&opts, "int main(void) { return 0; }\n");
3151 assert_eq!(result.messages, Vec::<String>::new());
3152 match result.artifact {
3153 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3154 other => panic!("expected an object, got {other:?}"),
3155 }
3156 }
3157
3158 #[test]
3160 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3161 let mut opts = options();
3162 opts.emit = EmitKind::Object;
3163 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3164 let result = run(&opts, "int f(void) { return 0; }\n");
3165 assert!(result.failed(), "an object nobody can read is worse than a message");
3166 assert!(
3167 result.messages.iter().any(|m| m.contains("no object writer")),
3168 "{:?}",
3169 result.messages
3170 );
3171 }
3172
3173 fn ir(source: &str) -> String {
3175 let mut opts = options();
3176 opts.emit = EmitKind::Ir;
3177 let result = run(&opts, source);
3178 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3179 result.text().to_owned()
3180 }
3181
3182 fn errors(source: &str) -> Vec<String> {
3184 let mut opts = options();
3185 opts.emit = EmitKind::Ir;
3186 let result = run(&opts, source);
3187 assert!(result.failed(), "expected this to be refused:\n{source}");
3188 result.messages
3189 }
3190
3191 fn body(source: &str) -> String {
3193 let text = ir(source);
3194 let (_, rest) = text.split_once("{\n").expect("a function definition");
3195 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3196 body.to_owned()
3197 }
3198
3199 #[test]
3207 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3208 let source = "inline int f(int x) { return x + 1; }\n";
3209 let with = |flag: bool| {
3210 let mut opts = options();
3211 opts.emit = EmitKind::Ir;
3212 opts.gnu89_inline = flag;
3213 let result = run(&opts, source);
3214 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3215 result.text().to_owned()
3216 };
3217
3218 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3221
3222 assert!(with(true).contains("block0"), "a body: {}", with(true));
3225 }
3226
3227 #[test]
3234 fn an_access_through_a_type_names_the_type_it_went_through() {
3235 let source = "\
3236struct s { int a; float b; };\n\
3237union u { int i; float f; };\n\
3238int scalar(int *p) { return *p; }\n\
3239float member(struct s *p) { p->a = 1; return p->b; }\n\
3240int element(int *a, long i) { return a[i]; }\n\
3241float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3242 let text = ir(source);
3243 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3244 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3245 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3246 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3249 assert_eq!(named, 6, "six accesses: {text}");
3250 }
3251
3252 #[test]
3259 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3260 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3261 let mut opts = options();
3262 opts.emit = EmitKind::Ir;
3263 opts.strict_aliasing = false;
3264 let result = run(&opts, source);
3265 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3266 let text = result.text().to_owned();
3267 assert!(!text.contains("tbaa"), "not even the root: {text}");
3268 }
3269
3270 #[test]
3278 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3279 let mut opts = options();
3280 opts.emit = EmitKind::Ir;
3281 opts.std = Std::C89;
3282 let compiled = |source: &str| {
3283 let result = run(&opts, source);
3284 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3285 result.text().to_owned()
3286 };
3287
3288 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3289 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3290 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3291
3292 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3294 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3295 }
3296
3297 #[test]
3305 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3306 let mut opts = options();
3307 opts.emit = EmitKind::Ir;
3308 opts.std = Std::C89;
3309 let compiled = |source: &str| {
3310 let result = run(&opts, source);
3311 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3312 result.text().to_owned()
3313 };
3314
3315 let text = compiled("int f(void) { return g(); }\n");
3317 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3318 assert!(text.contains("i32"), "and it gives back an int: {text}");
3319
3320 let text = compiled("int f(char c) { return g(c); }\n");
3323 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3324
3325 let mut opts = options();
3328 opts.std = Std::C89;
3329 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3330 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3331 }
3332
3333 #[test]
3343 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3344 let mut opts = options();
3345 opts.emit = EmitKind::Ir;
3346 opts.std = Std::C89;
3347 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3348 .text()
3349 .to_owned();
3350 assert!(text.contains("func @f()"), "the caller is there: {text}");
3351 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3352 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3353 }
3354
3355 #[test]
3363 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3364 let mut opts = options();
3365 opts.emit = EmitKind::Ir;
3366 opts.std = Std::C89;
3367 let compiled = |source: &str| run(&opts, source).text().to_owned();
3368
3369 let text = compiled("f (c) unsigned char c; { return c; }\n");
3370 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3371 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3372 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3373
3374 let text = compiled("f (s) short s; { return s; }\n");
3376 assert!(text.contains("trunc.i16"), "cut down: {text}");
3377 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3378
3379 let text = compiled("f (x) float x; { return x * 2; }\n");
3382 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3383 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3384
3385 let text = compiled("int f(unsigned char c) { return c; }\n");
3388 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3389 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3390 }
3391
3392 #[test]
3401 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3402 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3404 let cases = [
3405 ("static counted;\n", ["", "error", "warning", "error"]),
3406 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3407 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3408 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3409 (
3410 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3411 ["warning", "error", "warning", "error"],
3412 ),
3413 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3414 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3415 ];
3416
3417 for (source, wanted) in cases {
3418 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3419 let mut opts = options();
3420 opts.std = std;
3421 opts.permissive = permissive;
3422 let said = run(&opts, source).messages.join("\n");
3423 let severity = if said.contains(": error: ") {
3424 "error"
3425 } else if said.contains(": warning: ") {
3426 "warning"
3427 } else {
3428 ""
3429 };
3430 let how = if permissive { " -fpermissive" } else { "" };
3431 assert_eq!(
3432 severity,
3433 wanted,
3434 "under -std={}{how}, {source} was answered with `{said}`",
3435 std.as_str()
3436 );
3437 if wanted.is_empty() {
3438 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3439 }
3440 }
3441 }
3442 }
3443
3444 #[test]
3453 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3454 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3455 let cases = [
3456 (
3457 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3458 "first argument to 'va_arg' not of type 'va_list'",
3459 ["error", "error", "error", "error"],
3460 ),
3461 (
3462 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3463 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3464 ["warning", "error", "warning", "error"],
3465 ),
3466 (
3467 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3468 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3469 cast",
3470 ["warning", "error", "warning", "error"],
3471 ),
3472 (
3473 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3474 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3475 ["warning", "error", "warning", "error"],
3476 ),
3477 ];
3478
3479 for (source, message, wanted) in cases {
3480 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3481 let mut opts = options();
3482 opts.std = std;
3483 opts.permissive = permissive;
3484 let said = run(&opts, source).messages.join("\n");
3485 let how = if permissive { " -fpermissive" } else { "" };
3486 assert!(
3487 said.contains(&format!(": {wanted}: {message}")),
3488 "under -std={}{how}, {source} was answered with `{said}`",
3489 std.as_str()
3490 );
3491 }
3492 }
3493 }
3494
3495 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3497 let mut opts = options();
3498 opts.emit = EmitKind::Ir;
3499 opts.safety = tier;
3500 let result = run(&opts, source);
3501 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3502 result.text().to_owned()
3503 }
3504
3505 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3506
3507 fn padded_ir(padding: Padding, source: &str) -> String {
3509 let mut opts = options();
3510 opts.emit = EmitKind::Ir;
3511 opts.safety = rucc_session::Safety::Detect;
3512 opts.padding = padding;
3513 let result = run(&opts, source);
3514 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3515 result.text().to_owned()
3516 }
3517
3518 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3519 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3520
3521 #[test]
3522 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3523 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3527 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3528 }
3529
3530 #[test]
3531 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3532 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3535 assert!(!text.contains("owns"), "{text}");
3536 }
3537
3538 #[test]
3539 fn a_member_of_a_union_owns_nothing_after_it() {
3540 let text = padded_ir(
3544 Padding::Ignored,
3545 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3546 );
3547 assert!(!text.contains("owns"), "{text}");
3548 }
3549
3550 #[test]
3551 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3552 let text = padded_ir(
3557 Padding::Ignored,
3558 "struct inner { char c; };\n\
3559 struct outer { struct inner in; int x; };\n\
3560 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3561 );
3562 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3563 }
3564
3565 #[test]
3566 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3567 let text = ir(READS_THROUGH_A_POINTER);
3571 assert!(!text.contains("check_"), "{text}");
3572 assert!(!text.contains("cap_of"), "{text}");
3573 }
3574
3575 #[test]
3576 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3577 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3578 assert!(text.contains("cap_of"), "{text}");
3579 assert!(text.contains("check_bounds"), "{text}");
3580 assert!(text.contains("check_live"), "{text}");
3581 assert!(text.contains("check_deriv"), "{text}");
3583 assert!(text.contains("check_type"), "{text}");
3585 }
3586
3587 #[test]
3588 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3589 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3593 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3594 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3595 }
3596 }
3597
3598 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3600 let mut opts = options();
3601 opts.emit = EmitKind::SafetySummary;
3602 opts.safety = tier;
3603 let result = run(&opts, source);
3604 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3605 result.text().to_owned()
3606 }
3607
3608 #[test]
3609 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3610 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3611 assert!(text.contains("\"tier\": \"detect\""), "{text}");
3612 assert!(
3614 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
3615 "{text}"
3616 );
3617 assert!(
3618 text.contains(
3619 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
3620 ),
3621 "{text}"
3622 );
3623 }
3624
3625 #[test]
3626 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
3627 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
3631 assert!(text.contains("\"tier\": \"off\""), "{text}");
3632 assert!(
3633 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
3634 "{text}"
3635 );
3636 }
3637
3638 #[test]
3639 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
3640 let text = summary(
3641 rucc_session::Safety::Detect,
3642 "void *memcpy(void *, const void *, unsigned long);\n\
3643 int puts(const char *);\n\
3644 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
3645 );
3646 assert!(text.contains("\"interposed\": 1"), "{text}");
3647 assert!(text.contains("\"puts\""), "{text}");
3648 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
3652 }
3653
3654 #[test]
3655 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3656 let text = summary(
3660 rucc_session::Safety::Detect,
3661 "void *notes_open(void);\n\
3662 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3663 );
3664 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3665 assert!(text.contains("\"notes_open\""), "{text}");
3666 }
3667
3668 #[test]
3669 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3670 let text = summary(
3673 rucc_session::Safety::Detect,
3674 "static int len(const char *p) { return p ? 1 : 0; }\n\
3675 int f(void) { return len(\"x\"); }\n",
3676 );
3677 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3678 }
3679
3680 fn granules(source: &str) -> String {
3682 let mut opts = options();
3683 opts.emit = EmitKind::TypeGranules;
3684 let result = run(&opts, source);
3685 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3686 result.text().to_owned()
3687 }
3688
3689 #[test]
3690 fn the_granule_report_names_every_record_and_both_keyings() {
3691 let text = granules(
3692 "struct hot { char *p; int a; int b; };\n\
3693 int f(struct hot *h) { return h->a; }\n",
3694 );
3695 assert!(text.contains("struct hot"), "{text}");
3696 assert!(text.contains("every type distinct"), "{text}");
3699 assert!(text.contains("every pointer one type"), "{text}");
3700 assert!(text.contains("budget"), "{text}");
3701 }
3702
3703 #[test]
3704 fn a_record_nothing_uses_is_still_measured() {
3705 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3708 assert!(text.contains("struct unused"), "{text}");
3709 }
3710
3711 #[test]
3712 fn the_granule_report_stops_before_anything_is_lowered() {
3713 let text = granules(
3717 "struct wide { long double d; };\n\
3718 long double f(long double x) { return x * x; }\n",
3719 );
3720 assert!(text.contains("struct wide"), "{text}");
3721 }
3722
3723 #[test]
3724 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
3725 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
3728 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
3729 }
3730
3731 #[test]
3732 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
3733 let text = summary(
3734 rucc_session::Safety::Detect,
3735 "unsigned long f(int *p) { return (unsigned long) p; }\n",
3736 );
3737 assert!(text.contains("\"exposed\": 1"), "{text}");
3738 }
3739
3740 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
3742 let mut opts = options();
3743 opts.emit = EmitKind::Asm;
3744 opts.safety = tier;
3745 let result = run(&opts, source);
3746 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3747 result.text().to_owned()
3748 }
3749
3750 #[test]
3751 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
3752 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3753 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
3754 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
3755 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
3756 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
3757 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
3758 }
3759
3760 #[test]
3761 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
3762 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3766 let section = format!("\t.section\t{},", rucc_safety::SECTION);
3767 assert_eq!(text.matches(§ion).count(), 5, "{text}");
3768 for index in 0..5 {
3769 let name = format!("__rucc_safety_desc_{index}");
3770 assert!(text.contains(&format!("{name}:\n")), "{text}");
3773 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
3774 }
3775 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
3776 }
3777
3778 #[test]
3786 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
3787 let text = ir(concat!(
3788 "int g;\n",
3789 "int a = __builtin_constant_p(1);\n",
3790 "int b = __builtin_constant_p(g);\n",
3791 "int c = __builtin_constant_p(\"abc\");\n",
3792 "int d = __builtin_constant_p(&g);\n",
3793 "int e = __builtin_constant_p(1.5);\n",
3794 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
3795 ));
3796 assert!(text.contains("global @a : i32 = 1,"), "{text}");
3797 assert!(text.contains("global @b : i32 = 0,"), "{text}");
3798 assert!(text.contains("global @c : i32 = 1,"), "{text}");
3799 assert!(text.contains("global @d : i32 = 0,"), "{text}");
3800 assert!(text.contains("global @e : i32 = 1,"), "{text}");
3801 assert!(text.contains("global @h : i32 = 11,"), "{text}");
3802 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
3803
3804 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
3808 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
3809 }
3810
3811 #[test]
3820 fn a_call_to_a_library_builtin_reaches_the_library_function() {
3821 let text = body("void f(void) { __builtin_abort(); }\n");
3822 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
3823
3824 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
3827 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
3828 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
3829 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3830 }
3831
3832 #[test]
3845 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
3846 let text = ir(concat!(
3847 "char d[8];\n",
3848 "void f(const char *s, unsigned long n) {\n",
3849 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3850 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
3851 " __builtin___memset_chk(d, 0, n, 8);\n",
3852 "}\n",
3853 ));
3854 assert!(text.contains("call @__memcpy_chk("), "{text}");
3855 assert!(text.contains("call @__strcpy_chk("), "{text}");
3856 assert!(text.contains("call @__memset_chk("), "{text}");
3857 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
3858 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3859 }
3860
3861 #[test]
3869 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
3870 let text = ir(concat!(
3871 "extern char *p;\n",
3872 "char d[8];\n",
3873 "void f(const char *s, unsigned long n) {\n",
3874 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3875 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3876 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
3877 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3878 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
3879 "}\n",
3880 ));
3881
3882 assert!(
3884 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
3885 "{text}"
3886 );
3887
3888 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3891 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
3892 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3893
3894 assert!(text.contains("call @__sprintf_chk("), "{text}");
3897
3898 let asm = asm(concat!(
3901 "void f(char *p, const char *s, unsigned long n) {\n",
3902 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3903 "}\n",
3904 ));
3905 assert!(asm.contains("call\tmemcpy"), "{asm}");
3906 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
3907 }
3908
3909 #[test]
3917 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
3918 let text = ir(concat!(
3919 "char d[64];\n",
3920 "int f(const char *fmt, ...) {\n",
3921 " __builtin_va_list ap;\n",
3922 " __builtin_va_start(ap, fmt);\n",
3923 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
3924 " __builtin_va_end(ap);\n",
3925 " return n;\n",
3926 "}\n",
3927 ));
3928 assert!(text.contains("call @__vsprintf_chk("), "{text}");
3929 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
3930 }
3931
3932 #[test]
3943 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
3944 let text = body(concat!(
3945 "long long llabs(long long);\n",
3946 "long long f(long long x) { return llabs(x); }\n",
3947 ));
3948 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
3949 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
3950 assert!(text.contains("%3 = xor %0, %2"), "{text}");
3951 assert!(text.contains("%4 = sub %3, %2"), "{text}");
3952 assert!(!text.contains("call"), "the call does not happen:\n{text}");
3953
3954 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
3957 assert!(text.contains("iconst.i32 31"), "{text}");
3958 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
3959 assert!(text.contains("iconst.i64 63"), "{text}");
3960
3961 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
3964 assert!(!text.contains("call"), "{text}");
3965
3966 let text = ir(concat!(
3968 "long long llabs(long long b);\n",
3969 "long long g(long long x) { return llabs(x); }\n",
3970 "long long llabs(long long b) { return 7; }\n",
3971 ));
3972 assert!(!text.contains("call @llabs"), "{text}");
3973 }
3974
3975 #[test]
3982 fn a_byte_swap_is_arithmetic_and_not_a_call() {
3983 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
3984 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
3985
3986 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
3989 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
3990 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
3991 }
3992
3993 #[test]
3999 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4000 for (name, ty, width) in [
4001 ("__builtin_bswap16", "unsigned short", "i16"),
4002 ("__builtin_bswap32", "unsigned", "i32"),
4003 ("__builtin_bswap64", "unsigned long long", "i64"),
4004 ] {
4005 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4006 let text = body(&source);
4007 assert_eq!(
4008 text,
4009 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4010 "{name}"
4011 );
4012 }
4013 }
4014
4015 #[test]
4022 fn the_bit_counts_are_instructions_and_not_calls() {
4023 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4024 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4025
4026 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4027 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4028
4029 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4030 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4031 }
4032
4033 #[test]
4042 fn the_bit_counts_ask_about_the_width_their_name_says() {
4043 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4044 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4045 assert!(text.contains("%1 = ctlz %0"), "{text}");
4046 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4047
4048 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4051 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4052 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4053
4054 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4055 assert!(text.contains("%1 = ctpop %0"), "{text}");
4056 assert!(!text.contains("call"), "{text}");
4057 }
4058
4059 #[test]
4064 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4065 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4066 assert!(text.contains("%1 = ctpop %0"), "{text}");
4067 assert!(text.contains("iconst.i32 1"), "{text}");
4068 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4069 }
4070
4071 #[test]
4077 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4078 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4079 assert!(text.contains("%1 = cttz %0"), "{text}");
4080 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4081 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4082 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4083 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4084 assert!(!text.contains("br_if"), "no branch: {text}");
4085 }
4086
4087 #[test]
4097 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4098 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4099 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4100 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4101 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4102 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4103 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4104 assert!(text.contains("%7 = ctlz %6"), "{text}");
4105 assert!(!text.contains("call"), "{text}");
4106 assert!(!text.contains("br_if"), "no branch: {text}");
4107 }
4108
4109 #[test]
4115 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4116 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4117 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4118 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4119 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4120
4121 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4122 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4123
4124 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4127 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4128 }
4129
4130 #[test]
4138 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4139 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4140 assert!(text.contains("iconst.i64 63"), "{text}");
4141 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4142 assert!(!text.contains("call"), "{text}");
4143
4144 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4145 assert!(text.contains("iconst.i64 63"), "{text}");
4146 assert!(!text.contains("call"), "{text}");
4147 }
4148
4149 #[test]
4157 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4158 let text =
4159 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4160 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4161 assert!(!text.contains("store"), "nothing is written: {text}");
4162 assert!(!text.contains("call"), "{text}");
4163
4164 let text =
4167 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4168 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4169 assert!(!text.contains("store"), "{text}");
4170
4171 let text = body(concat!(
4174 "int g(void);\n",
4175 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4176 ));
4177 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4178 }
4179
4180 #[test]
4190 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4191 let text =
4192 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4193 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4194 assert!(text.contains("store %3 -> %2"), "{text}");
4195 assert!(!text.contains("call"), "{text}");
4196
4197 let text =
4198 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4199 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4200
4201 let text =
4202 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4203 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4204
4205 let text = body(
4208 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4209 );
4210 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4211 }
4212
4213 #[test]
4221 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4222 let text = body(
4223 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4224 );
4225 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4226 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4227 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4228
4229 let text = body(
4232 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4233 );
4234 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4235 assert!(!text.contains("sext."), "{text}");
4236 assert!(!text.contains("zext.i64"), "{text}");
4238 }
4239
4240 #[test]
4248 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4249 let text =
4250 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4251 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4252 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4253 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4254 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4255 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4256 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4257 }
4258
4259 #[test]
4266 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4267 for name in ["add", "sub", "mul"] {
4268 let source = format!(
4269 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4270 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4271 );
4272 let mut opts = options();
4273 opts.emit = EmitKind::MirFinal;
4274 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4275 }
4276 }
4277
4278 #[test]
4281 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4282 let messages =
4283 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4284 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4285
4286 let messages =
4287 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4288 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4289 }
4290
4291 #[test]
4302 fn an_ordered_access_is_ordered_in_the_ir() {
4303 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4304 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4305
4306 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4307 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4308
4309 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4310 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4311
4312 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4313 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4314
4315 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4318 assert!(text.contains("trunc.i8 %1"), "{text}");
4319 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4320 }
4321
4322 #[test]
4331 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4332 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4333 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4334 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4335
4336 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4337 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4338 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4339
4340 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4341 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4342 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4343 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4344 }
4345
4346 #[test]
4356 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4357 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4358 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4359
4360 for weaker in ["1", "2", "3", "4"] {
4361 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4362 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4363 }
4364 }
4365
4366 #[test]
4376 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
4377 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
4378 let source = format!("void f(void) {{ {name}(); }}\n");
4379 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
4380 let text = body(&source);
4381 assert!(text.contains("fence seq_cst"), "{name}: {text}");
4382 }
4383
4384 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
4385 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4386 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
4387 }
4388
4389 #[test]
4395 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4396 let text =
4399 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4400 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4401 assert!(text.contains("return %3"), "the value it found: {text}");
4402
4403 let text =
4404 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4405 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4406 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4407
4408 let text = body(
4411 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4412 );
4413 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4414 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4415 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4416 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4417
4418 let text = body(
4421 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4422 );
4423 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4424 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4425 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4426 }
4427
4428 #[test]
4435 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4436 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4437 for (ty, suffix, reg) in widths {
4438 let source = format!(
4439 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4440 );
4441 let text = asm(&source);
4442 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4443 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4444 assert!(text.contains("sete\t"), "{ty}: {text}");
4445 }
4446 let source =
4447 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4448 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4449
4450 for order in ["0", "2", "3", "4", "5"] {
4454 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4455 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4456 let text = asm(&source);
4457 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4458 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4459 }
4460 }
4461
4462 #[test]
4474 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4475 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4476 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4477 assert!(text.contains("return %2"), "the value that was there: {text}");
4478
4479 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4480 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4481 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4482
4483 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4484 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4485 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4486
4487 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4489 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4490
4491 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4494 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4495
4496 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4497 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4498
4499 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4502 assert!(text.contains("release"), "{text}");
4503 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4504
4505 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4509 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4510 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4511
4512 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4515 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4516
4517 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4518 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4519 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4520
4521 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4524 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4525 assert!(text.contains("%3 = and %2, %1"), "{text}");
4526 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4527 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4528 }
4529
4530 #[test]
4541 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4542 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4543 for (ty, suffix, reg) in widths {
4544 for (name, call, insn) in [
4545 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4546 ("or", "__sync_fetch_and_or(p, v)", "or"),
4547 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4548 ] {
4549 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4550 let text = asm(&source);
4551 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4552 assert!(
4553 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4554 "{ty} {name}: {text}"
4555 );
4556 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4557 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4559 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4560 }
4561 }
4562 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4563 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4564
4565 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4569 assert!(text.contains("cmpxchgl\t"), "{text}");
4570 assert!(text.contains("andl\t"), "{text}");
4571 assert!(text.contains("notl\t"), "{text}");
4572 }
4573
4574 #[test]
4583 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4584 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4585 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4586 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4587
4588 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4589 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4590 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4591
4592 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4595 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4596 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4597 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4598 }
4599
4600 #[test]
4611 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4612 for pointer in ["char", "int", "void"] {
4613 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4614 let text = body(&source);
4615 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4616 assert!(
4617 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4618 "{pointer}: {text}"
4619 );
4620 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4621
4622 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4623 let text = body(&source);
4624 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4625 }
4626
4627 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4630 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4631 assert!(text.contains("setne\t"), "{text}");
4632 }
4633
4634 #[test]
4642 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4643 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4644 for (ty, suffix, reg) in widths {
4645 let source =
4646 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4647 let text = asm(&source);
4648 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4649 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4650
4651 let source =
4652 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4653 let text = asm(&source);
4654 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4655 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4656 }
4657 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4658 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4659
4660 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4663 let text = asm(source);
4664 assert!(text.contains("negl\t"), "{text}");
4665 assert!(text.contains("xaddl\t"), "{text}");
4666
4667 for order in ["0", "2", "3", "4", "5"] {
4670 let source =
4671 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4672 let text = asm(&source);
4673 assert!(text.contains("xaddl\t"), "{order}: {text}");
4674 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4675 }
4676
4677 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4681 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4682 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4687 assert!(text.contains("movl\t$0, %eax"), "{text}");
4688 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4689 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4690 }
4691
4692 #[test]
4704 fn the_lock_free_questions_are_answered_as_constants() {
4705 for size in ["1", "2", "4", "8"] {
4706 let source =
4707 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4708 let text = asm(&source);
4709 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4710 assert!(!text.contains("call"), "and is not a call: {text}");
4711 }
4712 for size in ["3", "16", "sizeof(long double)"] {
4713 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4714 let text = asm(&source);
4715 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
4716 assert!(!text.contains("call"), "and is not a call either: {text}");
4717 }
4718
4719 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
4723 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
4724 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
4725 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
4726 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
4727 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
4728 }
4729
4730 #[test]
4742 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
4743 let mut opts = options();
4744 opts.emit = EmitKind::Ir;
4745
4746 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
4747 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
4748 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
4749
4750 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
4751 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
4752 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
4753
4754 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
4755 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
4756 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
4757 }
4758
4759 #[test]
4771 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
4772 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
4773 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
4774 assert!(text.contains("shrq"), "with the value halved first: {text}");
4775 assert!(text.contains("addsd"), "and doubled after: {text}");
4776 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4777
4778 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
4779 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
4780 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
4781 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
4782 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4783 }
4784
4785 #[test]
4796 fn a_plain_name_the_program_took_is_the_programs_own_function() {
4797 let taken = concat!(
4798 "static long long llabs(long long b) { return 7; }\n",
4799 "long long f(long long x) { return llabs(x); }\n",
4800 );
4801 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
4802
4803 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
4804 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
4805
4806 let plain = concat!(
4807 "long long llabs(long long b);\n",
4808 "long long f(long long x) { return llabs(x); }\n",
4809 );
4810 let mut opts = options();
4811 opts.emit = EmitKind::Ir;
4812 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
4813
4814 opts.builtins = false;
4815 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
4816
4817 opts.builtins = true;
4818 opts.no_builtin = vec!["llabs".to_owned()];
4819 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
4820 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
4821 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
4822
4823 opts.no_builtin = Vec::new();
4826 opts.builtins = false;
4827 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
4828 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
4829 }
4830
4831 #[test]
4844 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
4845 let text = ir(concat!(
4846 "long a = __builtin_expect(7, 1);\n",
4847 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
4848 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
4849 ));
4850 assert!(text.contains("global @a : i64 = 7,"), "{text}");
4851 assert!(text.contains("global @b : i64 = 9,"), "{text}");
4852 assert!(text.contains("global @c : i64 = 8,"), "{text}");
4853 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
4854
4855 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
4858 assert!(text.contains("sext"), "{text}");
4859
4860 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
4864 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
4865 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
4866 assert_eq!(body(source), one);
4867
4868 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
4873 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
4874 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
4875 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
4876 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
4877 }
4878
4879 #[test]
4891 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
4892 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
4893 let text = ir(promised);
4894 assert!(text.contains(" unreachable_hint\n"), "{text}");
4895 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
4896
4897 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
4901 assert!(after.contains("return"), "{after}");
4902
4903 let text = asm(promised);
4906 let mine = text.split_once("\nf:\n").expect("a definition").1;
4907 let mine = mine.split_once("\t.size").expect("a definition").0;
4908 let plain = asm("int f(int x) { if (x) return 1; }\n");
4909 let plain = plain.split_once("\nf:\n").expect("a definition").1;
4910 let plain = plain.split_once("\t.size").expect("a definition").0;
4911 assert_eq!(mine, plain);
4912 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
4915 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
4916 assert!(!mine.contains("ud2"), "{mine}");
4917 }
4918
4919 #[test]
4926 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
4927 let mut opts = options();
4928 opts.emit = EmitKind::Ir;
4929 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
4930 assert!(
4931 messages.iter().any(|m| m.contains("__builtin_abort")),
4932 "expected the written name in {messages:?}"
4933 );
4934 }
4935
4936 #[test]
4944 fn a_builtin_nothing_lowers_is_refused_by_name() {
4945 let mut opts = options();
4946 opts.emit = EmitKind::Ir;
4947 let builtin = "__atomic_signal_fence";
4948 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
4949 let messages = run(&opts, &source).messages;
4950 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
4951 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
4952 }
4953
4954 #[test]
4963 fn what_is_refused_is_the_call_and_not_the_name() {
4964 let text = ir(concat!(
4965 "void __atomic_signal_fence(int order) { (void)order; }\n",
4966 "void f(void) { __atomic_signal_fence(5); }\n",
4967 ));
4968 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
4969 }
4970
4971 #[test]
4980 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
4981 let text = ir(concat!(
4982 "struct S { char a[8]; int n; char b[12]; };\n",
4983 "char g[32];\n",
4984 "struct S gs;\n",
4985 "unsigned long whole = __builtin_object_size(g, 0);\n",
4986 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
4987 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
4988 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
4989 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
4990 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
4991 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
4992 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
4993 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
4994 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
4995 ));
4996 for (name, size) in [
4997 ("whole", 32),
4998 ("moved", 28),
4999 ("back", 4),
5000 ("outer", 24),
5001 ("inner", 8),
5002 ("scalar", 4),
5003 ("after", 16),
5004 ("into", 10),
5005 ("text", 6),
5006 ("dyn", 12),
5007 ] {
5008 let said = format!("global @{name} : i64 = {size},");
5009 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5010 }
5011 }
5012
5013 #[test]
5021 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5022 let text = body(concat!(
5023 "struct S { char a[8]; int n; char b[12]; };\n",
5024 "unsigned long f(void) {\n",
5025 " char loc[20];\n",
5026 " struct S ls;\n",
5027 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5028 "}\n",
5029 ));
5030 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5031 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5032 }
5033
5034 #[test]
5044 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5045 let text = ir(concat!(
5046 "struct T { int n; char f[]; };\n",
5047 "extern char *p;\n",
5048 "extern struct T *t;\n",
5049 "unsigned long largest = __builtin_object_size(p, 0);\n",
5050 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5051 "unsigned long least = __builtin_object_size(p, 2);\n",
5052 "unsigned long tight = __builtin_object_size(p, 3);\n",
5053 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5054 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5055 ));
5056 for name in ["largest", "nearest", "flex"] {
5057 let said = format!("global @{name} : i64 = -1,");
5061 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5062 }
5063 for name in ["least", "tight"] {
5064 let said = format!("global @{name} : i64 = 0,");
5065 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5066 }
5067 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5068 }
5069
5070 #[test]
5077 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5078 let text = body(concat!(
5079 "extern char *side(void);\n",
5080 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5081 ));
5082 assert!(!text.contains("call"), "nothing is called: {text}");
5083 }
5084
5085 #[test]
5090 fn a_kind_that_is_not_one_of_the_four_is_refused() {
5091 for source in [
5092 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
5093 + "{ return __builtin_object_size(p, k); }\n",
5094 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
5095 .to_owned(),
5096 "extern char *p;\nunsigned long f(void) ".to_owned()
5097 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
5098 ] {
5099 let messages = errors(&source);
5100 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
5101 assert!(named, "expected a complaint about the kind in {messages:?}");
5102 }
5103 }
5104
5105 #[test]
5111 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
5112 let text = ir(concat!(
5113 "void *buf[5];\n",
5114 "int f(void) {\n",
5115 " if (__builtin_setjmp(buf)) return 2;\n",
5116 " return 1;\n",
5117 "}\n",
5118 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5119 ));
5120 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5121 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5122 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5123 }
5124
5125 #[test]
5133 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5134 let text = ir(concat!(
5135 "void *buf[5];\n",
5136 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5137 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5138 ));
5139 let (saves, plain) = text.split_once("func @g").expect("both functions");
5140 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5141 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5142 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5143 }
5144
5145 #[test]
5154 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5155 let text =
5156 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5157 let body = text.split_once("\nf:\n").expect("the function").1;
5158 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5159 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5160 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5161 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5162 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5163 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5164 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5165 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5166 }
5167
5168 #[test]
5176 fn a_save_destroys_every_register_the_allocator_hands_out() {
5177 let text =
5178 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5179 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5180 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5181 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5182 }
5183 }
5184
5185 #[test]
5192 fn the_restore_puts_the_frame_back_before_it_jumps() {
5193 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5194 let mut opts = options();
5195 opts.emit = EmitKind::Asm;
5196 opts.opt_level = level;
5197 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5198 let result = run(&opts, source);
5199 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5200 let text = result.text().to_owned();
5201 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5202 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5203 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5204 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5205 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5206 }
5207 }
5208
5209 #[test]
5215 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5216 for source in [
5217 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5218 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5219 ] {
5220 let messages = errors(source);
5221 let named = messages.iter().any(|m| m.contains("E0710"));
5222 assert!(named, "expected a complaint about the value in {messages:?}");
5223 }
5224 }
5225
5226 #[test]
5231 fn a_static_function_nothing_refers_to_is_not_emitted() {
5232 let text = ir("static int dropped(void) { return 1; }\n\
5233 static int kept(void) { return 2; }\n\
5234 int main(void) { return kept(); }\n");
5235 assert!(text.contains("func @kept"), "{text}");
5236 assert!(!text.contains("dropped"), "{text}");
5237 }
5238
5239 #[test]
5245 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5246 let text = ir("static int ping(void);\n\
5247 static int pong(void) { return ping(); }\n\
5248 static int ping(void) { return pong(); }\n\
5249 int main(void) { return 0; }\n");
5250 assert!(!text.contains("ping"), "{text}");
5251 assert!(!text.contains("pong"), "{text}");
5252 }
5253
5254 #[test]
5260 fn naming_a_static_function_anywhere_keeps_it() {
5261 let text = ir("static int by_address(void) { return 1; }\n\
5262 static int in_an_image(void) { return 2; }\n\
5263 static int deeper(void) { return 3; }\n\
5264 static int reaches_deeper(void) { return deeper(); }\n\
5265 static int (*table[1])(void) = {in_an_image};\n\
5266 int main(void) {\n\
5267 int (*p)(void) = by_address;\n\
5268 return p() + table[0]() + reaches_deeper();\n\
5269 }\n");
5270 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5271 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5272 }
5273 }
5274
5275 #[test]
5281 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5282 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5283 let source = format!(
5284 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5285 int main(void) {{ return 0; }}\n"
5286 );
5287 let text = ir(&source);
5288 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5289 }
5290 }
5291
5292 #[test]
5295 fn a_function_anything_could_call_is_emitted_without_being_called() {
5296 let text =
5297 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5298 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5299 }
5300
5301 #[test]
5308 fn a_classification_c_has_an_operator_for_is_that_operator() {
5309 for (builtin, operator) in [
5310 ("__builtin_isgreater", "binary >"),
5311 ("__builtin_isgreaterequal", "binary >="),
5312 ("__builtin_isless", "binary <"),
5313 ("__builtin_islessequal", "binary <="),
5314 ] {
5315 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5316 let text = tast(&source);
5317 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5318 }
5319 }
5320
5321 #[test]
5330 fn the_classification_builtins_are_comparisons_and_not_calls() {
5331 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5332 assert_eq!(
5333 text,
5334 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5335 %2\n return %3\n"
5336 );
5337
5338 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5340 assert!(text.contains("fcmp one %0, %1"), "{text}");
5341
5342 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5343 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5344
5345 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5346 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5347 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5348 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5349 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5350 assert!(text.contains("%5 = or %3, %4"), "{text}");
5351
5352 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5355 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5356 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5357 assert!(text.contains("%5 = and %3, %4"), "{text}");
5358
5359 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5360 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5361 assert!(text.contains("icmp slt %1, %2"), "{text}");
5362
5363 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5366 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5367
5368 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5371 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5372 }
5373
5374 #[test]
5381 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5382 let text = ir(concat!(
5383 "int a = __builtin_isinff(1e300);\n",
5384 "int b = __builtin_isinf(1e300);\n",
5385 "int c = __builtin_isnan(0.0);\n",
5389 "int d = __builtin_signbit(-0.0);\n",
5390 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5391 ));
5392 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5393 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5394 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5395 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5396 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5397 }
5398
5399 #[test]
5401 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5402 let mut opts = options();
5403 opts.emit = EmitKind::Ir;
5404 let source = concat!(
5405 "int a(int x) { return __builtin_isnan(x); }\n",
5406 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5407 "int c(double x) { return __builtin_isnan(x, x); }\n",
5408 );
5409 let messages = run(&opts, source).messages;
5410 assert_eq!(
5411 messages,
5412 [
5413 "/main.c:1:23: error: non-floating-point argument in call to function \
5414 '__builtin_isnan' [E0685]",
5415 "/main.c:2:30: error: non-floating-point arguments in call to function \
5416 '__builtin_isunordered' [E0685]",
5417 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5418 ]
5419 );
5420 }
5421
5422 #[test]
5431 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5432 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5433 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5437 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5438 assert!(text.contains("%3 = and %1, %2"), "{text}");
5439 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5440 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5441 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5442 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5443 assert!(text.contains("%8 = and %6, %7"), "{text}");
5444
5445 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5449 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5450 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5451
5452 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5453 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5454 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5455 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5456
5457 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5458 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5459 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5460 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5464 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5465 assert!(!text.contains("call"), "{text}");
5466
5467 let text = body(concat!(
5470 "double g(void);\n",
5471 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5472 ));
5473 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5474 }
5475
5476 #[test]
5483 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5484 let text = ir(concat!(
5485 "int a = __builtin_isnormal(1.0);\n",
5486 "int b = __builtin_isnormal(0.0);\n",
5487 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5488 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5489 "int e = __builtin_isinf_sign(1.0);\n",
5490 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5491 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5492 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5493 ));
5494 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5495 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5496 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5497 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5498 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5499 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5500 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5501 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5502 }
5503
5504 #[test]
5510 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5511 let mut opts = options();
5512 opts.emit = EmitKind::Ir;
5513 let source = concat!(
5514 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5515 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5516 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5517 );
5518 let messages = run(&opts, source).messages;
5519 assert_eq!(
5520 messages,
5521 [
5522 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5523 '__builtin_fpclassify' [E0687]",
5524 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5525 [E0511]",
5526 "/main.c:3:23: error: non-floating-point argument in call to function \
5527 '__builtin_fpclassify' [E0685]",
5528 ]
5529 );
5530 }
5531
5532 #[test]
5540 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5541 let text = ir(concat!(
5542 "double a = __builtin_inf();\n",
5543 "float b = __builtin_huge_valf();\n",
5544 "long double c = __builtin_infl();\n",
5545 "double d = __builtin_huge_val();\n",
5546 ));
5547 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5548 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5549 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5550 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5551 assert!(!text.contains("call"), "{text}");
5552 }
5553
5554 #[test]
5563 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5564 let text = ir(concat!(
5565 "double a = __builtin_nan(\"\");\n",
5566 "double b = __builtin_nan(\"0x1\");\n",
5567 "double c = __builtin_nan(\"010\");\n",
5569 "double d = __builtin_nans(\"\");\n",
5570 "double e = __builtin_nans(\"0x1\");\n",
5571 "float f = __builtin_nanf(\"0x1\");\n",
5572 "float g = __builtin_nansf(\"\");\n",
5573 "long double h = __builtin_nansl(\"\");\n",
5574 ));
5575 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5576 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5577 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5578 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5579 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5580 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5581 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5582 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5583
5584 let text = ir(concat!(
5587 "double f(const char *p) { return __builtin_nan(p); }\n",
5588 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5589 ));
5590 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5591 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5592 }
5593
5594 #[test]
5602 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5603 let text = ir(concat!(
5604 "unsigned long a = __builtin_strlen(\"hello\");\n",
5605 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5606 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5607 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5608 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5609 ));
5610 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5611 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5612 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5613 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5614 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5615 assert!(!text.contains("call"), "{text}");
5616
5617 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5619 assert!(text.contains("call @strlen("), "{text}");
5620 }
5621
5622 #[test]
5629 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5630 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5631 assert!(text.contains("bitcast.i64 %0"), "{text}");
5632 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5633 assert!(text.contains("and %1, %2"), "{text}");
5634 assert!(text.contains("bitcast.f64 %3"), "{text}");
5635 assert!(!text.contains("call"), "{text}");
5636
5637 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5638 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5639 assert!(text.contains("%8 = or %4, %7"), "{text}");
5640 assert!(!text.contains("call"), "{text}");
5641
5642 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5645 assert!(text.contains("bitcast.i80 %0"), "{text}");
5646 assert!(text.contains("bitcast.f80"), "{text}");
5647
5648 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5651 assert!(text.contains("fpext.f64 %0"), "{text}");
5652 assert!(text.contains("bitcast.i64 %1"), "{text}");
5653 }
5654
5655 #[test]
5664 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5665 let text =
5666 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5667 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5668 assert!(!text.contains("call"), "{text}");
5669
5670 let text =
5671 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5672 assert!(text.contains("bitcast.i32 %0"), "{text}");
5673 assert!(!text.contains("call"), "{text}");
5674
5675 let text = body(concat!(
5676 "double copysign(double x, double y);\n",
5677 "double f(double x, double y) { return copysign(x, y); }\n",
5678 ));
5679 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5680 assert!(!text.contains("call"), "{text}");
5681
5682 let text = body(concat!(
5683 "float copysignf(float x, float y);\n",
5684 "float f(float x, float y) { return copysignf(x, y); }\n",
5685 ));
5686 assert!(!text.contains("call"), "{text}");
5687
5688 let text = ir(concat!(
5692 "long double fabsl(long double x);\n",
5693 "long double f(long double x) { return fabsl(x); }\n",
5694 ));
5695 assert!(text.contains("call @fabsl"), "{text}");
5696 }
5697
5698 #[test]
5706 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5707 let taken = concat!(
5708 "static double fabs(double b) { return 7; }\n",
5709 "double f(double x) { return fabs(x); }\n",
5710 );
5711 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5712
5713 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5714 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5715
5716 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
5717 let mut opts = options();
5718 opts.emit = EmitKind::Ir;
5719 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
5720
5721 opts.builtins = false;
5722 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
5723
5724 opts.builtins = true;
5725 opts.no_builtin = vec!["fabs".to_owned()];
5726 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
5727 let one = concat!(
5728 "double copysign(double a, double b);\n",
5729 "double f(double x) { return copysign(x, 1.0); }\n",
5730 );
5731 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
5732
5733 opts.no_builtin = Vec::new();
5735 opts.builtins = false;
5736 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
5737 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
5738 }
5739
5740 #[test]
5749 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
5750 let text = ir(concat!(
5751 "double a = __builtin_fabs(-3.5);\n",
5752 "double b = __builtin_copysign(1.0, -0.0);\n",
5753 "double c = __builtin_copysign(0.0, -2.0);\n",
5754 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
5756 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
5757 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
5758 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
5759 "long double i = __builtin_fabsl(-__builtin_infl());\n",
5760 ));
5761 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
5762 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
5763 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
5764 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
5765 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
5766 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
5767 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
5768 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5769 }
5770
5771 #[test]
5779 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
5780 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
5781 assert!(!text.contains("call"), "{text}");
5782 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
5783 assert!(!text.contains("call"), "{text}");
5784
5785 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
5788 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5789 assert!(!text.contains("call"), "{text}");
5790 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
5791 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
5792
5793 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
5796 assert_eq!(written, text, "the name and the operator are the same thing");
5797
5798 let text = body(concat!(
5800 "double creal(_Complex double z);\n",
5801 "double f(_Complex double z) { return creal(z); }\n",
5802 ));
5803 assert!(!text.contains("call"), "{text}");
5804 let text = body(concat!(
5805 "_Complex float conjf(_Complex float z);\n",
5806 "_Complex float f(_Complex float z) { return conjf(z); }\n",
5807 ));
5808 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5809 assert!(!text.contains("call"), "{text}");
5810
5811 let taken = concat!(
5814 "static double creal(_Complex double z) { return 7; }\n",
5815 "double f(_Complex double z) { return creal(z); }\n",
5816 );
5817 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
5818 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
5819 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
5820 let plain = concat!(
5821 "double cimag(_Complex double z);\n",
5822 "double f(_Complex double z) { return cimag(z); }\n",
5823 );
5824 let mut opts = options();
5825 opts.emit = EmitKind::Ir;
5826 opts.builtins = false;
5827 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
5828 opts.builtins = true;
5829 opts.no_builtin = vec!["cimag".to_owned()];
5830 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
5831
5832 let text = ir(concat!(
5834 "double a = __builtin_creal(1.5 + 2.5i);\n",
5835 "double b = __builtin_cimag(1.5 + 2.5i);\n",
5836 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
5837 ));
5838 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
5839 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
5840 assert!(
5841 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
5842 "the conjugate of a constant is the constant with the second half negated: {text}"
5843 );
5844 assert!(!text.contains("call"), "{text}");
5845 }
5846
5847 #[test]
5855 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
5856 let text = ir(concat!(
5857 "double a = __builtin_ceil(1.5);\n",
5858 "double b = __builtin_floor(1.5);\n",
5859 "double c = __builtin_trunc(-1.5);\n",
5860 "double d = __builtin_round(2.5);\n",
5863 "double e = __builtin_ceil(-0.5);\n",
5865 "double f = __builtin_fmax(1.0, 2.0);\n",
5866 "double g = __builtin_fmin(1.0, 2.0);\n",
5867 "float h = __builtin_ceilf(1.25f);\n",
5868 "double ceil(double x);\n",
5871 "double i = ceil(2.25);\n",
5872 ));
5873 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
5874 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
5875 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
5876 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
5877 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
5878 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
5879 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
5880 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
5881 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
5882 assert!(!text.contains("call"), "{text}");
5883 }
5884
5885 #[test]
5893 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
5894 let text = ir(concat!(
5895 "double f(double x) { return __builtin_ceil(x); }\n",
5896 "float g(float x) { return __builtin_floorf(x); }\n",
5897 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
5898 ));
5899 assert!(text.contains("call @ceil("), "{text}");
5900 assert!(text.contains("call @floorf("), "{text}");
5901 assert!(text.contains("call @fmax("), "{text}");
5902
5903 let text = ir(concat!(
5907 "double f(void) { return __builtin_rint(2.5); }\n",
5908 "double g(void) { return __builtin_nearbyint(2.5); }\n",
5909 ));
5910 assert!(text.contains("call @rint("), "{text}");
5911 assert!(text.contains("call @nearbyint("), "{text}");
5912
5913 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
5916 assert!(text.contains("call @fmin("), "{text}");
5917
5918 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
5921 let mut opts = options();
5922 opts.emit = EmitKind::Ir;
5923 opts.no_builtin = vec!["ceil".to_owned()];
5924 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
5925 }
5926
5927 #[test]
5934 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
5935 let text = ir(concat!(
5936 "constexpr int side = 4;\n",
5937 "constexpr int wider = side + 1;\n",
5938 "constexpr double half = 1.5;\n",
5939 "struct point { int x; int y; };\n",
5940 "constexpr struct point origin = { 5, 6 };\n",
5941 "int square[side * side];\n",
5942 "int rectangle[wider];\n",
5943 "int rounded[(int)half * 2];\n",
5944 "int across[origin.y];\n",
5945 "enum named { four = side };\n",
5946 "int e = four;\n",
5947 ));
5948 assert!(text.contains("global @square : bytes 64 ="), "{text}");
5949 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
5950 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
5951 assert!(text.contains("global @across : bytes 24 ="), "{text}");
5952 assert!(text.contains("global @e : i32 = 4,"), "{text}");
5953
5954 let mut opts = options();
5957 opts.emit = EmitKind::Ir;
5958 let konst = "const int n = 1;\nint a[n];\n";
5959 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
5960 assert_eq!(run(&opts, konst).messages, [message]);
5961
5962 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
5964 assert_eq!(run(&opts, subscript).messages, [message]);
5965
5966 let address = "constexpr int c = 3;\nint *p = &c;\n";
5968 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
5969 pointer target type [E0514]";
5970 assert_eq!(run(&opts, address).messages, [warning]);
5971 }
5972
5973 #[test]
5982 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
5983 let mut opts = options();
5986 opts.std = Std::C17;
5987 let source = concat!(
5988 "int add(a, b)\n",
5989 "int a;\n",
5990 "int b;\n",
5991 "{ return a + b; }\n",
5992 "int promoted(c)\n",
5993 "char c;\n",
5994 "{ return c; }\n",
5995 "int narrow(char);\n",
5996 "int narrow(c)\n",
5997 "char c;\n",
5998 "{ return c; }\n",
5999 "int first(a)\n",
6000 "int a[4];\n",
6001 "{ return a[0]; }\n",
6002 );
6003 let result = run(&opts, source);
6004 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6005 let text = result.text();
6006 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6007 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6008 assert!(text.contains("c : char object automatic defined"), "{text}");
6010 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
6011 assert!(text.contains("first : int(int *) function external defined"), "{text}");
6013 }
6014
6015 #[test]
6022 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
6023 let mut opts = options();
6024 opts.std = Std::C17;
6025 for (source, message) in [
6026 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
6027 (
6028 "int f(a)\nint a;\nint b;\n{ return a; }\n",
6029 "3:5: error: declaration for parameter 'b' but no such parameter",
6030 ),
6031 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
6032 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
6033 (
6034 "int f(a)\nstatic int a;\n{ return a; }\n",
6035 "2:12: error: storage class specified for parameter 'a'",
6036 ),
6037 (
6038 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
6039 "2:7: error: argument 'a' doesn't match prototype",
6040 ),
6041 ] {
6042 let result = run(&opts, source);
6043 assert!(result.failed(), "expected this to fail:\n{source}");
6044 assert!(result.messages[0].contains(message), "{:?}", result.messages);
6045 }
6046
6047 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
6050 let mut older = options();
6051 older.std = Std::C89;
6052 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
6053 let result = run(&opts, implicit);
6054 assert!(
6055 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
6056 "{:?}",
6057 result.messages
6058 );
6059
6060 let mut newer = options();
6064 newer.std = Std::C23;
6065 let plain = "int f(a)\nint a;\n{ return a; }\n";
6066 let result = run(&newer, plain);
6067 assert!(!result.failed(), "{:?}", result.messages);
6068 assert_eq!(
6069 result.messages,
6070 ["/main.c:1:5: warning: old-style function definition [E0412]"]
6071 );
6072 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
6073 }
6074
6075 #[test]
6082 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
6083 let array = "int a[8] = { [3] 7 };\n";
6084 let member = "struct s { int x; } v = { x: 7 };\n";
6085 for source in [array, member] {
6086 let result = run(&options(), source);
6087 assert!(!result.failed(), "{:?}", result.messages);
6088 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
6089 }
6090
6091 let mut asked = options();
6092 asked.pedantic = true;
6093 assert_eq!(
6094 run(&asked, array).messages,
6095 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
6096 );
6097 assert_eq!(
6098 run(&asked, member).messages,
6099 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
6100 );
6101 }
6102
6103 #[test]
6110 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
6111 let text = ir(concat!(
6112 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
6113 "struct brim { char buf[9223372036854775807L]; };\n",
6114 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
6115 "unsigned long h = sizeof(struct huge_struct);\n",
6116 "unsigned long b = sizeof(struct brim);\n",
6117 "unsigned long y = sizeof(struct bitty);\n",
6118 ));
6119 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6120 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6121 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6122
6123 let mut opts = options();
6124 opts.emit = EmitKind::Ir;
6125 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6126 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6127 assert_eq!(run(&opts, over).messages, [message]);
6128 let array = "struct wide { short buf[1L << 62]; };\n";
6129 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6130 maximum object size '9223372036854775807' [E0537]";
6131 assert_eq!(run(&opts, array).messages[0], message);
6132 }
6133
6134 fn compile_bytes(source: &[u8]) -> Compiled {
6139 let mut opts = options();
6140 opts.emit = EmitKind::Ir;
6141 let mut fs = MemoryFileSystem::new();
6142 fs.insert("/main.c", source.to_vec());
6143 compile(&opts, "/main.c", &fs)
6144 }
6145
6146 #[test]
6153 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6154 let mut source = b"char s[] = \"a".to_vec();
6155 source.push(0xff);
6156 source.extend_from_slice(b"b\";\nchar c = '");
6157 source.push(0xff);
6158 source.extend_from_slice(b"';\n");
6159 let result = compile_bytes(&source);
6160 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6161 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6162 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6164
6165 let mut stray = b"int a".to_vec();
6166 stray.push(0xff);
6167 stray.extend_from_slice(b" = 1;\n");
6168 let result = compile_bytes(&stray);
6169 assert!(
6170 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6171 "{:?}",
6172 result.messages
6173 );
6174 }
6175
6176 #[test]
6177 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6178 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6179 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6180 let expected = "\
6181func @add(i32, i32) -> i32, linkage(external) {
6182block0(%0: i32, %1: i32):
6183 %2 = add.nsw %0, %1
6184 return %2
6185}
6186";
6187 assert!(text.contains(expected), "{text}");
6188 }
6189
6190 #[test]
6191 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6192 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6193 assert!(!text.contains("alloca"), "{text}");
6194 assert!(!text.contains("load"), "{text}");
6195 assert!(!text.contains("store"), "{text}");
6196 }
6197
6198 #[test]
6199 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6200 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6201 let expected = "\
6202block0:
6203 %0 = alloca, size 4, align 4
6204 %1 = iconst.i32 1
6205 store %1 -> %0, align 4, tbaa !1
6206 %2 = call @g(%0) : (ptr) -> i32
6207 return %2
6208";
6209 assert_eq!(text, expected);
6210 }
6211
6212 #[test]
6213 fn a_loop_carries_what_it_changes_as_block_parameters() {
6214 let text = body(
6217 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6218 return total;\n}\n",
6219 );
6220 assert!(!text.contains("alloca"), "{text}");
6221 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6222 assert!(text.contains("jump block1("), "{text}");
6223 }
6224
6225 #[test]
6226 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6227 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6228 assert!(text.contains("icmp slt %0, %1"), "{text}");
6229 assert!(!text.contains("zext"), "{text}");
6230 }
6231
6232 #[test]
6233 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6234 let text = body("int f(int a, int b) { return a && b; }\n");
6235 let expected = "\
6236block0(%0: i32, %1: i32):
6237 %2 = iconst.i32 0
6238 %3 = icmp ne %0, %2
6239 %4 = iconst.i1 0
6240 br_if %3, block1, block2(%4)
6241
6242block1:
6243 %5 = iconst.i32 0
6244 %6 = icmp ne %1, %5
6245 jump block2(%6)
6246
6247block2(%7: i1):
6248 %8 = zext.i32 %7
6249 return %8
6250";
6251 assert_eq!(text, expected);
6252 }
6253
6254 #[test]
6255 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6256 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6257 assert!(!text.contains("block3"), "{text}");
6260 assert!(!text.contains("iconst.i32 3"), "{text}");
6261 }
6262
6263 #[test]
6264 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6265 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6266 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6267 assert!(body("int f(void) { }\n").contains("unreachable"));
6268 }
6269
6270 #[test]
6271 fn a_structure_is_copied_rather_than_held_in_a_value() {
6272 let text = body(
6273 "struct point { int x, y; };\n\
6274 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6275 );
6276 assert!(text.contains("memcpy"), "{text}");
6277 }
6278
6279 #[test]
6280 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6281 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6282 assert!(text.contains("memset"), "{text}");
6283 }
6284
6285 #[test]
6286 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6287 let text = body(
6288 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6289 default: r = 4; } return r; }\n",
6290 );
6291 let expected = "\
6292block0(%0: i32):
6293 %1 = iconst.i32 0
6294 switch %0, block1, [1 => block2, 2 => block3(%1)]
6295
6296block1:
6297 %2 = iconst.i32 4
6298 jump block4(%2)
6299
6300block2:
6301 %3 = iconst.i32 1
6302 jump block3(%3)
6303
6304block3(%4: i32):
6305 %5 = iconst.i32 2
6306 %6 = add.nsw %4, %5
6307 jump block4(%6)
6308
6309block4(%7: i32):
6310 return %7
6311";
6312 assert_eq!(text, expected);
6313 }
6314
6315 #[test]
6316 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6317 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6320 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6321 assert!(text.contains("icmp ule"), "{text}");
6322 assert!(!text.contains("switch"), "{text}");
6323 }
6324
6325 #[test]
6326 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6327 let text = body(
6328 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6329 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6330 );
6331 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6334 assert!(text.contains("block5:\n jump block7("), "{text}");
6335 assert!(text.contains("block6:\n jump block8("), "{text}");
6336 }
6337
6338 #[test]
6339 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6340 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6341 }
6342
6343 #[test]
6344 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6345 let text = body(
6350 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6351 return n; }\n",
6352 );
6353 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6356 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6357 assert!(text.contains("block4:\n jump block3("), "{text}");
6358 }
6359
6360 #[test]
6361 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6362 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6365 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6366 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6367 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6368 }
6369
6370 #[test]
6371 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6372 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6373 assert!(!text.contains("alloca"), "{text}");
6377 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6378 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6379 }
6380
6381 #[test]
6382 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6383 let text =
6384 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6385 assert!(!text.contains("alloca"), "{text}");
6386 assert!(text.contains("block1(%2: i32):"), "{text}");
6387 assert!(text.contains("jump block1(%5)"), "{text}");
6388 }
6389
6390 #[test]
6391 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6392 assert_eq!(
6395 body("int f(int x) { return x; spare: return 0; }\n"),
6396 "block0(%0: i32):\n return %0\n"
6397 );
6398 }
6399
6400 #[test]
6401 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6402 let text = body(
6403 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6404 );
6405 assert_eq!(
6408 text,
6409 "\
6410block0(%0: ptr):
6411 %1 = load.i8 %0, align 1
6412 %2 = iconst.i8 3
6413 %3 = ashr %1, %2
6414 %4 = sext.i32 %3
6415 return %4
6416"
6417 );
6418 }
6419
6420 #[test]
6421 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6422 let text =
6426 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6427 assert_eq!(
6428 text,
6429 "\
6430block0(%0: ptr, %1: i32):
6431 %2 = iconst.i32 16777215
6432 %3 = and %1, %2
6433 %4 = trunc.i16 %3
6434 store %4 -> %0, align 2
6435 %5 = iconst.i32 16
6436 %6 = lshr %3, %5
6437 %7 = trunc.i8 %6
6438 %8 = iconst.i64 2
6439 %9 = ptr_add %0, %8
6440 store %7 -> %9, align 1
6441 return
6442"
6443 );
6444 }
6445
6446 #[test]
6447 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6448 let text =
6449 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6450 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6453 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6454 }
6455
6456 #[test]
6457 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6458 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6461 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6462 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6463 }
6464
6465 #[test]
6466 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6467 let text = body(
6471 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6472 );
6473 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6474 }
6475
6476 #[test]
6477 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6478 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6481 assert!(
6482 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6483 "{text}"
6484 );
6485 }
6486
6487 #[test]
6488 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6489 let text = ir(concat!(
6494 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6495 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6496 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6497 "char s[2] = \"hi\";\n",
6498 ));
6499 assert!(
6500 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6501 "{text}"
6502 );
6503 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6504 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6505 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6508 }
6509
6510 #[test]
6511 fn a_definition_takes_a_parameter_it_left_unnamed() {
6512 let text = ir("int f(int a, int) { return a; }\n");
6516 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6517 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6518
6519 let text = ir("int g(int, int n) { return n; }\n");
6522 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6523 }
6524
6525 #[test]
6526 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6527 let text = body(concat!(
6532 "struct s { int f; int g; };\n",
6533 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6534 "{ *d = *e = a[0] = *c; }\n",
6535 ));
6536 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6537 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6538 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6539 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6540 }
6541
6542 #[test]
6543 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6544 let mut opts = options();
6549 opts.emit = EmitKind::Ir;
6550 let result = run(
6551 &opts,
6552 concat!(
6553 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6554 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6555 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6556 "const union u c = { { \"1234\", \"567\" } };\n",
6557 ),
6558 );
6559 let text = result.text();
6560 assert_eq!(
6561 result.messages,
6562 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6563 (5 chars into 3 available) [E0637]"]
6564 );
6565 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6566 assert!(
6567 text.contains(
6568 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6569 bytes \"9\\00\", zero 3 }"
6570 ),
6571 "{text}"
6572 );
6573 assert!(
6576 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6577 "{text}"
6578 );
6579 }
6580
6581 #[test]
6582 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6583 let text = body(concat!(
6587 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6588 "void g(struct v *);\n",
6589 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6590 ));
6591 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6592 }
6593
6594 #[test]
6595 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6596 let text = ir(concat!(
6601 "struct s { int x; };\n",
6602 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6603 "int n = (int){ 7 };\n",
6604 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6605 ));
6606 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6607 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6608 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6611 }
6612
6613 #[test]
6614 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6615 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6619 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6620 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6621 }
6622
6623 #[test]
6624 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6625 let text = ir("unsigned char foo[1][0];\n");
6629 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6630 }
6631
6632 #[test]
6633 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
6634 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
6637 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
6638 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
6639 }
6640
6641 #[test]
6642 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
6643 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
6647 assert!(
6648 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
6649 "{text}"
6650 );
6651 }
6652
6653 #[test]
6654 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
6655 let text = body(
6660 "\
6661struct s { int a, b; };
6662struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
6663",
6664 );
6665 assert!(text.contains("block3(%7: ptr)"), "{text}");
6667 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
6668 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
6669 }
6670
6671 #[test]
6679 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
6680 let text = body("int f(int i) { return ++i ?: 10; }\n");
6681 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
6682 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6683
6684 let text = body("long f(int i) { return ++i ?: 10L; }\n");
6687 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
6688 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6689
6690 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
6692 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
6693
6694 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
6697 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
6698 }
6699
6700 #[test]
6701 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
6702 let text = ir("\
6706struct pair { int a, b; };
6707struct pair make(int a, int b);
6708struct pair twice(struct pair p) { return make(p.a, p.b); }
6709");
6710 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
6711 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
6712 }
6713
6714 #[test]
6715 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
6716 let text = ir("\
6720struct big { double v[8]; };
6721struct big grow(struct big b);
6722struct big twice(struct big b) { return grow(grow(b)); }
6723");
6724 assert!(
6725 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
6726 "{text}"
6727 );
6728 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
6729 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
6732 }
6733
6734 #[test]
6735 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
6736 let text = ir("\
6741struct big { double v[8]; };
6742struct pair { int a, b; };
6743int p(const char *, ...);
6744int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
6745");
6746 assert!(
6747 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
6748 "{text}"
6749 );
6750 }
6751
6752 #[test]
6753 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
6754 let body = body(
6757 "\
6758struct pair { int a, b; };
6759struct pair make(int a, int b);
6760int second(void) { return make(1, 2).b; }
6761",
6762 );
6763 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
6764 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
6765 }
6766
6767 #[test]
6768 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
6769 let source = "\
6773struct hfa { float x, y, z; };
6774int take(struct hfa h);
6775int give(struct hfa h) { return take(h); }
6776";
6777 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
6778 let mut opts = options();
6779 opts.emit = EmitKind::Ir;
6780 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
6781 let result = run(&opts, source);
6782 assert_eq!(result.messages, Vec::<String>::new());
6783 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
6784 }
6785
6786 #[test]
6787 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
6788 let source = "\
6791int use(int *);
6792void f(int n) {
6793 {
6794 int a[n];
6795 use(a);
6796 }
6797 use(0);
6798}
6799";
6800 let body = body(source);
6801 assert!(body.contains("mul.nsw"), "{body}");
6802 assert!(body.contains("stacksave"), "{body}");
6803 assert!(body.contains("alloca %"), "{body}");
6804 assert!(body.contains("stackrestore"), "{body}");
6805 }
6806
6807 #[test]
6808 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
6809 let source = "\
6814int use(int *);
6815int f(int n) {
6816 {
6817 int a[n];
6818 if (use(a)) goto out;
6819 use(0);
6820 }
6821out:
6822 return 0;
6823}
6824";
6825 let body = body(source);
6826 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
6828 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6829 assert!(after.starts_with(" %4\n jump block"), "{body}");
6830 }
6831
6832 #[test]
6833 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
6834 let source = "\
6838int use(int *);
6839int f(int n) {
6840 int a[n];
6841again:
6842 if (use(a)) goto again;
6843 return 0;
6844}
6845";
6846 let body = body(source);
6847 assert!(body.contains("stacksave"), "{body}");
6848 assert!(!body.contains("stackrestore"), "{body}");
6849 }
6850
6851 #[test]
6852 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
6853 let source = "\
6858int use(int *);
6859int f(int n) {
6860again:
6861 {
6862 int a[n];
6863 if (use(a)) goto again;
6864 }
6865 return 0;
6866}
6867";
6868 let body = body(source);
6869 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6870 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6871 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
6872 }
6873
6874 #[test]
6875 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
6876 let source = "\
6882int f(void);
6883void t(void) {
6884 int count = 10;
6885 for (; count--;) {
6886 int b[f()];
6887 int i;
6888 for (i = 0; i < f(); i++) {
6889 b[i] = count;
6890 }
6891 }
6892}
6893";
6894 let body = body(source);
6895 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6899 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6900 let next = after.split("\n\n").next().expect("the block the restore is in");
6903 assert!(next.contains("jump block1("), "{body}");
6904 }
6905
6906 #[test]
6907 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
6908 let source = "\
6911unsigned long f(int n) {
6912 int a[n];
6913 n = 0;
6914 return sizeof a;
6915}
6916";
6917 let body = body(source);
6918 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
6920 }
6921
6922 #[test]
6923 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
6924 let source = "\
6927int use(int);
6928int f(int x) {
6929 return ({
6930 int t = use(x);
6931 t * t;
6932 });
6933}
6934";
6935 let expected = "\
6936block0(%0: i32):
6937 %1 = call @use(%0) : (i32) -> i32
6938 %2 = mul.nsw %1, %1
6939 return %2
6940";
6941 assert_eq!(body(source), expected);
6942 }
6943
6944 #[test]
6945 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
6946 let source = "int f(int x) { return ({ return x; 0; }); }\n";
6950 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
6951 }
6952
6953 #[test]
6954 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
6955 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
6959 let expected = "\
6960block0(%0: ptr):
6961 %1 = va_arg.f64 %0
6962 %2 = va_arg.f64 %0
6963 %3 = fadd %1, %2
6964 return %3
6965";
6966 assert_eq!(body(source), expected);
6967 }
6968
6969 #[test]
6970 fn one_that_reads_a_structure_answers_where_the_object_is() {
6971 let source = "\
6985struct s { int a; long b; };
6986long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
6987";
6988 let expected = "\
6989block0(%0: ptr):
6990 %1 = alloca, size 16, align 16
6991 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
6992 memcpy %1, %2, size 16, align 8
6993 %3 = iconst.i64 8
6994 %4 = ptr_add %1, %3
6995 %5 = load.i64 %4, align 8, tbaa !1
6996 return %5
6997";
6998 assert_eq!(body(source), expected);
6999 }
7000
7001 #[test]
7005 fn the_classification_says_which_registers_the_object_arrived_in() {
7006 let source = "\
7007struct s { double a; double b; };
7008double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
7009";
7010 assert!(
7011 body(source)
7012 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
7013 "{}",
7014 body(source)
7015 );
7016
7017 let big = "\
7018struct s { long a[4]; };
7019long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
7020";
7021 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
7022 }
7023
7024 #[test]
7025 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
7026 let source = "\
7030int f(int c) {
7031 void *p = c ? &&one : &&two;
7032 goto *p;
7033one:
7034 return 1;
7035two:
7036 return 2;
7037}
7038";
7039 let expected = "\
7040block0(%0: i32):
7041 %1 = iconst.i32 0
7042 %2 = icmp ne %0, %1
7043 br_if %2, block1, block2
7044
7045block1:
7046 %3 = block_addr block3
7047 jump block4(%3)
7048
7049block2:
7050 %4 = block_addr block5
7051 jump block4(%4)
7052
7053block3:
7054 %5 = iconst.i32 1
7055 return %5
7056
7057block4(%6: ptr):
7058 indirect_br %6, block3, block5
7059
7060block5:
7061 %7 = iconst.i32 2
7062 return %7
7063";
7064 assert_eq!(body(source), expected);
7065 }
7066
7067 #[test]
7068 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
7069 let source = "void **next(void);
7072void f(void) { goto *next(); }
7073";
7074 let expected = "\
7075block0:
7076 %0 = call @next() : () -> ptr
7077 unreachable
7078";
7079 assert_eq!(body(source), expected);
7080 }
7081
7082 #[test]
7083 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
7084 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
7087 let expected = "\
7088block0:
7089 inline_asm.volatile \"mfence\", \"\", \"memory\"()
7090 return
7091";
7092 assert_eq!(body(source), expected);
7093 }
7094
7095 #[test]
7096 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
7097 let source = "\
7100int f(int x, int y) {
7101 int r;
7102 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
7103 return r + y;
7104}
7105";
7106 let expected = "\
7107block0(%0: i32, %1: i32):
7108 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
7109 %4 = add.nsw %2, %3
7110 return %4
7111";
7112 assert_eq!(body(source), expected);
7113 }
7114
7115 #[test]
7116 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
7117 let source = "\
7122struct pair { int a, b; };
7123int f(int x) {
7124 int slot = x;
7125 struct pair p = { x, x };
7126 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7127 return slot + p.a;
7128}
7129";
7130 let text = body(source);
7131 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7132 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7133 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7134 }
7135
7136 #[test]
7137 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7138 let source = "\
7143int f(int x) {
7144 int r = 7;
7145 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7146 return r;
7147away:
7148 return r;
7149}
7150";
7151 let expected = "\
7152block0(%0: i32):
7153 %1 = iconst.i32 7
7154 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7155
7156block1:
7157 return %2
7158
7159block2:
7160 return %1
7161";
7162 assert_eq!(body(source), expected);
7163 }
7164
7165 #[test]
7166 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7167 let mut opts = options();
7171 opts.emit = EmitKind::Ir;
7172 for (source, expected) in [
7173 (
7174 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7175 "output operand constraint lacks '='",
7176 ),
7177 (
7178 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7179 "lvalue required in 'asm' statement",
7180 ),
7181 (
7182 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7183 "read-only variable 'g' used as 'asm' output",
7184 ),
7185 (
7186 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7187 "input operand constraint contains '='",
7188 ),
7189 (
7190 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7191 "memory input 0 is not directly addressable",
7192 ),
7193 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7194 (
7195 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7196 "duplicate asm operand name 'a'",
7197 ),
7198 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7199 ] {
7200 let result = run(&opts, source);
7201 assert!(result.failed(), "expected this to be reported:\n{source}");
7202 assert!(
7203 result.messages.iter().any(|m| m.contains(expected)),
7204 "{expected}\n{:?}",
7205 result.messages
7206 );
7207 }
7208 }
7209
7210 #[test]
7215 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7216 let text = ir(concat!(
7217 "__asm__(\n",
7218 " \".section .rodata\\n\"\n",
7219 " \".globl first\\n\"\n",
7220 " \".balign 8\\n\"\n",
7221 " \"first:\\n\"\n",
7222 " \".long 1\\n\"\n",
7223 " \".long 2\\n\"\n",
7224 " \".globl last\\n\"\n",
7225 " \"last:\\n\"\n",
7226 " \".quad last - first\\n\");\n",
7227 "extern const int first[];\n",
7228 "extern const long last;\n",
7229 ));
7230 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7231 assert!(text.contains("global @last : i64 = 8"), "{text}");
7232 }
7233
7234 #[test]
7238 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7239 let text = ir(concat!(
7240 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7241 "extern int counter;\n",
7242 "int read(void) { return counter; }\n",
7243 ));
7244 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7245 }
7246
7247 #[test]
7250 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7251 let mut opts = options();
7252 opts.emit = EmitKind::Ir;
7253 let mut fs = MemoryFileSystem::new();
7254 fs.insert(
7255 "/main.c",
7256 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7257 );
7258 fs.insert("seed", b"hi".to_vec());
7259 let result = compile(&opts, "/main.c", &fs);
7260 assert_eq!(result.messages, Vec::<String>::new());
7261 let text = result.text();
7262 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7263 }
7264
7265 #[test]
7268 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7269 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7270 assert!(
7271 messages
7272 .iter()
7273 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7274 "{messages:?}"
7275 );
7276 }
7277
7278 #[test]
7281 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7282 for source in [
7283 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7284 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7285 ] {
7286 let messages = errors(source);
7287 assert!(
7288 messages
7289 .iter()
7290 .any(|m| m.contains("not supported yet")
7291 && m.contains("in an `asm` at file scope")),
7292 "{source}\n{messages:?}"
7293 );
7294 }
7295 }
7296
7297 #[test]
7298 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7299 let mut opts = options();
7300 opts.emit = EmitKind::Ir;
7301 for source in [
7302 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7303 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7304 ] {
7305 let result = run(&opts, source);
7306 assert!(result.failed(), "expected this to be reported:\n{source}");
7307 assert!(
7308 result.messages.iter().any(|m| m.contains("not supported yet")),
7309 "{:?}",
7310 result.messages
7311 );
7312 }
7313 }
7314
7315 fn round_trip(source: &str) -> (String, String) {
7317 let printed = ir(source);
7318 let mut opts = options();
7319 opts.emit = EmitKind::Ir;
7320 let mut fs = MemoryFileSystem::new();
7321 fs.insert("/main.ir", printed.clone().into_bytes());
7322 let result = compile_ir(&opts, "/main.ir", &fs);
7323 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7324 (printed, result.text().to_owned())
7325 }
7326
7327 #[test]
7328 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7329 let (printed, again) = round_trip(
7333 "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",
7334 );
7335 assert_eq!(printed, again);
7336 }
7337
7338 #[test]
7339 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7340 let mut opts = options();
7341 opts.emit = EmitKind::Ir;
7342 let mut fs = MemoryFileSystem::new();
7343 let text = "\
7344; ModuleID = 'a.c'
7345; format 0
7346target triple = \"x86_64-unknown-linux-gnu\"
7347target datalayout = \"e-p:64:64-i64:64-S128\"
7348
7349func @f(), linkage(external) {
7350block0:
7351 frobnicate
7352}
7353";
7354 fs.insert("/main.ir", text.as_bytes().to_vec());
7355 let result = compile_ir(&opts, "/main.ir", &fs);
7356 assert!(result.failed());
7357 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7358 }
7359
7360 #[test]
7361 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7362 let mut opts = options();
7365 opts.emit = EmitKind::Ir;
7366 let mut fs = MemoryFileSystem::new();
7367 let text = "\
7368; ModuleID = 'a.c'
7369; format 0
7370target triple = \"x86_64-unknown-linux-gnu\"
7371target datalayout = \"e-p:64:64-i64:64-S128\"
7372
7373func @f(), linkage(external) {
7374block0:
7375 %0 = iconst.i32 1
7376 return %0
7377}
7378";
7379 fs.insert("/main.ir", text.as_bytes().to_vec());
7380 let result = compile_ir(&opts, "/main.ir", &fs);
7381 assert!(result.failed());
7382 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7383 }
7384
7385 #[test]
7386 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7387 let mut fs = MemoryFileSystem::new();
7389 fs.insert("/main.ir", Vec::new());
7390 let result = compile_ir(&options(), "/main.ir", &fs);
7391 assert!(result.failed());
7392 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7393 }
7394
7395 #[test]
7396 fn the_printed_ir_reads_back_as_the_same_module() {
7397 let text = ir("\
7400struct point { int x, y; };
7401static const char greeting[] = \"hi\";
7402int table[4] = { 1, 2, 3 };
7403int puts(const char *);
7404double half(double x) { return x / 2.0; }
7405int f(int n) {
7406 int total = 0;
7407 for (int i = 0; i < n; i++) {
7408 if (i == 3) continue;
7409 total += table[i];
7410 }
7411 switch (n) {
7412 case 0: total = 1;
7413 case 1: total++; break;
7414 default: total = -total;
7415 }
7416 struct point p = { total, 1 };
7417 int *q = &p.y;
7418 puts(greeting);
7419 return p.x + *q;
7420}
7421int dispatch(int c) {
7422 void *p = c ? &&one : &&two;
7423 goto *p;
7424one:
7425 return 1;
7426two:
7427 return 2;
7428}
7429int assembly(int x, int *p) {
7430 int r;
7431 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7432 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7433 return r;
7434away:
7435 return 0;
7436}
7437");
7438 let mut names = Interner::new();
7439 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7440 assert_eq!(rucc_ir::print(&module, &names), text);
7441 }
7442
7443 #[test]
7444 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7445 let mut opts = options();
7449 opts.emit = EmitKind::Object;
7450 opts.save_temps = rucc_session::SaveTemps::Object;
7451 let result = run(&opts, "#define N 2\nint a[N];\n");
7452 assert_eq!(result.messages, Vec::<String>::new());
7453 let text = result.temps.preprocessed.expect("the preprocessed text");
7454 assert!(text.contains("int a[2];"), "{text}");
7455 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7456 let asm = result.temps.assembly.expect("the assembly");
7457 assert!(asm.contains("a:"), "{asm}");
7458 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7459 }
7460
7461 #[test]
7462 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7463 let mut opts = options();
7466 opts.emit = EmitKind::Object;
7467 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7468 }
7469
7470 #[test]
7471 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7472 let mut opts = options();
7475 opts.emit = EmitKind::Ir;
7476 opts.save_temps = rucc_session::SaveTemps::Cwd;
7477 let result = run(&opts, "int a;\n");
7478 assert!(result.temps.preprocessed.is_some());
7479 assert_eq!(result.temps.assembly, None);
7480 }
7481}