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]
2511 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
2512 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
2513 assert!(text.contains("movl\t$2, %eax"), "{text}");
2514 assert!(!text.contains("cvttsd2si"), "{text}");
2515 }
2516
2517 #[test]
2525 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
2526 let text =
2527 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
2528 assert!(text.contains("movl\t$30, %eax"), "{text}");
2529 assert!(!text.contains("t(%rip)"), "{text}");
2530 }
2531
2532 #[test]
2535 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
2536 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
2537 assert!(text.contains("movl\t$98, %eax"), "{text}");
2538 }
2539
2540 #[test]
2544 fn a_table_that_is_not_read_only_keeps_its_load() {
2545 let text = optimized(
2546 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
2547 );
2548 assert!(!text.contains("movl\t$30, %eax"), "{text}");
2549 }
2550
2551 #[test]
2558 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
2559 let text = optimized(
2560 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
2561 );
2562 assert!(!text.contains("call\tlink_error"), "{text}");
2563 }
2564
2565 #[test]
2567 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2568 let text = asm("long f(void *p) { return (long)p; }\n");
2569 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2574 let mnemonic = line.split_whitespace().next().unwrap_or("");
2575 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2576 }
2577 }
2578
2579 #[test]
2583 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2584 let six = "long a, long b, long c, long d, long e, long f";
2585 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2586
2587 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2594 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2595
2596 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2600 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2601 let eight =
2602 "double a, double b, double c, double d, double e, double f, double g, double h";
2603 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2604 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2605 }
2606
2607 #[test]
2610 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2611 let six = "1, 2, 3, 4, 5, 6";
2612 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2613 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2614
2615 assert!(text.contains("\tmovq\t%"), "{text}");
2616 assert!(text.contains(", (%rsp)\n"), "{text}");
2617 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2618 assert!(text.contains("\tsubq\t$"), "{text}");
2620
2621 let narrow = "int g(int, int, int, int, int, int, int);\n";
2623 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2624 assert!(text.contains("\tmovl\t%"), "{text}");
2625 assert!(text.contains(", (%rsp)\n"), "{text}");
2626 }
2627
2628 #[test]
2631 fn a_variadic_call_counts_registers_and_not_arguments() {
2632 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2633 let decl = "int g(int, ...);\n";
2634 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2635
2636 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2637 assert!(text.contains("\tmovsd\t%"), "{text}");
2638 assert!(text.contains(", (%rsp)\n"), "{text}");
2639 }
2640
2641 #[test]
2646 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2647 let body =
2648 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2649 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2650
2651 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2654 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2655 assert!(!text.contains(", 0(%r"), "{text}");
2656 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2659 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2660
2661 assert!(text.contains("\tsubq\t$"), "{text}");
2663 }
2664
2665 #[test]
2668 fn va_start_writes_the_four_fields_the_psabi_describes() {
2669 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2670 let params = "int a, int b, int c, double d";
2671 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2672
2673 assert!(text.contains(" movl $24, "), "{text}");
2677 assert!(text.contains(" movl $64, "), "{text}");
2678 assert!(text.contains(", 8(%r"), "{text}");
2682 assert!(text.contains(", 16(%r"), "{text}");
2683 let frame: u32 = text
2684 .lines()
2685 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
2686 .expect("a variadic function takes a frame for the save area");
2687 let above = |line: &str| {
2688 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
2689 Some(at > frame)
2690 };
2691 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
2692 }
2693
2694 #[test]
2697 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
2698 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
2699 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
2700 let text = asm(&ints);
2701
2702 assert!(text.contains("$40, "), "{text}");
2705 assert!(text.contains(" cmpl "), "{text}");
2706 assert!(text.contains(" ja "), "{text}");
2710
2711 let arg = "__builtin_va_arg(ap, double)";
2712 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
2713 assert!(text.contains("$160, "), "the last vector slot: {text}");
2714 }
2715
2716 #[test]
2719 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
2720 let decl = "struct pair { long a, b; };\n";
2721 let body = "struct pair p = *q; return p.a + p.b;";
2722 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
2723
2724 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
2725 assert!(!text.contains("\tcall"), "{text}");
2726 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
2728 }
2729
2730 #[test]
2733 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
2734 let decl = "struct bytes { char a[8]; };\n";
2735 let body = "struct bytes p = *q; return p.a[0];";
2736 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
2737
2738 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
2740 }
2741
2742 #[test]
2745 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
2746 let decl = "struct wide { long a, b, c; };\n";
2747 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
2748
2749 assert!(!text.contains("memset"), "nothing calls the library: {text}");
2750 assert!(text.contains("\tmovq\t$0, ") || text.contains("$0, %"), "the zero: {text}");
2751 }
2752
2753 #[test]
2756 fn a_copy_too_large_to_unroll_calls_the_runtime() {
2757 let decl = "struct huge { char a[4096]; };\n";
2758 let mut opts = options();
2759 opts.emit = EmitKind::Asm;
2760 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
2761 let result = run(&opts, &source);
2762 assert!(!result.failed(), "{:?}", result.messages);
2763 let text = result.text();
2764 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
2765 assert!(text.contains("4096"), "the size travels: {text}");
2768 }
2769
2770 #[test]
2777 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
2778 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
2779 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
2780
2781 let copy = text.find("call\tmemcpy").expect("the copy");
2782 let call = text.find("call\ttake").expect("the call");
2783 assert!(copy < call, "the copy comes first: {text}");
2784 assert!(text.contains("leaq\t(%rsp), %rdi"), "the destination: {text}");
2787 assert!(text.contains("$4096, %edx"), "the size: {text}");
2788 }
2789
2790 #[test]
2793 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
2794 let six = "long a, long b, long c, long d, long e, long f";
2795 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
2796 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
2797
2798 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
2802 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
2803 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
2804 }
2805
2806 #[test]
2808 fn the_target_decides_how_the_assembly_is_spelled() {
2809 let mut opts = options();
2810 opts.emit = EmitKind::Asm;
2811 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
2812 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
2813 assert!(text.contains("__TEXT,__text"), "{text}");
2814 assert!(text.contains("\n_f:\n"), "{text}");
2815 assert!(!text.contains(".note.GNU-stack"), "{text}");
2816 }
2817
2818 fn obj(source: &str) -> Vec<u8> {
2820 let mut opts = options();
2821 opts.emit = EmitKind::Object;
2822 let result = run(&opts, source);
2823 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2824 match result.artifact {
2825 Artifact::Object { bytes, .. } => bytes,
2826 other => panic!("expected an object, got {other:?}"),
2827 }
2828 }
2829
2830 #[test]
2836 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
2837 let bytes = obj("int add(int a, int b) { return a + b; }\n");
2838 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
2839 let text = asm("int add(int a, int b) { return a + b; }\n");
2840 assert!(
2841 text.contains("\taddl\t"),
2842 "and the listing of it is the same instructions:\n{text}"
2843 );
2844 }
2845
2846 #[test]
2848 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
2849 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
2850 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
2851 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
2852 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
2853 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
2856 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
2857 assert!(!text.contains(".globl\thidden"), "{text}");
2858 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2861 }
2862
2863 #[test]
2870 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
2871 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
2872 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
2873 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
2874
2875 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
2878 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
2879
2880 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
2883 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
2884
2885 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
2887 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
2888 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
2889 }
2890
2891 #[test]
2893 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
2894 let text = asm("const char *f(void) { return \"hi\"; }\n");
2895 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
2896 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2897 let label = text
2898 .lines()
2899 .find(|line| line.starts_with(".Lstr"))
2900 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
2901 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
2902 }
2903
2904 #[test]
2906 fn an_address_in_an_initializer_is_left_to_the_linker() {
2907 let source = "int counter;\nint *p = &counter;\n";
2908 let text = asm(source);
2909 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
2910 let bytes = obj(source);
2913 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
2914 }
2915
2916 #[test]
2925 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
2926 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
2929 struct m { void (*x)(void); void (*y)(void); };\n\
2930 const struct m t = { a, b };\n");
2931 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
2932 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
2933
2934 let text =
2937 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
2938 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
2939
2940 let text = asm("const int fixed = 7;\n");
2942 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
2943 }
2944
2945 #[test]
2952 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
2953 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
2954 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
2957 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
2958 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
2961 assert!(text.contains("%fs:0"), "{text}");
2962 }
2963
2964 #[test]
2970 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
2971 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
2972 assert!(text.contains("movq\t%fs:0, "), "{text}");
2973 assert!(!text.contains("GOTTPOFF"), "{text}");
2975 }
2976
2977 #[test]
2988 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
2989 for (locality, wanted) in
2990 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
2991 {
2992 let source =
2993 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
2994 let text = asm(&source);
2995 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
2996 }
2997 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
2999 assert!(text.contains("\tprefetcht0\t"), "{text}");
3000 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3003 assert!(text.contains("\tprefetcht0\t"), "{text}");
3004 assert!(!text.contains("prefetchw"), "{text}");
3005 }
3006
3007 #[test]
3018 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3019 let text = asm("void stop(void) { __builtin_trap(); }\n");
3020 assert!(text.contains("\tud2\n"), "{text}");
3021 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3022
3023 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3024 assert!(text.contains("\tud2\n"), "{text}");
3025 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3026 }
3027
3028 #[test]
3040 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3041 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3042 assert!(!text.contains("assume_aligned"), "{text}");
3043 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3044
3045 let source = "unsigned long width(void);\n\
3046 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3047 let text = asm(source);
3048 assert!(!text.contains("assume_aligned"), "{text}");
3049 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3050 }
3051
3052 #[test]
3062 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3063 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3064 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3065 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3066 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3067
3068 let walk = |depth: u32| {
3069 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3070 asm(&source).matches("movq\t(%r").count()
3071 };
3072 assert_eq!(walk(1), 1, "one link is one load");
3073 assert_eq!(walk(3), 3, "three links are three loads");
3074 }
3075
3076 #[test]
3086 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3087 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3088 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3089 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3090 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3091
3092 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3093 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3094 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3095 }
3096
3097 #[test]
3108 fn a_depth_that_is_not_a_small_constant_is_refused() {
3109 let mut opts = options();
3110 opts.emit = EmitKind::Ir;
3111 for source in [
3112 "void *up(int n) { return __builtin_return_address(n); }\n",
3113 "void *up(void) { return __builtin_frame_address(1000); }\n",
3114 ] {
3115 let messages = run(&opts, source).messages;
3116 let named = messages.iter().any(|m| m.contains("E0705"));
3117 assert!(named, "expected a refusal in {messages:?}");
3118 }
3119 }
3120
3121 #[test]
3133 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3134 let text =
3135 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3136 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
3137 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
3138 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3139
3140 let plain = concat!(
3143 "extern void *alloca(__SIZE_TYPE__);\n",
3144 "void use(void *p);\n",
3145 "void f(unsigned long n) { use(alloca(n)); }\n",
3146 );
3147 let text = asm(plain);
3148 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
3149 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
3150
3151 let own = concat!(
3154 "static void *alloca(unsigned long n) { return 0; }\n",
3155 "void *f(unsigned long n) { return alloca(n); }\n",
3156 );
3157 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3158 }
3159
3160 #[test]
3170 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3171 let inner = "{ use(__builtin_alloca(n)); }";
3172 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3173 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3174 let text = asm(&source);
3175 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3179 let taking = line.contains("subq");
3180 let leaving = line.contains("%rbp");
3181 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3182 }
3183 }
3184 }
3185
3186 #[test]
3188 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3189 let source = "int callee(void); int g(void) { return callee(); }\n";
3193 let bytes = obj(source);
3194 assert!(
3195 bytes.windows(7).any(|w| w == b"callee\0"),
3196 "the object has to name the callee for the linker to find it"
3197 );
3198 let text = asm(source);
3199 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3200 }
3201
3202 #[test]
3208 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3209 let mut opts = options();
3210 opts.emit = EmitKind::Executable;
3212 let result = run(&opts, "int main(void) { return 0; }\n");
3213 assert_eq!(result.messages, Vec::<String>::new());
3214 match result.artifact {
3215 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3216 other => panic!("expected an object, got {other:?}"),
3217 }
3218 }
3219
3220 #[test]
3222 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3223 let mut opts = options();
3224 opts.emit = EmitKind::Object;
3225 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3226 let result = run(&opts, "int f(void) { return 0; }\n");
3227 assert!(result.failed(), "an object nobody can read is worse than a message");
3228 assert!(
3229 result.messages.iter().any(|m| m.contains("no object writer")),
3230 "{:?}",
3231 result.messages
3232 );
3233 }
3234
3235 fn ir(source: &str) -> String {
3237 let mut opts = options();
3238 opts.emit = EmitKind::Ir;
3239 let result = run(&opts, source);
3240 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3241 result.text().to_owned()
3242 }
3243
3244 fn errors(source: &str) -> Vec<String> {
3246 let mut opts = options();
3247 opts.emit = EmitKind::Ir;
3248 let result = run(&opts, source);
3249 assert!(result.failed(), "expected this to be refused:\n{source}");
3250 result.messages
3251 }
3252
3253 fn body(source: &str) -> String {
3255 let text = ir(source);
3256 let (_, rest) = text.split_once("{\n").expect("a function definition");
3257 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3258 body.to_owned()
3259 }
3260
3261 #[test]
3269 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3270 let source = "inline int f(int x) { return x + 1; }\n";
3271 let with = |flag: bool| {
3272 let mut opts = options();
3273 opts.emit = EmitKind::Ir;
3274 opts.gnu89_inline = flag;
3275 let result = run(&opts, source);
3276 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3277 result.text().to_owned()
3278 };
3279
3280 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3283
3284 assert!(with(true).contains("block0"), "a body: {}", with(true));
3287 }
3288
3289 #[test]
3296 fn an_access_through_a_type_names_the_type_it_went_through() {
3297 let source = "\
3298struct s { int a; float b; };\n\
3299union u { int i; float f; };\n\
3300int scalar(int *p) { return *p; }\n\
3301float member(struct s *p) { p->a = 1; return p->b; }\n\
3302int element(int *a, long i) { return a[i]; }\n\
3303float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3304 let text = ir(source);
3305 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3306 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3307 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3308 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3311 assert_eq!(named, 6, "six accesses: {text}");
3312 }
3313
3314 #[test]
3321 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3322 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3323 let mut opts = options();
3324 opts.emit = EmitKind::Ir;
3325 opts.strict_aliasing = false;
3326 let result = run(&opts, source);
3327 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3328 let text = result.text().to_owned();
3329 assert!(!text.contains("tbaa"), "not even the root: {text}");
3330 }
3331
3332 #[test]
3340 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3341 let mut opts = options();
3342 opts.emit = EmitKind::Ir;
3343 opts.std = Std::C89;
3344 let compiled = |source: &str| {
3345 let result = run(&opts, source);
3346 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3347 result.text().to_owned()
3348 };
3349
3350 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3351 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3352 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3353
3354 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3356 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3357 }
3358
3359 #[test]
3367 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3368 let mut opts = options();
3369 opts.emit = EmitKind::Ir;
3370 opts.std = Std::C89;
3371 let compiled = |source: &str| {
3372 let result = run(&opts, source);
3373 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3374 result.text().to_owned()
3375 };
3376
3377 let text = compiled("int f(void) { return g(); }\n");
3379 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3380 assert!(text.contains("i32"), "and it gives back an int: {text}");
3381
3382 let text = compiled("int f(char c) { return g(c); }\n");
3385 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3386
3387 let mut opts = options();
3390 opts.std = Std::C89;
3391 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3392 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3393 }
3394
3395 #[test]
3405 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3406 let mut opts = options();
3407 opts.emit = EmitKind::Ir;
3408 opts.std = Std::C89;
3409 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3410 .text()
3411 .to_owned();
3412 assert!(text.contains("func @f()"), "the caller is there: {text}");
3413 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3414 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3415 }
3416
3417 #[test]
3425 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3426 let mut opts = options();
3427 opts.emit = EmitKind::Ir;
3428 opts.std = Std::C89;
3429 let compiled = |source: &str| run(&opts, source).text().to_owned();
3430
3431 let text = compiled("f (c) unsigned char c; { return c; }\n");
3432 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3433 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3434 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3435
3436 let text = compiled("f (s) short s; { return s; }\n");
3438 assert!(text.contains("trunc.i16"), "cut down: {text}");
3439 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3440
3441 let text = compiled("f (x) float x; { return x * 2; }\n");
3444 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3445 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3446
3447 let text = compiled("int f(unsigned char c) { return c; }\n");
3450 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3451 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3452 }
3453
3454 #[test]
3463 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3464 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3466 let cases = [
3467 ("static counted;\n", ["", "error", "warning", "error"]),
3468 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3469 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3470 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3471 (
3472 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3473 ["warning", "error", "warning", "error"],
3474 ),
3475 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3476 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3477 ];
3478
3479 for (source, 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 severity = if said.contains(": error: ") {
3486 "error"
3487 } else if said.contains(": warning: ") {
3488 "warning"
3489 } else {
3490 ""
3491 };
3492 let how = if permissive { " -fpermissive" } else { "" };
3493 assert_eq!(
3494 severity,
3495 wanted,
3496 "under -std={}{how}, {source} was answered with `{said}`",
3497 std.as_str()
3498 );
3499 if wanted.is_empty() {
3500 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3501 }
3502 }
3503 }
3504 }
3505
3506 #[test]
3515 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3516 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3517 let cases = [
3518 (
3519 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3520 "first argument to 'va_arg' not of type 'va_list'",
3521 ["error", "error", "error", "error"],
3522 ),
3523 (
3524 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3525 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3526 ["warning", "error", "warning", "error"],
3527 ),
3528 (
3529 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3530 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3531 cast",
3532 ["warning", "error", "warning", "error"],
3533 ),
3534 (
3535 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3536 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3537 ["warning", "error", "warning", "error"],
3538 ),
3539 ];
3540
3541 for (source, message, wanted) in cases {
3542 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3543 let mut opts = options();
3544 opts.std = std;
3545 opts.permissive = permissive;
3546 let said = run(&opts, source).messages.join("\n");
3547 let how = if permissive { " -fpermissive" } else { "" };
3548 assert!(
3549 said.contains(&format!(": {wanted}: {message}")),
3550 "under -std={}{how}, {source} was answered with `{said}`",
3551 std.as_str()
3552 );
3553 }
3554 }
3555 }
3556
3557 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3559 let mut opts = options();
3560 opts.emit = EmitKind::Ir;
3561 opts.safety = tier;
3562 let result = run(&opts, source);
3563 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3564 result.text().to_owned()
3565 }
3566
3567 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3568
3569 fn padded_ir(padding: Padding, source: &str) -> String {
3571 let mut opts = options();
3572 opts.emit = EmitKind::Ir;
3573 opts.safety = rucc_session::Safety::Detect;
3574 opts.padding = padding;
3575 let result = run(&opts, source);
3576 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3577 result.text().to_owned()
3578 }
3579
3580 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3581 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3582
3583 #[test]
3584 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3585 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3589 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3590 }
3591
3592 #[test]
3593 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3594 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3597 assert!(!text.contains("owns"), "{text}");
3598 }
3599
3600 #[test]
3601 fn a_member_of_a_union_owns_nothing_after_it() {
3602 let text = padded_ir(
3606 Padding::Ignored,
3607 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3608 );
3609 assert!(!text.contains("owns"), "{text}");
3610 }
3611
3612 #[test]
3613 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3614 let text = padded_ir(
3619 Padding::Ignored,
3620 "struct inner { char c; };\n\
3621 struct outer { struct inner in; int x; };\n\
3622 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3623 );
3624 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3625 }
3626
3627 #[test]
3628 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3629 let text = ir(READS_THROUGH_A_POINTER);
3633 assert!(!text.contains("check_"), "{text}");
3634 assert!(!text.contains("cap_of"), "{text}");
3635 }
3636
3637 #[test]
3638 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3639 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3640 assert!(text.contains("cap_of"), "{text}");
3641 assert!(text.contains("check_bounds"), "{text}");
3642 assert!(text.contains("check_live"), "{text}");
3643 assert!(text.contains("check_deriv"), "{text}");
3645 assert!(text.contains("check_type"), "{text}");
3647 }
3648
3649 #[test]
3650 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3651 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3655 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3656 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3657 }
3658 }
3659
3660 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3662 let mut opts = options();
3663 opts.emit = EmitKind::SafetySummary;
3664 opts.safety = tier;
3665 let result = run(&opts, source);
3666 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3667 result.text().to_owned()
3668 }
3669
3670 #[test]
3671 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3672 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3673 assert!(text.contains("\"tier\": \"detect\""), "{text}");
3674 assert!(
3676 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
3677 "{text}"
3678 );
3679 assert!(
3680 text.contains(
3681 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
3682 ),
3683 "{text}"
3684 );
3685 }
3686
3687 #[test]
3688 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
3689 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
3693 assert!(text.contains("\"tier\": \"off\""), "{text}");
3694 assert!(
3695 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
3696 "{text}"
3697 );
3698 }
3699
3700 #[test]
3701 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
3702 let text = summary(
3703 rucc_session::Safety::Detect,
3704 "void *memcpy(void *, const void *, unsigned long);\n\
3705 int puts(const char *);\n\
3706 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
3707 );
3708 assert!(text.contains("\"interposed\": 1"), "{text}");
3709 assert!(text.contains("\"puts\""), "{text}");
3710 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
3714 }
3715
3716 #[test]
3717 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3718 let text = summary(
3722 rucc_session::Safety::Detect,
3723 "void *notes_open(void);\n\
3724 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3725 );
3726 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3727 assert!(text.contains("\"notes_open\""), "{text}");
3728 }
3729
3730 #[test]
3731 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3732 let text = summary(
3735 rucc_session::Safety::Detect,
3736 "static int len(const char *p) { return p ? 1 : 0; }\n\
3737 int f(void) { return len(\"x\"); }\n",
3738 );
3739 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3740 }
3741
3742 fn granules(source: &str) -> String {
3744 let mut opts = options();
3745 opts.emit = EmitKind::TypeGranules;
3746 let result = run(&opts, source);
3747 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3748 result.text().to_owned()
3749 }
3750
3751 #[test]
3752 fn the_granule_report_names_every_record_and_both_keyings() {
3753 let text = granules(
3754 "struct hot { char *p; int a; int b; };\n\
3755 int f(struct hot *h) { return h->a; }\n",
3756 );
3757 assert!(text.contains("struct hot"), "{text}");
3758 assert!(text.contains("every type distinct"), "{text}");
3761 assert!(text.contains("every pointer one type"), "{text}");
3762 assert!(text.contains("budget"), "{text}");
3763 }
3764
3765 #[test]
3766 fn a_record_nothing_uses_is_still_measured() {
3767 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3770 assert!(text.contains("struct unused"), "{text}");
3771 }
3772
3773 #[test]
3774 fn the_granule_report_stops_before_anything_is_lowered() {
3775 let text = granules(
3779 "struct wide { long double d; };\n\
3780 long double f(long double x) { return x * x; }\n",
3781 );
3782 assert!(text.contains("struct wide"), "{text}");
3783 }
3784
3785 #[test]
3786 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
3787 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
3790 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
3791 }
3792
3793 #[test]
3794 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
3795 let text = summary(
3796 rucc_session::Safety::Detect,
3797 "unsigned long f(int *p) { return (unsigned long) p; }\n",
3798 );
3799 assert!(text.contains("\"exposed\": 1"), "{text}");
3800 }
3801
3802 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
3804 let mut opts = options();
3805 opts.emit = EmitKind::Asm;
3806 opts.safety = tier;
3807 let result = run(&opts, source);
3808 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3809 result.text().to_owned()
3810 }
3811
3812 #[test]
3813 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
3814 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3815 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
3816 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
3817 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
3818 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
3819 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
3820 }
3821
3822 #[test]
3823 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
3824 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3828 let section = format!("\t.section\t{},", rucc_safety::SECTION);
3829 assert_eq!(text.matches(§ion).count(), 5, "{text}");
3830 for index in 0..5 {
3831 let name = format!("__rucc_safety_desc_{index}");
3832 assert!(text.contains(&format!("{name}:\n")), "{text}");
3835 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
3836 }
3837 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
3838 }
3839
3840 #[test]
3848 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
3849 let text = ir(concat!(
3850 "int g;\n",
3851 "int a = __builtin_constant_p(1);\n",
3852 "int b = __builtin_constant_p(g);\n",
3853 "int c = __builtin_constant_p(\"abc\");\n",
3854 "int d = __builtin_constant_p(&g);\n",
3855 "int e = __builtin_constant_p(1.5);\n",
3856 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
3857 ));
3858 assert!(text.contains("global @a : i32 = 1,"), "{text}");
3859 assert!(text.contains("global @b : i32 = 0,"), "{text}");
3860 assert!(text.contains("global @c : i32 = 1,"), "{text}");
3861 assert!(text.contains("global @d : i32 = 0,"), "{text}");
3862 assert!(text.contains("global @e : i32 = 1,"), "{text}");
3863 assert!(text.contains("global @h : i32 = 11,"), "{text}");
3864 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
3865
3866 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
3870 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
3871 }
3872
3873 #[test]
3882 fn a_call_to_a_library_builtin_reaches_the_library_function() {
3883 let text = body("void f(void) { __builtin_abort(); }\n");
3884 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
3885
3886 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
3889 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
3890 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
3891 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3892 }
3893
3894 #[test]
3907 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
3908 let text = ir(concat!(
3909 "char d[8];\n",
3910 "void f(const char *s, unsigned long n) {\n",
3911 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3912 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
3913 " __builtin___memset_chk(d, 0, n, 8);\n",
3914 "}\n",
3915 ));
3916 assert!(text.contains("call @__memcpy_chk("), "{text}");
3917 assert!(text.contains("call @__strcpy_chk("), "{text}");
3918 assert!(text.contains("call @__memset_chk("), "{text}");
3919 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
3920 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
3921 }
3922
3923 #[test]
3931 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
3932 let text = ir(concat!(
3933 "extern char *p;\n",
3934 "char d[8];\n",
3935 "void f(const char *s, unsigned long n) {\n",
3936 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
3937 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3938 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
3939 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3940 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
3941 "}\n",
3942 ));
3943
3944 assert!(
3946 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
3947 "{text}"
3948 );
3949
3950 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3953 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
3954 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
3955
3956 assert!(text.contains("call @__sprintf_chk("), "{text}");
3959
3960 let asm = asm(concat!(
3963 "void f(char *p, const char *s, unsigned long n) {\n",
3964 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
3965 "}\n",
3966 ));
3967 assert!(asm.contains("call\tmemcpy"), "{asm}");
3968 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
3969 }
3970
3971 #[test]
3979 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
3980 let text = ir(concat!(
3981 "char d[64];\n",
3982 "int f(const char *fmt, ...) {\n",
3983 " __builtin_va_list ap;\n",
3984 " __builtin_va_start(ap, fmt);\n",
3985 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
3986 " __builtin_va_end(ap);\n",
3987 " return n;\n",
3988 "}\n",
3989 ));
3990 assert!(text.contains("call @__vsprintf_chk("), "{text}");
3991 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
3992 }
3993
3994 #[test]
4005 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4006 let text = body(concat!(
4007 "long long llabs(long long);\n",
4008 "long long f(long long x) { return llabs(x); }\n",
4009 ));
4010 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4011 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4012 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4013 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4014 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4015
4016 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4019 assert!(text.contains("iconst.i32 31"), "{text}");
4020 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4021 assert!(text.contains("iconst.i64 63"), "{text}");
4022
4023 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4026 assert!(!text.contains("call"), "{text}");
4027
4028 let text = ir(concat!(
4030 "long long llabs(long long b);\n",
4031 "long long g(long long x) { return llabs(x); }\n",
4032 "long long llabs(long long b) { return 7; }\n",
4033 ));
4034 assert!(!text.contains("call @llabs"), "{text}");
4035 }
4036
4037 #[test]
4044 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4045 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4046 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4047
4048 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4051 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4052 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4053 }
4054
4055 #[test]
4061 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4062 for (name, ty, width) in [
4063 ("__builtin_bswap16", "unsigned short", "i16"),
4064 ("__builtin_bswap32", "unsigned", "i32"),
4065 ("__builtin_bswap64", "unsigned long long", "i64"),
4066 ] {
4067 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4068 let text = body(&source);
4069 assert_eq!(
4070 text,
4071 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4072 "{name}"
4073 );
4074 }
4075 }
4076
4077 #[test]
4084 fn the_bit_counts_are_instructions_and_not_calls() {
4085 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4086 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4087
4088 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4089 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4090
4091 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4092 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4093 }
4094
4095 #[test]
4104 fn the_bit_counts_ask_about_the_width_their_name_says() {
4105 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4106 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4107 assert!(text.contains("%1 = ctlz %0"), "{text}");
4108 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4109
4110 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4113 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4114 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4115
4116 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4117 assert!(text.contains("%1 = ctpop %0"), "{text}");
4118 assert!(!text.contains("call"), "{text}");
4119 }
4120
4121 #[test]
4126 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4127 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4128 assert!(text.contains("%1 = ctpop %0"), "{text}");
4129 assert!(text.contains("iconst.i32 1"), "{text}");
4130 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4131 }
4132
4133 #[test]
4139 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4140 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4141 assert!(text.contains("%1 = cttz %0"), "{text}");
4142 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4143 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4144 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4145 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4146 assert!(!text.contains("br_if"), "no branch: {text}");
4147 }
4148
4149 #[test]
4159 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4160 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4161 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4162 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4163 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4164 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4165 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4166 assert!(text.contains("%7 = ctlz %6"), "{text}");
4167 assert!(!text.contains("call"), "{text}");
4168 assert!(!text.contains("br_if"), "no branch: {text}");
4169 }
4170
4171 #[test]
4177 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4178 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4179 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4180 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4181 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4182
4183 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4184 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4185
4186 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4189 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4190 }
4191
4192 #[test]
4200 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4201 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4202 assert!(text.contains("iconst.i64 63"), "{text}");
4203 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4204 assert!(!text.contains("call"), "{text}");
4205
4206 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4207 assert!(text.contains("iconst.i64 63"), "{text}");
4208 assert!(!text.contains("call"), "{text}");
4209 }
4210
4211 #[test]
4219 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4220 let text =
4221 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4222 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4223 assert!(!text.contains("store"), "nothing is written: {text}");
4224 assert!(!text.contains("call"), "{text}");
4225
4226 let text =
4229 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4230 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4231 assert!(!text.contains("store"), "{text}");
4232
4233 let text = body(concat!(
4236 "int g(void);\n",
4237 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4238 ));
4239 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4240 }
4241
4242 #[test]
4252 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4253 let text =
4254 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4255 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4256 assert!(text.contains("store %3 -> %2"), "{text}");
4257 assert!(!text.contains("call"), "{text}");
4258
4259 let text =
4260 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4261 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4262
4263 let text =
4264 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4265 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4266
4267 let text = body(
4270 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4271 );
4272 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4273 }
4274
4275 #[test]
4283 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4284 let text = body(
4285 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4286 );
4287 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4288 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4289 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4290
4291 let text = body(
4294 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4295 );
4296 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4297 assert!(!text.contains("sext."), "{text}");
4298 assert!(!text.contains("zext.i64"), "{text}");
4300 }
4301
4302 #[test]
4310 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4311 let text =
4312 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4313 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4314 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4315 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4316 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4317 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4318 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4319 }
4320
4321 #[test]
4328 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4329 for name in ["add", "sub", "mul"] {
4330 let source = format!(
4331 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4332 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4333 );
4334 let mut opts = options();
4335 opts.emit = EmitKind::MirFinal;
4336 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4337 }
4338 }
4339
4340 #[test]
4343 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4344 let messages =
4345 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4346 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4347
4348 let messages =
4349 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4350 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4351 }
4352
4353 #[test]
4364 fn an_ordered_access_is_ordered_in_the_ir() {
4365 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4366 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4367
4368 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4369 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4370
4371 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4372 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4373
4374 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4375 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4376
4377 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4380 assert!(text.contains("trunc.i8 %1"), "{text}");
4381 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4382 }
4383
4384 #[test]
4393 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4394 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4395 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4396 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4397
4398 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4399 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4400 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4401
4402 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4403 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4404 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4405 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4406 }
4407
4408 #[test]
4418 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4419 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4420 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4421
4422 for weaker in ["1", "2", "3", "4"] {
4423 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4424 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4425 }
4426 }
4427
4428 #[test]
4438 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
4439 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
4440 let source = format!("void f(void) {{ {name}(); }}\n");
4441 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
4442 let text = body(&source);
4443 assert!(text.contains("fence seq_cst"), "{name}: {text}");
4444 }
4445
4446 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
4447 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4448 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
4449 }
4450
4451 #[test]
4457 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4458 let text =
4461 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4462 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4463 assert!(text.contains("return %3"), "the value it found: {text}");
4464
4465 let text =
4466 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4467 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4468 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4469
4470 let text = body(
4473 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4474 );
4475 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4476 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4477 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4478 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4479
4480 let text = body(
4483 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4484 );
4485 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4486 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4487 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4488 }
4489
4490 #[test]
4497 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4498 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4499 for (ty, suffix, reg) in widths {
4500 let source = format!(
4501 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4502 );
4503 let text = asm(&source);
4504 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4505 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4506 assert!(text.contains("sete\t"), "{ty}: {text}");
4507 }
4508 let source =
4509 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4510 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4511
4512 for order in ["0", "2", "3", "4", "5"] {
4516 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4517 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4518 let text = asm(&source);
4519 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4520 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4521 }
4522 }
4523
4524 #[test]
4536 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4537 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4538 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4539 assert!(text.contains("return %2"), "the value that was there: {text}");
4540
4541 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4542 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4543 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4544
4545 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4546 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4547 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4548
4549 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4551 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4552
4553 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4556 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4557
4558 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4559 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4560
4561 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4564 assert!(text.contains("release"), "{text}");
4565 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4566
4567 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4571 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4572 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4573
4574 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4577 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4578
4579 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4580 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4581 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4582
4583 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4586 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4587 assert!(text.contains("%3 = and %2, %1"), "{text}");
4588 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4589 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4590 }
4591
4592 #[test]
4603 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4604 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4605 for (ty, suffix, reg) in widths {
4606 for (name, call, insn) in [
4607 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4608 ("or", "__sync_fetch_and_or(p, v)", "or"),
4609 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4610 ] {
4611 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4612 let text = asm(&source);
4613 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4614 assert!(
4615 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4616 "{ty} {name}: {text}"
4617 );
4618 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4619 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4621 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4622 }
4623 }
4624 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4625 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4626
4627 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4631 assert!(text.contains("cmpxchgl\t"), "{text}");
4632 assert!(text.contains("andl\t"), "{text}");
4633 assert!(text.contains("notl\t"), "{text}");
4634 }
4635
4636 #[test]
4645 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4646 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4647 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4648 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4649
4650 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4651 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4652 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4653
4654 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4657 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4658 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4659 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4660 }
4661
4662 #[test]
4673 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4674 for pointer in ["char", "int", "void"] {
4675 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4676 let text = body(&source);
4677 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4678 assert!(
4679 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4680 "{pointer}: {text}"
4681 );
4682 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4683
4684 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4685 let text = body(&source);
4686 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4687 }
4688
4689 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4692 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4693 assert!(text.contains("setne\t"), "{text}");
4694 }
4695
4696 #[test]
4704 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4705 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4706 for (ty, suffix, reg) in widths {
4707 let source =
4708 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4709 let text = asm(&source);
4710 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4711 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4712
4713 let source =
4714 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4715 let text = asm(&source);
4716 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4717 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4718 }
4719 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4720 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4721
4722 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4725 let text = asm(source);
4726 assert!(text.contains("negl\t"), "{text}");
4727 assert!(text.contains("xaddl\t"), "{text}");
4728
4729 for order in ["0", "2", "3", "4", "5"] {
4732 let source =
4733 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4734 let text = asm(&source);
4735 assert!(text.contains("xaddl\t"), "{order}: {text}");
4736 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4737 }
4738
4739 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4743 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4744 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4749 assert!(text.contains("movl\t$0, %eax"), "{text}");
4750 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4751 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4752 }
4753
4754 #[test]
4766 fn the_lock_free_questions_are_answered_as_constants() {
4767 for size in ["1", "2", "4", "8"] {
4768 let source =
4769 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4770 let text = asm(&source);
4771 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4772 assert!(!text.contains("call"), "and is not a call: {text}");
4773 }
4774 for size in ["3", "16", "sizeof(long double)"] {
4775 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4776 let text = asm(&source);
4777 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
4778 assert!(!text.contains("call"), "and is not a call either: {text}");
4779 }
4780
4781 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
4785 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
4786 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
4787 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
4788 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
4789 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
4790 }
4791
4792 #[test]
4804 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
4805 let mut opts = options();
4806 opts.emit = EmitKind::Ir;
4807
4808 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
4809 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
4810 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
4811
4812 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
4813 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
4814 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
4815
4816 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
4817 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
4818 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
4819 }
4820
4821 #[test]
4833 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
4834 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
4835 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
4836 assert!(text.contains("shrq"), "with the value halved first: {text}");
4837 assert!(text.contains("addsd"), "and doubled after: {text}");
4838 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4839
4840 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
4841 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
4842 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
4843 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
4844 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4845 }
4846
4847 #[test]
4858 fn a_plain_name_the_program_took_is_the_programs_own_function() {
4859 let taken = concat!(
4860 "static long long llabs(long long b) { return 7; }\n",
4861 "long long f(long long x) { return llabs(x); }\n",
4862 );
4863 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
4864
4865 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
4866 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
4867
4868 let plain = concat!(
4869 "long long llabs(long long b);\n",
4870 "long long f(long long x) { return llabs(x); }\n",
4871 );
4872 let mut opts = options();
4873 opts.emit = EmitKind::Ir;
4874 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
4875
4876 opts.builtins = false;
4877 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
4878
4879 opts.builtins = true;
4880 opts.no_builtin = vec!["llabs".to_owned()];
4881 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
4882 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
4883 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
4884
4885 opts.no_builtin = Vec::new();
4888 opts.builtins = false;
4889 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
4890 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
4891 }
4892
4893 #[test]
4906 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
4907 let text = ir(concat!(
4908 "long a = __builtin_expect(7, 1);\n",
4909 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
4910 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
4911 ));
4912 assert!(text.contains("global @a : i64 = 7,"), "{text}");
4913 assert!(text.contains("global @b : i64 = 9,"), "{text}");
4914 assert!(text.contains("global @c : i64 = 8,"), "{text}");
4915 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
4916
4917 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
4920 assert!(text.contains("sext"), "{text}");
4921
4922 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
4926 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
4927 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
4928 assert_eq!(body(source), one);
4929
4930 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
4935 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
4936 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
4937 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
4938 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
4939 }
4940
4941 #[test]
4953 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
4954 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
4955 let text = ir(promised);
4956 assert!(text.contains(" unreachable_hint\n"), "{text}");
4957 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
4958
4959 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
4963 assert!(after.contains("return"), "{after}");
4964
4965 let text = asm(promised);
4968 let mine = text.split_once("\nf:\n").expect("a definition").1;
4969 let mine = mine.split_once("\t.size").expect("a definition").0;
4970 let plain = asm("int f(int x) { if (x) return 1; }\n");
4971 let plain = plain.split_once("\nf:\n").expect("a definition").1;
4972 let plain = plain.split_once("\t.size").expect("a definition").0;
4973 assert_eq!(mine, plain);
4974 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
4977 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
4978 assert!(!mine.contains("ud2"), "{mine}");
4979 }
4980
4981 #[test]
4988 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
4989 let mut opts = options();
4990 opts.emit = EmitKind::Ir;
4991 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
4992 assert!(
4993 messages.iter().any(|m| m.contains("__builtin_abort")),
4994 "expected the written name in {messages:?}"
4995 );
4996 }
4997
4998 #[test]
5006 fn a_builtin_nothing_lowers_is_refused_by_name() {
5007 let mut opts = options();
5008 opts.emit = EmitKind::Ir;
5009 let builtin = "__atomic_signal_fence";
5010 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5011 let messages = run(&opts, &source).messages;
5012 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5013 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5014 }
5015
5016 #[test]
5025 fn what_is_refused_is_the_call_and_not_the_name() {
5026 let text = ir(concat!(
5027 "void __atomic_signal_fence(int order) { (void)order; }\n",
5028 "void f(void) { __atomic_signal_fence(5); }\n",
5029 ));
5030 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5031 }
5032
5033 #[test]
5042 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5043 let text = ir(concat!(
5044 "struct S { char a[8]; int n; char b[12]; };\n",
5045 "char g[32];\n",
5046 "struct S gs;\n",
5047 "unsigned long whole = __builtin_object_size(g, 0);\n",
5048 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5049 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5050 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5051 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5052 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5053 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5054 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5055 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5056 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5057 ));
5058 for (name, size) in [
5059 ("whole", 32),
5060 ("moved", 28),
5061 ("back", 4),
5062 ("outer", 24),
5063 ("inner", 8),
5064 ("scalar", 4),
5065 ("after", 16),
5066 ("into", 10),
5067 ("text", 6),
5068 ("dyn", 12),
5069 ] {
5070 let said = format!("global @{name} : i64 = {size},");
5071 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5072 }
5073 }
5074
5075 #[test]
5083 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5084 let text = body(concat!(
5085 "struct S { char a[8]; int n; char b[12]; };\n",
5086 "unsigned long f(void) {\n",
5087 " char loc[20];\n",
5088 " struct S ls;\n",
5089 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5090 "}\n",
5091 ));
5092 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5093 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5094 }
5095
5096 #[test]
5106 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5107 let text = ir(concat!(
5108 "struct T { int n; char f[]; };\n",
5109 "extern char *p;\n",
5110 "extern struct T *t;\n",
5111 "unsigned long largest = __builtin_object_size(p, 0);\n",
5112 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5113 "unsigned long least = __builtin_object_size(p, 2);\n",
5114 "unsigned long tight = __builtin_object_size(p, 3);\n",
5115 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5116 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5117 ));
5118 for name in ["largest", "nearest", "flex"] {
5119 let said = format!("global @{name} : i64 = -1,");
5123 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5124 }
5125 for name in ["least", "tight"] {
5126 let said = format!("global @{name} : i64 = 0,");
5127 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5128 }
5129 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5130 }
5131
5132 #[test]
5139 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5140 let text = body(concat!(
5141 "extern char *side(void);\n",
5142 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5143 ));
5144 assert!(!text.contains("call"), "nothing is called: {text}");
5145 }
5146
5147 #[test]
5152 fn a_kind_that_is_not_one_of_the_four_is_refused() {
5153 for source in [
5154 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
5155 + "{ return __builtin_object_size(p, k); }\n",
5156 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
5157 .to_owned(),
5158 "extern char *p;\nunsigned long f(void) ".to_owned()
5159 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
5160 ] {
5161 let messages = errors(&source);
5162 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
5163 assert!(named, "expected a complaint about the kind in {messages:?}");
5164 }
5165 }
5166
5167 #[test]
5173 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
5174 let text = ir(concat!(
5175 "void *buf[5];\n",
5176 "int f(void) {\n",
5177 " if (__builtin_setjmp(buf)) return 2;\n",
5178 " return 1;\n",
5179 "}\n",
5180 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5181 ));
5182 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5183 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5184 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5185 }
5186
5187 #[test]
5195 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5196 let text = ir(concat!(
5197 "void *buf[5];\n",
5198 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5199 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5200 ));
5201 let (saves, plain) = text.split_once("func @g").expect("both functions");
5202 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5203 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5204 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5205 }
5206
5207 #[test]
5216 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5217 let text =
5218 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5219 let body = text.split_once("\nf:\n").expect("the function").1;
5220 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5221 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5222 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5223 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5224 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5225 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5226 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5227 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5228 }
5229
5230 #[test]
5238 fn a_save_destroys_every_register_the_allocator_hands_out() {
5239 let text =
5240 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5241 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5242 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5243 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5244 }
5245 }
5246
5247 #[test]
5254 fn the_restore_puts_the_frame_back_before_it_jumps() {
5255 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5256 let mut opts = options();
5257 opts.emit = EmitKind::Asm;
5258 opts.opt_level = level;
5259 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5260 let result = run(&opts, source);
5261 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5262 let text = result.text().to_owned();
5263 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5264 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5265 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5266 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5267 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5268 }
5269 }
5270
5271 #[test]
5277 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5278 for source in [
5279 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5280 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5281 ] {
5282 let messages = errors(source);
5283 let named = messages.iter().any(|m| m.contains("E0710"));
5284 assert!(named, "expected a complaint about the value in {messages:?}");
5285 }
5286 }
5287
5288 #[test]
5293 fn a_static_function_nothing_refers_to_is_not_emitted() {
5294 let text = ir("static int dropped(void) { return 1; }\n\
5295 static int kept(void) { return 2; }\n\
5296 int main(void) { return kept(); }\n");
5297 assert!(text.contains("func @kept"), "{text}");
5298 assert!(!text.contains("dropped"), "{text}");
5299 }
5300
5301 #[test]
5307 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5308 let text = ir("static int ping(void);\n\
5309 static int pong(void) { return ping(); }\n\
5310 static int ping(void) { return pong(); }\n\
5311 int main(void) { return 0; }\n");
5312 assert!(!text.contains("ping"), "{text}");
5313 assert!(!text.contains("pong"), "{text}");
5314 }
5315
5316 #[test]
5322 fn naming_a_static_function_anywhere_keeps_it() {
5323 let text = ir("static int by_address(void) { return 1; }\n\
5324 static int in_an_image(void) { return 2; }\n\
5325 static int deeper(void) { return 3; }\n\
5326 static int reaches_deeper(void) { return deeper(); }\n\
5327 static int (*table[1])(void) = {in_an_image};\n\
5328 int main(void) {\n\
5329 int (*p)(void) = by_address;\n\
5330 return p() + table[0]() + reaches_deeper();\n\
5331 }\n");
5332 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5333 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5334 }
5335 }
5336
5337 #[test]
5343 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5344 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5345 let source = format!(
5346 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5347 int main(void) {{ return 0; }}\n"
5348 );
5349 let text = ir(&source);
5350 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5351 }
5352 }
5353
5354 #[test]
5357 fn a_function_anything_could_call_is_emitted_without_being_called() {
5358 let text =
5359 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5360 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5361 }
5362
5363 #[test]
5370 fn a_classification_c_has_an_operator_for_is_that_operator() {
5371 for (builtin, operator) in [
5372 ("__builtin_isgreater", "binary >"),
5373 ("__builtin_isgreaterequal", "binary >="),
5374 ("__builtin_isless", "binary <"),
5375 ("__builtin_islessequal", "binary <="),
5376 ] {
5377 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5378 let text = tast(&source);
5379 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5380 }
5381 }
5382
5383 #[test]
5392 fn the_classification_builtins_are_comparisons_and_not_calls() {
5393 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5394 assert_eq!(
5395 text,
5396 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5397 %2\n return %3\n"
5398 );
5399
5400 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5402 assert!(text.contains("fcmp one %0, %1"), "{text}");
5403
5404 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5405 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5406
5407 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5408 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5409 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5410 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5411 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5412 assert!(text.contains("%5 = or %3, %4"), "{text}");
5413
5414 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5417 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5418 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5419 assert!(text.contains("%5 = and %3, %4"), "{text}");
5420
5421 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5422 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5423 assert!(text.contains("icmp slt %1, %2"), "{text}");
5424
5425 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5428 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5429
5430 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5433 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5434 }
5435
5436 #[test]
5443 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5444 let text = ir(concat!(
5445 "int a = __builtin_isinff(1e300);\n",
5446 "int b = __builtin_isinf(1e300);\n",
5447 "int c = __builtin_isnan(0.0);\n",
5451 "int d = __builtin_signbit(-0.0);\n",
5452 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5453 ));
5454 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5455 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5456 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5457 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5458 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5459 }
5460
5461 #[test]
5463 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5464 let mut opts = options();
5465 opts.emit = EmitKind::Ir;
5466 let source = concat!(
5467 "int a(int x) { return __builtin_isnan(x); }\n",
5468 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5469 "int c(double x) { return __builtin_isnan(x, x); }\n",
5470 );
5471 let messages = run(&opts, source).messages;
5472 assert_eq!(
5473 messages,
5474 [
5475 "/main.c:1:23: error: non-floating-point argument in call to function \
5476 '__builtin_isnan' [E0685]",
5477 "/main.c:2:30: error: non-floating-point arguments in call to function \
5478 '__builtin_isunordered' [E0685]",
5479 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5480 ]
5481 );
5482 }
5483
5484 #[test]
5493 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5494 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5495 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5499 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5500 assert!(text.contains("%3 = and %1, %2"), "{text}");
5501 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5502 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5503 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5504 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5505 assert!(text.contains("%8 = and %6, %7"), "{text}");
5506
5507 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5511 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5512 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5513
5514 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5515 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5516 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5517 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5518
5519 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5520 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5521 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5522 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5526 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5527 assert!(!text.contains("call"), "{text}");
5528
5529 let text = body(concat!(
5532 "double g(void);\n",
5533 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5534 ));
5535 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5536 }
5537
5538 #[test]
5545 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5546 let text = ir(concat!(
5547 "int a = __builtin_isnormal(1.0);\n",
5548 "int b = __builtin_isnormal(0.0);\n",
5549 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5550 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5551 "int e = __builtin_isinf_sign(1.0);\n",
5552 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5553 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5554 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5555 ));
5556 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5557 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5558 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5559 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5560 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5561 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5562 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5563 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5564 }
5565
5566 #[test]
5572 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5573 let mut opts = options();
5574 opts.emit = EmitKind::Ir;
5575 let source = concat!(
5576 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5577 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5578 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5579 );
5580 let messages = run(&opts, source).messages;
5581 assert_eq!(
5582 messages,
5583 [
5584 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5585 '__builtin_fpclassify' [E0687]",
5586 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5587 [E0511]",
5588 "/main.c:3:23: error: non-floating-point argument in call to function \
5589 '__builtin_fpclassify' [E0685]",
5590 ]
5591 );
5592 }
5593
5594 #[test]
5602 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5603 let text = ir(concat!(
5604 "double a = __builtin_inf();\n",
5605 "float b = __builtin_huge_valf();\n",
5606 "long double c = __builtin_infl();\n",
5607 "double d = __builtin_huge_val();\n",
5608 ));
5609 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5610 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5611 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5612 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5613 assert!(!text.contains("call"), "{text}");
5614 }
5615
5616 #[test]
5625 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5626 let text = ir(concat!(
5627 "double a = __builtin_nan(\"\");\n",
5628 "double b = __builtin_nan(\"0x1\");\n",
5629 "double c = __builtin_nan(\"010\");\n",
5631 "double d = __builtin_nans(\"\");\n",
5632 "double e = __builtin_nans(\"0x1\");\n",
5633 "float f = __builtin_nanf(\"0x1\");\n",
5634 "float g = __builtin_nansf(\"\");\n",
5635 "long double h = __builtin_nansl(\"\");\n",
5636 ));
5637 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5638 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5639 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5640 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5641 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5642 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5643 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5644 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5645
5646 let text = ir(concat!(
5649 "double f(const char *p) { return __builtin_nan(p); }\n",
5650 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5651 ));
5652 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5653 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5654 }
5655
5656 #[test]
5664 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5665 let text = ir(concat!(
5666 "unsigned long a = __builtin_strlen(\"hello\");\n",
5667 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5668 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5669 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5670 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5671 ));
5672 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5673 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5674 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5675 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5676 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5677 assert!(!text.contains("call"), "{text}");
5678
5679 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5681 assert!(text.contains("call @strlen("), "{text}");
5682 }
5683
5684 #[test]
5691 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5692 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5693 assert!(text.contains("bitcast.i64 %0"), "{text}");
5694 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5695 assert!(text.contains("and %1, %2"), "{text}");
5696 assert!(text.contains("bitcast.f64 %3"), "{text}");
5697 assert!(!text.contains("call"), "{text}");
5698
5699 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5700 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5701 assert!(text.contains("%8 = or %4, %7"), "{text}");
5702 assert!(!text.contains("call"), "{text}");
5703
5704 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5707 assert!(text.contains("bitcast.i80 %0"), "{text}");
5708 assert!(text.contains("bitcast.f80"), "{text}");
5709
5710 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5713 assert!(text.contains("fpext.f64 %0"), "{text}");
5714 assert!(text.contains("bitcast.i64 %1"), "{text}");
5715 }
5716
5717 #[test]
5726 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5727 let text =
5728 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5729 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5730 assert!(!text.contains("call"), "{text}");
5731
5732 let text =
5733 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5734 assert!(text.contains("bitcast.i32 %0"), "{text}");
5735 assert!(!text.contains("call"), "{text}");
5736
5737 let text = body(concat!(
5738 "double copysign(double x, double y);\n",
5739 "double f(double x, double y) { return copysign(x, y); }\n",
5740 ));
5741 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5742 assert!(!text.contains("call"), "{text}");
5743
5744 let text = body(concat!(
5745 "float copysignf(float x, float y);\n",
5746 "float f(float x, float y) { return copysignf(x, y); }\n",
5747 ));
5748 assert!(!text.contains("call"), "{text}");
5749
5750 let text = ir(concat!(
5754 "long double fabsl(long double x);\n",
5755 "long double f(long double x) { return fabsl(x); }\n",
5756 ));
5757 assert!(text.contains("call @fabsl"), "{text}");
5758 }
5759
5760 #[test]
5768 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5769 let taken = concat!(
5770 "static double fabs(double b) { return 7; }\n",
5771 "double f(double x) { return fabs(x); }\n",
5772 );
5773 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5774
5775 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5776 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5777
5778 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
5779 let mut opts = options();
5780 opts.emit = EmitKind::Ir;
5781 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
5782
5783 opts.builtins = false;
5784 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
5785
5786 opts.builtins = true;
5787 opts.no_builtin = vec!["fabs".to_owned()];
5788 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
5789 let one = concat!(
5790 "double copysign(double a, double b);\n",
5791 "double f(double x) { return copysign(x, 1.0); }\n",
5792 );
5793 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
5794
5795 opts.no_builtin = Vec::new();
5797 opts.builtins = false;
5798 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
5799 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
5800 }
5801
5802 #[test]
5811 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
5812 let text = ir(concat!(
5813 "double a = __builtin_fabs(-3.5);\n",
5814 "double b = __builtin_copysign(1.0, -0.0);\n",
5815 "double c = __builtin_copysign(0.0, -2.0);\n",
5816 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
5818 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
5819 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
5820 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
5821 "long double i = __builtin_fabsl(-__builtin_infl());\n",
5822 ));
5823 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
5824 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
5825 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
5826 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
5827 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
5828 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
5829 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
5830 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5831 }
5832
5833 #[test]
5841 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
5842 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
5843 assert!(!text.contains("call"), "{text}");
5844 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
5845 assert!(!text.contains("call"), "{text}");
5846
5847 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
5850 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5851 assert!(!text.contains("call"), "{text}");
5852 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
5853 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
5854
5855 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
5858 assert_eq!(written, text, "the name and the operator are the same thing");
5859
5860 let text = body(concat!(
5862 "double creal(_Complex double z);\n",
5863 "double f(_Complex double z) { return creal(z); }\n",
5864 ));
5865 assert!(!text.contains("call"), "{text}");
5866 let text = body(concat!(
5867 "_Complex float conjf(_Complex float z);\n",
5868 "_Complex float f(_Complex float z) { return conjf(z); }\n",
5869 ));
5870 assert_eq!(text.matches("fneg").count(), 1, "{text}");
5871 assert!(!text.contains("call"), "{text}");
5872
5873 let taken = concat!(
5876 "static double creal(_Complex double z) { return 7; }\n",
5877 "double f(_Complex double z) { return creal(z); }\n",
5878 );
5879 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
5880 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
5881 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
5882 let plain = concat!(
5883 "double cimag(_Complex double z);\n",
5884 "double f(_Complex double z) { return cimag(z); }\n",
5885 );
5886 let mut opts = options();
5887 opts.emit = EmitKind::Ir;
5888 opts.builtins = false;
5889 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
5890 opts.builtins = true;
5891 opts.no_builtin = vec!["cimag".to_owned()];
5892 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
5893
5894 let text = ir(concat!(
5896 "double a = __builtin_creal(1.5 + 2.5i);\n",
5897 "double b = __builtin_cimag(1.5 + 2.5i);\n",
5898 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
5899 ));
5900 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
5901 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
5902 assert!(
5903 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
5904 "the conjugate of a constant is the constant with the second half negated: {text}"
5905 );
5906 assert!(!text.contains("call"), "{text}");
5907 }
5908
5909 #[test]
5917 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
5918 let text = ir(concat!(
5919 "double a = __builtin_ceil(1.5);\n",
5920 "double b = __builtin_floor(1.5);\n",
5921 "double c = __builtin_trunc(-1.5);\n",
5922 "double d = __builtin_round(2.5);\n",
5925 "double e = __builtin_ceil(-0.5);\n",
5927 "double f = __builtin_fmax(1.0, 2.0);\n",
5928 "double g = __builtin_fmin(1.0, 2.0);\n",
5929 "float h = __builtin_ceilf(1.25f);\n",
5930 "double ceil(double x);\n",
5933 "double i = ceil(2.25);\n",
5934 ));
5935 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
5936 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
5937 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
5938 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
5939 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
5940 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
5941 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
5942 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
5943 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
5944 assert!(!text.contains("call"), "{text}");
5945 }
5946
5947 #[test]
5955 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
5956 let text = ir(concat!(
5957 "double f(double x) { return __builtin_ceil(x); }\n",
5958 "float g(float x) { return __builtin_floorf(x); }\n",
5959 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
5960 ));
5961 assert!(text.contains("call @ceil("), "{text}");
5962 assert!(text.contains("call @floorf("), "{text}");
5963 assert!(text.contains("call @fmax("), "{text}");
5964
5965 let text = ir(concat!(
5969 "double f(void) { return __builtin_rint(2.5); }\n",
5970 "double g(void) { return __builtin_nearbyint(2.5); }\n",
5971 ));
5972 assert!(text.contains("call @rint("), "{text}");
5973 assert!(text.contains("call @nearbyint("), "{text}");
5974
5975 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
5978 assert!(text.contains("call @fmin("), "{text}");
5979
5980 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
5983 let mut opts = options();
5984 opts.emit = EmitKind::Ir;
5985 opts.no_builtin = vec!["ceil".to_owned()];
5986 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
5987 }
5988
5989 #[test]
5996 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
5997 let text = ir(concat!(
5998 "constexpr int side = 4;\n",
5999 "constexpr int wider = side + 1;\n",
6000 "constexpr double half = 1.5;\n",
6001 "struct point { int x; int y; };\n",
6002 "constexpr struct point origin = { 5, 6 };\n",
6003 "int square[side * side];\n",
6004 "int rectangle[wider];\n",
6005 "int rounded[(int)half * 2];\n",
6006 "int across[origin.y];\n",
6007 "enum named { four = side };\n",
6008 "int e = four;\n",
6009 ));
6010 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6011 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6012 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6013 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6014 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6015
6016 let mut opts = options();
6019 opts.emit = EmitKind::Ir;
6020 let konst = "const int n = 1;\nint a[n];\n";
6021 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6022 assert_eq!(run(&opts, konst).messages, [message]);
6023
6024 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6026 assert_eq!(run(&opts, subscript).messages, [message]);
6027
6028 let address = "constexpr int c = 3;\nint *p = &c;\n";
6030 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6031 pointer target type [E0514]";
6032 assert_eq!(run(&opts, address).messages, [warning]);
6033 }
6034
6035 #[test]
6044 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
6045 let mut opts = options();
6048 opts.std = Std::C17;
6049 let source = concat!(
6050 "int add(a, b)\n",
6051 "int a;\n",
6052 "int b;\n",
6053 "{ return a + b; }\n",
6054 "int promoted(c)\n",
6055 "char c;\n",
6056 "{ return c; }\n",
6057 "int narrow(char);\n",
6058 "int narrow(c)\n",
6059 "char c;\n",
6060 "{ return c; }\n",
6061 "int first(a)\n",
6062 "int a[4];\n",
6063 "{ return a[0]; }\n",
6064 );
6065 let result = run(&opts, source);
6066 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6067 let text = result.text();
6068 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6069 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6070 assert!(text.contains("c : char object automatic defined"), "{text}");
6072 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
6073 assert!(text.contains("first : int(int *) function external defined"), "{text}");
6075 }
6076
6077 #[test]
6084 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
6085 let mut opts = options();
6086 opts.std = Std::C17;
6087 for (source, message) in [
6088 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
6089 (
6090 "int f(a)\nint a;\nint b;\n{ return a; }\n",
6091 "3:5: error: declaration for parameter 'b' but no such parameter",
6092 ),
6093 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
6094 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
6095 (
6096 "int f(a)\nstatic int a;\n{ return a; }\n",
6097 "2:12: error: storage class specified for parameter 'a'",
6098 ),
6099 (
6100 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
6101 "2:7: error: argument 'a' doesn't match prototype",
6102 ),
6103 ] {
6104 let result = run(&opts, source);
6105 assert!(result.failed(), "expected this to fail:\n{source}");
6106 assert!(result.messages[0].contains(message), "{:?}", result.messages);
6107 }
6108
6109 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
6112 let mut older = options();
6113 older.std = Std::C89;
6114 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
6115 let result = run(&opts, implicit);
6116 assert!(
6117 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
6118 "{:?}",
6119 result.messages
6120 );
6121
6122 let mut newer = options();
6126 newer.std = Std::C23;
6127 let plain = "int f(a)\nint a;\n{ return a; }\n";
6128 let result = run(&newer, plain);
6129 assert!(!result.failed(), "{:?}", result.messages);
6130 assert_eq!(
6131 result.messages,
6132 ["/main.c:1:5: warning: old-style function definition [E0412]"]
6133 );
6134 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
6135 }
6136
6137 #[test]
6144 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
6145 let array = "int a[8] = { [3] 7 };\n";
6146 let member = "struct s { int x; } v = { x: 7 };\n";
6147 for source in [array, member] {
6148 let result = run(&options(), source);
6149 assert!(!result.failed(), "{:?}", result.messages);
6150 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
6151 }
6152
6153 let mut asked = options();
6154 asked.pedantic = true;
6155 assert_eq!(
6156 run(&asked, array).messages,
6157 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
6158 );
6159 assert_eq!(
6160 run(&asked, member).messages,
6161 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
6162 );
6163 }
6164
6165 #[test]
6172 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
6173 let text = ir(concat!(
6174 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
6175 "struct brim { char buf[9223372036854775807L]; };\n",
6176 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
6177 "unsigned long h = sizeof(struct huge_struct);\n",
6178 "unsigned long b = sizeof(struct brim);\n",
6179 "unsigned long y = sizeof(struct bitty);\n",
6180 ));
6181 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6182 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6183 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6184
6185 let mut opts = options();
6186 opts.emit = EmitKind::Ir;
6187 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6188 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6189 assert_eq!(run(&opts, over).messages, [message]);
6190 let array = "struct wide { short buf[1L << 62]; };\n";
6191 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6192 maximum object size '9223372036854775807' [E0537]";
6193 assert_eq!(run(&opts, array).messages[0], message);
6194 }
6195
6196 fn compile_bytes(source: &[u8]) -> Compiled {
6201 let mut opts = options();
6202 opts.emit = EmitKind::Ir;
6203 let mut fs = MemoryFileSystem::new();
6204 fs.insert("/main.c", source.to_vec());
6205 compile(&opts, "/main.c", &fs)
6206 }
6207
6208 #[test]
6215 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6216 let mut source = b"char s[] = \"a".to_vec();
6217 source.push(0xff);
6218 source.extend_from_slice(b"b\";\nchar c = '");
6219 source.push(0xff);
6220 source.extend_from_slice(b"';\n");
6221 let result = compile_bytes(&source);
6222 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6223 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6224 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6226
6227 let mut stray = b"int a".to_vec();
6228 stray.push(0xff);
6229 stray.extend_from_slice(b" = 1;\n");
6230 let result = compile_bytes(&stray);
6231 assert!(
6232 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6233 "{:?}",
6234 result.messages
6235 );
6236 }
6237
6238 #[test]
6239 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6240 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6241 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6242 let expected = "\
6243func @add(i32, i32) -> i32, linkage(external) {
6244block0(%0: i32, %1: i32):
6245 %2 = add.nsw %0, %1
6246 return %2
6247}
6248";
6249 assert!(text.contains(expected), "{text}");
6250 }
6251
6252 #[test]
6253 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6254 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6255 assert!(!text.contains("alloca"), "{text}");
6256 assert!(!text.contains("load"), "{text}");
6257 assert!(!text.contains("store"), "{text}");
6258 }
6259
6260 #[test]
6261 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6262 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6263 let expected = "\
6264block0:
6265 %0 = alloca, size 4, align 4
6266 %1 = iconst.i32 1
6267 store %1 -> %0, align 4, tbaa !1
6268 %2 = call @g(%0) : (ptr) -> i32
6269 return %2
6270";
6271 assert_eq!(text, expected);
6272 }
6273
6274 #[test]
6275 fn a_loop_carries_what_it_changes_as_block_parameters() {
6276 let text = body(
6279 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6280 return total;\n}\n",
6281 );
6282 assert!(!text.contains("alloca"), "{text}");
6283 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6284 assert!(text.contains("jump block1("), "{text}");
6285 }
6286
6287 #[test]
6288 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6289 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6290 assert!(text.contains("icmp slt %0, %1"), "{text}");
6291 assert!(!text.contains("zext"), "{text}");
6292 }
6293
6294 #[test]
6295 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6296 let text = body("int f(int a, int b) { return a && b; }\n");
6297 let expected = "\
6298block0(%0: i32, %1: i32):
6299 %2 = iconst.i32 0
6300 %3 = icmp ne %0, %2
6301 %4 = iconst.i1 0
6302 br_if %3, block1, block2(%4)
6303
6304block1:
6305 %5 = iconst.i32 0
6306 %6 = icmp ne %1, %5
6307 jump block2(%6)
6308
6309block2(%7: i1):
6310 %8 = zext.i32 %7
6311 return %8
6312";
6313 assert_eq!(text, expected);
6314 }
6315
6316 #[test]
6317 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6318 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6319 assert!(!text.contains("block3"), "{text}");
6322 assert!(!text.contains("iconst.i32 3"), "{text}");
6323 }
6324
6325 #[test]
6326 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6327 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6328 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6329 assert!(body("int f(void) { }\n").contains("unreachable"));
6330 }
6331
6332 #[test]
6333 fn a_structure_is_copied_rather_than_held_in_a_value() {
6334 let text = body(
6335 "struct point { int x, y; };\n\
6336 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6337 );
6338 assert!(text.contains("memcpy"), "{text}");
6339 }
6340
6341 #[test]
6342 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6343 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6344 assert!(text.contains("memset"), "{text}");
6345 }
6346
6347 #[test]
6348 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6349 let text = body(
6350 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6351 default: r = 4; } return r; }\n",
6352 );
6353 let expected = "\
6354block0(%0: i32):
6355 %1 = iconst.i32 0
6356 switch %0, block1, [1 => block2, 2 => block3(%1)]
6357
6358block1:
6359 %2 = iconst.i32 4
6360 jump block4(%2)
6361
6362block2:
6363 %3 = iconst.i32 1
6364 jump block3(%3)
6365
6366block3(%4: i32):
6367 %5 = iconst.i32 2
6368 %6 = add.nsw %4, %5
6369 jump block4(%6)
6370
6371block4(%7: i32):
6372 return %7
6373";
6374 assert_eq!(text, expected);
6375 }
6376
6377 #[test]
6378 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6379 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6382 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6383 assert!(text.contains("icmp ule"), "{text}");
6384 assert!(!text.contains("switch"), "{text}");
6385 }
6386
6387 #[test]
6388 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6389 let text = body(
6390 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6391 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6392 );
6393 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6396 assert!(text.contains("block5:\n jump block7("), "{text}");
6397 assert!(text.contains("block6:\n jump block8("), "{text}");
6398 }
6399
6400 #[test]
6401 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6402 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6403 }
6404
6405 #[test]
6406 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6407 let text = body(
6412 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6413 return n; }\n",
6414 );
6415 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6418 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6419 assert!(text.contains("block4:\n jump block3("), "{text}");
6420 }
6421
6422 #[test]
6423 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6424 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6427 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6428 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6429 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6430 }
6431
6432 #[test]
6433 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6434 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6435 assert!(!text.contains("alloca"), "{text}");
6439 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6440 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6441 }
6442
6443 #[test]
6444 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6445 let text =
6446 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6447 assert!(!text.contains("alloca"), "{text}");
6448 assert!(text.contains("block1(%2: i32):"), "{text}");
6449 assert!(text.contains("jump block1(%5)"), "{text}");
6450 }
6451
6452 #[test]
6453 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6454 assert_eq!(
6457 body("int f(int x) { return x; spare: return 0; }\n"),
6458 "block0(%0: i32):\n return %0\n"
6459 );
6460 }
6461
6462 #[test]
6463 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6464 let text = body(
6465 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6466 );
6467 assert_eq!(
6470 text,
6471 "\
6472block0(%0: ptr):
6473 %1 = load.i8 %0, align 1
6474 %2 = iconst.i8 3
6475 %3 = ashr %1, %2
6476 %4 = sext.i32 %3
6477 return %4
6478"
6479 );
6480 }
6481
6482 #[test]
6483 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6484 let text =
6488 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6489 assert_eq!(
6490 text,
6491 "\
6492block0(%0: ptr, %1: i32):
6493 %2 = iconst.i32 16777215
6494 %3 = and %1, %2
6495 %4 = trunc.i16 %3
6496 store %4 -> %0, align 2
6497 %5 = iconst.i32 16
6498 %6 = lshr %3, %5
6499 %7 = trunc.i8 %6
6500 %8 = iconst.i64 2
6501 %9 = ptr_add %0, %8
6502 store %7 -> %9, align 1
6503 return
6504"
6505 );
6506 }
6507
6508 #[test]
6509 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6510 let text =
6511 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6512 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6515 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6516 }
6517
6518 #[test]
6519 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6520 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6523 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6524 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6525 }
6526
6527 #[test]
6528 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6529 let text = body(
6533 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6534 );
6535 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6536 }
6537
6538 #[test]
6539 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6540 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6543 assert!(
6544 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6545 "{text}"
6546 );
6547 }
6548
6549 #[test]
6550 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6551 let text = ir(concat!(
6556 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6557 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6558 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6559 "char s[2] = \"hi\";\n",
6560 ));
6561 assert!(
6562 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6563 "{text}"
6564 );
6565 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6566 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6567 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6570 }
6571
6572 #[test]
6573 fn a_definition_takes_a_parameter_it_left_unnamed() {
6574 let text = ir("int f(int a, int) { return a; }\n");
6578 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6579 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6580
6581 let text = ir("int g(int, int n) { return n; }\n");
6584 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6585 }
6586
6587 #[test]
6588 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6589 let text = body(concat!(
6594 "struct s { int f; int g; };\n",
6595 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6596 "{ *d = *e = a[0] = *c; }\n",
6597 ));
6598 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6599 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6600 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6601 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6602 }
6603
6604 #[test]
6605 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6606 let mut opts = options();
6611 opts.emit = EmitKind::Ir;
6612 let result = run(
6613 &opts,
6614 concat!(
6615 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6616 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6617 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6618 "const union u c = { { \"1234\", \"567\" } };\n",
6619 ),
6620 );
6621 let text = result.text();
6622 assert_eq!(
6623 result.messages,
6624 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6625 (5 chars into 3 available) [E0637]"]
6626 );
6627 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6628 assert!(
6629 text.contains(
6630 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6631 bytes \"9\\00\", zero 3 }"
6632 ),
6633 "{text}"
6634 );
6635 assert!(
6638 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6639 "{text}"
6640 );
6641 }
6642
6643 #[test]
6644 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6645 let text = body(concat!(
6649 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6650 "void g(struct v *);\n",
6651 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6652 ));
6653 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6654 }
6655
6656 #[test]
6657 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6658 let text = ir(concat!(
6663 "struct s { int x; };\n",
6664 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6665 "int n = (int){ 7 };\n",
6666 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6667 ));
6668 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6669 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6670 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6673 }
6674
6675 #[test]
6676 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6677 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6681 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6682 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6683 }
6684
6685 #[test]
6686 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6687 let text = ir("unsigned char foo[1][0];\n");
6691 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6692 }
6693
6694 #[test]
6695 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
6696 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
6699 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
6700 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
6701 }
6702
6703 #[test]
6704 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
6705 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
6709 assert!(
6710 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
6711 "{text}"
6712 );
6713 }
6714
6715 #[test]
6716 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
6717 let text = body(
6722 "\
6723struct s { int a, b; };
6724struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
6725",
6726 );
6727 assert!(text.contains("block3(%7: ptr)"), "{text}");
6729 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
6730 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
6731 }
6732
6733 #[test]
6741 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
6742 let text = body("int f(int i) { return ++i ?: 10; }\n");
6743 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
6744 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6745
6746 let text = body("long f(int i) { return ++i ?: 10L; }\n");
6749 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
6750 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6751
6752 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
6754 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
6755
6756 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
6759 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
6760 }
6761
6762 #[test]
6763 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
6764 let text = ir("\
6768struct pair { int a, b; };
6769struct pair make(int a, int b);
6770struct pair twice(struct pair p) { return make(p.a, p.b); }
6771");
6772 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
6773 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
6774 }
6775
6776 #[test]
6777 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
6778 let text = ir("\
6782struct big { double v[8]; };
6783struct big grow(struct big b);
6784struct big twice(struct big b) { return grow(grow(b)); }
6785");
6786 assert!(
6787 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
6788 "{text}"
6789 );
6790 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
6791 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
6794 }
6795
6796 #[test]
6797 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
6798 let text = ir("\
6803struct big { double v[8]; };
6804struct pair { int a, b; };
6805int p(const char *, ...);
6806int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
6807");
6808 assert!(
6809 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
6810 "{text}"
6811 );
6812 }
6813
6814 #[test]
6815 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
6816 let body = body(
6819 "\
6820struct pair { int a, b; };
6821struct pair make(int a, int b);
6822int second(void) { return make(1, 2).b; }
6823",
6824 );
6825 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
6826 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
6827 }
6828
6829 #[test]
6830 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
6831 let source = "\
6835struct hfa { float x, y, z; };
6836int take(struct hfa h);
6837int give(struct hfa h) { return take(h); }
6838";
6839 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
6840 let mut opts = options();
6841 opts.emit = EmitKind::Ir;
6842 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
6843 let result = run(&opts, source);
6844 assert_eq!(result.messages, Vec::<String>::new());
6845 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
6846 }
6847
6848 #[test]
6849 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
6850 let source = "\
6853int use(int *);
6854void f(int n) {
6855 {
6856 int a[n];
6857 use(a);
6858 }
6859 use(0);
6860}
6861";
6862 let body = body(source);
6863 assert!(body.contains("mul.nsw"), "{body}");
6864 assert!(body.contains("stacksave"), "{body}");
6865 assert!(body.contains("alloca %"), "{body}");
6866 assert!(body.contains("stackrestore"), "{body}");
6867 }
6868
6869 #[test]
6870 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
6871 let source = "\
6876int use(int *);
6877int f(int n) {
6878 {
6879 int a[n];
6880 if (use(a)) goto out;
6881 use(0);
6882 }
6883out:
6884 return 0;
6885}
6886";
6887 let body = body(source);
6888 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
6890 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6891 assert!(after.starts_with(" %4\n jump block"), "{body}");
6892 }
6893
6894 #[test]
6895 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
6896 let source = "\
6900int use(int *);
6901int f(int n) {
6902 int a[n];
6903again:
6904 if (use(a)) goto again;
6905 return 0;
6906}
6907";
6908 let body = body(source);
6909 assert!(body.contains("stacksave"), "{body}");
6910 assert!(!body.contains("stackrestore"), "{body}");
6911 }
6912
6913 #[test]
6914 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
6915 let source = "\
6920int use(int *);
6921int f(int n) {
6922again:
6923 {
6924 int a[n];
6925 if (use(a)) goto again;
6926 }
6927 return 0;
6928}
6929";
6930 let body = body(source);
6931 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6932 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6933 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
6934 }
6935
6936 #[test]
6937 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
6938 let source = "\
6944int f(void);
6945void t(void) {
6946 int count = 10;
6947 for (; count--;) {
6948 int b[f()];
6949 int i;
6950 for (i = 0; i < f(); i++) {
6951 b[i] = count;
6952 }
6953 }
6954}
6955";
6956 let body = body(source);
6957 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
6961 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
6962 let next = after.split("\n\n").next().expect("the block the restore is in");
6965 assert!(next.contains("jump block1("), "{body}");
6966 }
6967
6968 #[test]
6969 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
6970 let source = "\
6973unsigned long f(int n) {
6974 int a[n];
6975 n = 0;
6976 return sizeof a;
6977}
6978";
6979 let body = body(source);
6980 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
6982 }
6983
6984 #[test]
6985 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
6986 let source = "\
6989int use(int);
6990int f(int x) {
6991 return ({
6992 int t = use(x);
6993 t * t;
6994 });
6995}
6996";
6997 let expected = "\
6998block0(%0: i32):
6999 %1 = call @use(%0) : (i32) -> i32
7000 %2 = mul.nsw %1, %1
7001 return %2
7002";
7003 assert_eq!(body(source), expected);
7004 }
7005
7006 #[test]
7007 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
7008 let source = "int f(int x) { return ({ return x; 0; }); }\n";
7012 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
7013 }
7014
7015 #[test]
7016 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
7017 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
7021 let expected = "\
7022block0(%0: ptr):
7023 %1 = va_arg.f64 %0
7024 %2 = va_arg.f64 %0
7025 %3 = fadd %1, %2
7026 return %3
7027";
7028 assert_eq!(body(source), expected);
7029 }
7030
7031 #[test]
7032 fn one_that_reads_a_structure_answers_where_the_object_is() {
7033 let source = "\
7047struct s { int a; long b; };
7048long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
7049";
7050 let expected = "\
7051block0(%0: ptr):
7052 %1 = alloca, size 16, align 16
7053 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
7054 memcpy %1, %2, size 16, align 8
7055 %3 = iconst.i64 8
7056 %4 = ptr_add %1, %3
7057 %5 = load.i64 %4, align 8, tbaa !1
7058 return %5
7059";
7060 assert_eq!(body(source), expected);
7061 }
7062
7063 #[test]
7067 fn the_classification_says_which_registers_the_object_arrived_in() {
7068 let source = "\
7069struct s { double a; double b; };
7070double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
7071";
7072 assert!(
7073 body(source)
7074 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
7075 "{}",
7076 body(source)
7077 );
7078
7079 let big = "\
7080struct s { long a[4]; };
7081long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
7082";
7083 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
7084 }
7085
7086 #[test]
7087 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
7088 let source = "\
7092int f(int c) {
7093 void *p = c ? &&one : &&two;
7094 goto *p;
7095one:
7096 return 1;
7097two:
7098 return 2;
7099}
7100";
7101 let expected = "\
7102block0(%0: i32):
7103 %1 = iconst.i32 0
7104 %2 = icmp ne %0, %1
7105 br_if %2, block1, block2
7106
7107block1:
7108 %3 = block_addr block3
7109 jump block4(%3)
7110
7111block2:
7112 %4 = block_addr block5
7113 jump block4(%4)
7114
7115block3:
7116 %5 = iconst.i32 1
7117 return %5
7118
7119block4(%6: ptr):
7120 indirect_br %6, block3, block5
7121
7122block5:
7123 %7 = iconst.i32 2
7124 return %7
7125";
7126 assert_eq!(body(source), expected);
7127 }
7128
7129 #[test]
7130 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
7131 let source = "void **next(void);
7134void f(void) { goto *next(); }
7135";
7136 let expected = "\
7137block0:
7138 %0 = call @next() : () -> ptr
7139 unreachable
7140";
7141 assert_eq!(body(source), expected);
7142 }
7143
7144 #[test]
7145 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
7146 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
7149 let expected = "\
7150block0:
7151 inline_asm.volatile \"mfence\", \"\", \"memory\"()
7152 return
7153";
7154 assert_eq!(body(source), expected);
7155 }
7156
7157 #[test]
7158 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
7159 let source = "\
7162int f(int x, int y) {
7163 int r;
7164 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
7165 return r + y;
7166}
7167";
7168 let expected = "\
7169block0(%0: i32, %1: i32):
7170 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
7171 %4 = add.nsw %2, %3
7172 return %4
7173";
7174 assert_eq!(body(source), expected);
7175 }
7176
7177 #[test]
7178 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
7179 let source = "\
7184struct pair { int a, b; };
7185int f(int x) {
7186 int slot = x;
7187 struct pair p = { x, x };
7188 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7189 return slot + p.a;
7190}
7191";
7192 let text = body(source);
7193 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7194 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7195 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7196 }
7197
7198 #[test]
7199 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7200 let source = "\
7205int f(int x) {
7206 int r = 7;
7207 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7208 return r;
7209away:
7210 return r;
7211}
7212";
7213 let expected = "\
7214block0(%0: i32):
7215 %1 = iconst.i32 7
7216 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7217
7218block1:
7219 return %2
7220
7221block2:
7222 return %1
7223";
7224 assert_eq!(body(source), expected);
7225 }
7226
7227 #[test]
7228 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7229 let mut opts = options();
7233 opts.emit = EmitKind::Ir;
7234 for (source, expected) in [
7235 (
7236 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7237 "output operand constraint lacks '='",
7238 ),
7239 (
7240 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7241 "lvalue required in 'asm' statement",
7242 ),
7243 (
7244 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7245 "read-only variable 'g' used as 'asm' output",
7246 ),
7247 (
7248 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7249 "input operand constraint contains '='",
7250 ),
7251 (
7252 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7253 "memory input 0 is not directly addressable",
7254 ),
7255 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7256 (
7257 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7258 "duplicate asm operand name 'a'",
7259 ),
7260 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7261 ] {
7262 let result = run(&opts, source);
7263 assert!(result.failed(), "expected this to be reported:\n{source}");
7264 assert!(
7265 result.messages.iter().any(|m| m.contains(expected)),
7266 "{expected}\n{:?}",
7267 result.messages
7268 );
7269 }
7270 }
7271
7272 #[test]
7277 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7278 let text = ir(concat!(
7279 "__asm__(\n",
7280 " \".section .rodata\\n\"\n",
7281 " \".globl first\\n\"\n",
7282 " \".balign 8\\n\"\n",
7283 " \"first:\\n\"\n",
7284 " \".long 1\\n\"\n",
7285 " \".long 2\\n\"\n",
7286 " \".globl last\\n\"\n",
7287 " \"last:\\n\"\n",
7288 " \".quad last - first\\n\");\n",
7289 "extern const int first[];\n",
7290 "extern const long last;\n",
7291 ));
7292 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7293 assert!(text.contains("global @last : i64 = 8"), "{text}");
7294 }
7295
7296 #[test]
7300 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7301 let text = ir(concat!(
7302 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7303 "extern int counter;\n",
7304 "int read(void) { return counter; }\n",
7305 ));
7306 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7307 }
7308
7309 #[test]
7312 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7313 let mut opts = options();
7314 opts.emit = EmitKind::Ir;
7315 let mut fs = MemoryFileSystem::new();
7316 fs.insert(
7317 "/main.c",
7318 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7319 );
7320 fs.insert("seed", b"hi".to_vec());
7321 let result = compile(&opts, "/main.c", &fs);
7322 assert_eq!(result.messages, Vec::<String>::new());
7323 let text = result.text();
7324 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7325 }
7326
7327 #[test]
7330 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7331 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7332 assert!(
7333 messages
7334 .iter()
7335 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7336 "{messages:?}"
7337 );
7338 }
7339
7340 #[test]
7343 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7344 for source in [
7345 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7346 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7347 ] {
7348 let messages = errors(source);
7349 assert!(
7350 messages
7351 .iter()
7352 .any(|m| m.contains("not supported yet")
7353 && m.contains("in an `asm` at file scope")),
7354 "{source}\n{messages:?}"
7355 );
7356 }
7357 }
7358
7359 #[test]
7360 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7361 let mut opts = options();
7362 opts.emit = EmitKind::Ir;
7363 for source in [
7364 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7365 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7366 ] {
7367 let result = run(&opts, source);
7368 assert!(result.failed(), "expected this to be reported:\n{source}");
7369 assert!(
7370 result.messages.iter().any(|m| m.contains("not supported yet")),
7371 "{:?}",
7372 result.messages
7373 );
7374 }
7375 }
7376
7377 fn round_trip(source: &str) -> (String, String) {
7379 let printed = ir(source);
7380 let mut opts = options();
7381 opts.emit = EmitKind::Ir;
7382 let mut fs = MemoryFileSystem::new();
7383 fs.insert("/main.ir", printed.clone().into_bytes());
7384 let result = compile_ir(&opts, "/main.ir", &fs);
7385 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7386 (printed, result.text().to_owned())
7387 }
7388
7389 #[test]
7390 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7391 let (printed, again) = round_trip(
7395 "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",
7396 );
7397 assert_eq!(printed, again);
7398 }
7399
7400 #[test]
7401 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7402 let mut opts = options();
7403 opts.emit = EmitKind::Ir;
7404 let mut fs = MemoryFileSystem::new();
7405 let text = "\
7406; ModuleID = 'a.c'
7407; format 0
7408target triple = \"x86_64-unknown-linux-gnu\"
7409target datalayout = \"e-p:64:64-i64:64-S128\"
7410
7411func @f(), linkage(external) {
7412block0:
7413 frobnicate
7414}
7415";
7416 fs.insert("/main.ir", text.as_bytes().to_vec());
7417 let result = compile_ir(&opts, "/main.ir", &fs);
7418 assert!(result.failed());
7419 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7420 }
7421
7422 #[test]
7423 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7424 let mut opts = options();
7427 opts.emit = EmitKind::Ir;
7428 let mut fs = MemoryFileSystem::new();
7429 let text = "\
7430; ModuleID = 'a.c'
7431; format 0
7432target triple = \"x86_64-unknown-linux-gnu\"
7433target datalayout = \"e-p:64:64-i64:64-S128\"
7434
7435func @f(), linkage(external) {
7436block0:
7437 %0 = iconst.i32 1
7438 return %0
7439}
7440";
7441 fs.insert("/main.ir", text.as_bytes().to_vec());
7442 let result = compile_ir(&opts, "/main.ir", &fs);
7443 assert!(result.failed());
7444 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7445 }
7446
7447 #[test]
7448 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7449 let mut fs = MemoryFileSystem::new();
7451 fs.insert("/main.ir", Vec::new());
7452 let result = compile_ir(&options(), "/main.ir", &fs);
7453 assert!(result.failed());
7454 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7455 }
7456
7457 #[test]
7458 fn the_printed_ir_reads_back_as_the_same_module() {
7459 let text = ir("\
7462struct point { int x, y; };
7463static const char greeting[] = \"hi\";
7464int table[4] = { 1, 2, 3 };
7465int puts(const char *);
7466double half(double x) { return x / 2.0; }
7467int f(int n) {
7468 int total = 0;
7469 for (int i = 0; i < n; i++) {
7470 if (i == 3) continue;
7471 total += table[i];
7472 }
7473 switch (n) {
7474 case 0: total = 1;
7475 case 1: total++; break;
7476 default: total = -total;
7477 }
7478 struct point p = { total, 1 };
7479 int *q = &p.y;
7480 puts(greeting);
7481 return p.x + *q;
7482}
7483int dispatch(int c) {
7484 void *p = c ? &&one : &&two;
7485 goto *p;
7486one:
7487 return 1;
7488two:
7489 return 2;
7490}
7491int assembly(int x, int *p) {
7492 int r;
7493 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7494 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7495 return r;
7496away:
7497 return 0;
7498}
7499");
7500 let mut names = Interner::new();
7501 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7502 assert_eq!(rucc_ir::print(&module, &names), text);
7503 }
7504
7505 #[test]
7506 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7507 let mut opts = options();
7511 opts.emit = EmitKind::Object;
7512 opts.save_temps = rucc_session::SaveTemps::Object;
7513 let result = run(&opts, "#define N 2\nint a[N];\n");
7514 assert_eq!(result.messages, Vec::<String>::new());
7515 let text = result.temps.preprocessed.expect("the preprocessed text");
7516 assert!(text.contains("int a[2];"), "{text}");
7517 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7518 let asm = result.temps.assembly.expect("the assembly");
7519 assert!(asm.contains("a:"), "{asm}");
7520 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7521 }
7522
7523 #[test]
7524 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7525 let mut opts = options();
7528 opts.emit = EmitKind::Object;
7529 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7530 }
7531
7532 #[test]
7533 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7534 let mut opts = options();
7537 opts.emit = EmitKind::Ir;
7538 opts.save_temps = rucc_session::SaveTemps::Cwd;
7539 let result = run(&opts, "int a;\n");
7540 assert!(result.temps.preprocessed.is_some());
7541 assert_eq!(result.temps.assembly, None);
7542 }
7543}