1use std::path::Path;
14
15use rucc_base::Interner;
16use rucc_codegen::coverage::Fired;
17use rucc_codegen::elsewhere::Elsewhere;
18use rucc_codegen::lowering::Lowerings;
19use rucc_codegen::pipeline::{self, Machine, Recording};
20use rucc_codegen::pressure::Pressure;
21use rucc_cost::Goal;
22use rucc_diag::{Diagnostic, Severity, Span};
23use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
24use rucc_lex::{Convert, Keywords, PpToken, convert};
25use rucc_lower::Protector as LowerProtector;
26use rucc_sema::{Checker, Context as CheckContext};
27use rucc_session::{
28 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
29};
30use rucc_target::TargetInfo;
31use rucc_tuple::{Arch, ObjectFormat};
32
33use crate::preprocess::render;
34
35#[derive(Debug, Clone, PartialEq, Eq, Default)]
42pub enum Artifact {
43 #[default]
46 Nothing,
47 Text(String),
49 Object {
56 bytes: Vec<u8>,
58 defines: Vec<String>,
62 },
63}
64
65impl Artifact {
66 #[must_use]
68 pub fn bytes(&self) -> &[u8] {
69 match self {
70 Artifact::Nothing => &[],
71 Artifact::Text(text) => text.as_bytes(),
72 Artifact::Object { bytes, .. } => bytes,
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct Compiled {
80 pub artifact: Artifact,
82 pub messages: Vec<String>,
84 pub errors: u32,
86 pub fired: Fired,
92 pub pressure: Pressure,
97 pub lowerings: Lowerings,
102 pub dumps: Vec<rucc_opt::Dump>,
108 pub remarks: String,
114 pub deps: Vec<rucc_pp::Dependency>,
119 pub temps: Temps,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Default)]
135pub struct Temps {
136 pub preprocessed: Option<String>,
138 pub assembly: Option<String>,
140}
141
142impl Compiled {
143 #[must_use]
145 pub fn failed(&self) -> bool {
146 self.errors > 0
147 }
148
149 #[must_use]
154 pub fn text(&self) -> &str {
155 match &self.artifact {
156 Artifact::Text(text) => text,
157 _ => "",
158 }
159 }
160}
161
162#[must_use]
175pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
176 let mut sess = Session::new(opts.clone());
177 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
181 let mut diagnostics: Vec<Diagnostic> = Vec::new();
182 let mut fired = Fired::new();
184 let mut pressure = Pressure::new();
186 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
187 let mut dumps = Vec::new();
189 let mut remarks = String::new();
190 let mut temps = Temps::default();
192
193 let bytes = match fs.read(Path::new(name)) {
194 Ok(bytes) => bytes,
195 Err(e) => return failure(format!("{name}: {e}")),
196 };
197 let Ok(file) = sess.sources.add_shared(name, bytes, None) else {
198 return failure(format!("{name}: the source map has no room left for this file"));
199 };
200
201 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
205 let predef = rucc_pp::Predef::for_options(opts);
206 let expanded: Vec<PpToken> = {
207 let mut tokens = Vec::new();
208 {
213 let mut cx =
214 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
215 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
216 cx.pedantic = opts.pedantic;
217 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
218 return failure(format!(
219 "{name}: the source map has no room for the built in macros"
220 ));
221 }
222 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
223 return failure(format!("{name}: the source map has no room for the command line"));
224 }
225 tokens.append(&mut pp.run(file, &mut cx));
226 }
227 if opts.save_temps.wanted() {
228 temps.preprocessed = Some(rucc_pp::print(
229 file,
230 &tokens,
231 pp.line_directives(),
232 &sess.sources,
233 &sess.interner,
234 rucc_pp::PrintOptions { line_markers: opts.line_markers },
235 ));
236 }
237 tokens.iter().map(|token| token.to_pp()).collect()
238 };
239 diagnostics.extend(pp.take_diagnostics());
240 let deps = pp.dependencies().to_vec();
243
244 let cx = Convert {
247 keywords: &keywords,
248 interner: &sess.interner,
249 target: &sess.target,
250 std: opts.std,
251 gnu: opts.gnu_extensions,
252 pedantic: opts.pedantic,
253 };
254 let (tokens, complaints) = convert(&expanded, &cx);
255 diagnostics.extend(complaints);
256
257 let parsed = rucc_parse::parse(
258 &tokens,
259 rucc_parse::Context {
260 interner: &sess.interner,
261 std: opts.std,
262 gnu: opts.gnu_extensions,
263 pedantic: opts.pedantic,
264 error_limit: opts.error_limit as usize,
265 },
266 );
267 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
268 diagnostics.extend(parsed.diagnostics);
269
270 let mut artifact = Artifact::Nothing;
271 let mut instrumented = Instrumented::default();
274 if !parse_failed {
275 let mut checker = Checker::new(
276 &parsed.ast,
277 CheckContext {
278 names: &sess.interner,
279 target: &sess.target,
280 std: opts.std,
281 gnu: opts.gnu_extensions,
282 pedantic: opts.pedantic,
283 permissive: opts.permissive,
284 gnu89_inline: opts.gnu89_inline,
285 error_limit: opts.error_limit as usize,
286 builtins: opts.builtins && opts.hosted,
289 no_builtin: &opts.no_builtin,
290 short_enums: opts.short_enums,
291 ms_extensions: sess.ms_extensions(),
292 trapping_math: opts.trapping_math,
293 },
294 );
295 checker.check_unit();
296 let checked = checker.finish();
297 if !checked.failed() {
298 match opts.emit {
299 EmitKind::Tast => {
300 artifact = Artifact::Text(rucc_sema::print(
301 &checked.tast,
302 &checked.types,
303 &sess.interner,
304 ));
305 }
306 EmitKind::TypeGranules => {
310 artifact = Artifact::Text(rucc_types::granule_report(
311 &checked.types,
312 &sess.interner,
313 &sess.target,
314 ));
315 }
316 EmitKind::Ir
317 | EmitKind::MirFinal
318 | EmitKind::Asm
319 | EmitKind::Object
320 | EmitKind::Archive
321 | EmitKind::Executable
322 | EmitKind::SafetySummary => {
323 let mut read = |named: &str| {
328 fs.read(Path::new(named))
329 .map(|bytes| bytes.as_slice().to_vec())
330 .map_err(|why| why.to_string())
331 };
332 let mut lowered = rucc_lower::lower(
333 name,
334 rucc_lower::Context {
335 tast: &checked.tast,
336 types: &checked.types,
337 target: &sess.target,
338 names: &mut sess.interner,
339 visibility: match opts.visibility {
340 Visibility::Default => IrVisibility::Default,
341 Visibility::Hidden => IrVisibility::Hidden,
342 Visibility::Protected => IrVisibility::Protected,
343 },
344 protector: match opts.protector {
345 Protector::None => LowerProtector::None,
346 Protector::Buffers => LowerProtector::Buffers,
347 Protector::Strong => LowerProtector::Strong,
348 Protector::All => LowerProtector::All,
349 },
350 wrapping: rucc_lower::Wrapping {
351 signed: opts.wrapping.signed,
352 pointer: opts.wrapping.pointer,
353 trap: opts.wrapping.trap,
354 },
355 aliasing: opts.strict_aliasing,
356 padding: opts.padding == Padding::Ignored,
357 contract: match opts.fp_contract {
358 Contract::Off => FpContract::Off,
359 Contract::On => FpContract::On,
360 Contract::Fast => FpContract::Fast,
361 },
362 read: &mut read,
363 },
364 );
365 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
369 if !failed {
370 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
375 for error in errors {
376 diagnostics.push(internal(&format!("invalid IR, {error}")));
377 }
378 } else if let Err(complaints) =
379 instrument(&mut lowered.module, &mut sess.interner, opts)
380 .map(|done| instrumented = done)
381 {
382 diagnostics.extend(complaints);
383 } else if let Err(complaints) = optimize(
384 &mut lowered.module,
385 &sess.interner,
386 &sess.target,
387 opts,
388 name,
389 &mut dumps,
390 &mut remarks,
391 ) {
392 diagnostics.extend(complaints);
393 } else if opts.emit == EmitKind::SafetySummary {
394 artifact = Artifact::Text(
399 rucc_safety::summarize(
400 &lowered.module,
401 &sess.interner,
402 name,
403 opts.safety.as_str(),
404 instrumented.checks,
405 instrumented.interposed,
406 instrumented.crossings,
407 )
408 .render(),
409 );
410 } else if opts.emit == EmitKind::Ir {
411 artifact =
416 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
417 } else {
418 match generate(
421 &mut lowered.module,
422 &mut sess.interner,
423 &sess.target,
424 opts,
425 &mut Recording {
426 fired: &mut fired,
427 pressure: &mut pressure,
428 lowerings: &mut lowerings,
429 },
430 &mut temps.assembly,
431 ) {
432 Ok(made) => artifact = made,
433 Err(complaints) => diagnostics.extend(complaints),
434 }
435 }
436 }
437 diagnostics.extend(lowered.diagnostics);
438 }
439 _ => {}
440 }
441 }
442 diagnostics.extend(checked.diagnostics);
443 }
444
445 let mut messages = Vec::with_capacity(diagnostics.len());
446 let mut errors = 0;
447 for diag in &diagnostics {
448 if !opts.warnings && diag.severity == Severity::Warning {
452 continue;
453 }
454 if diag.severity.is_fatal()
455 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
456 {
457 errors += 1;
458 }
459 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
460 }
461 if errors > 0 {
462 artifact = Artifact::Nothing;
464 }
465 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
468}
469
470#[must_use]
480pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
481 let mut sess = Session::new(opts.clone());
482 if opts.emit != EmitKind::Ir {
483 return failure(format!(
484 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
485 the C in front of it became",
486 opts.emit.as_str()
487 ));
488 }
489 let bytes = match fs.read(Path::new(name)) {
490 Ok(bytes) => bytes,
491 Err(e) => return failure(format!("{name}: {e}")),
492 };
493 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
494 return failure(format!("{name}: this is not text, so it is not IR"));
495 };
496
497 let module = match rucc_ir::parse(text, &mut sess.interner) {
498 Ok(module) => module,
499 Err(error) => {
500 return failure(format!("{name}:{}: {}", error.line, error.message));
501 }
502 };
503 let mut diagnostics: Vec<Diagnostic> = Vec::new();
504 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
505 for error in errors {
506 diagnostics.push(invalid(&format!("invalid IR, {error}")));
507 }
508 }
509 let mut messages = Vec::with_capacity(diagnostics.len());
510 for diag in &diagnostics {
511 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
512 }
513 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
514 let artifact = if errors > 0 {
515 Artifact::Nothing
516 } else {
517 Artifact::Text(rucc_ir::print(&module, &sess.interner))
518 };
519 Compiled {
521 artifact,
522 messages,
523 errors,
524 fired: Fired::new(),
525 pressure: Pressure::new(),
526 lowerings: Lowerings::new(),
527 dumps: Vec::new(),
528 remarks: String::new(),
529 deps: Vec::new(),
530 temps: Temps::default(),
531 }
532}
533
534fn instrument(
557 module: &mut rucc_ir::Module,
558 names: &mut Interner,
559 opts: &Options,
560) -> Result<Instrumented, Vec<Diagnostic>> {
561 if !opts.safety.instruments() {
562 return Ok(Instrumented::default());
563 }
564 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
565 checks.freed = rucc_safety::ending::checks(module, names);
573 let interposed = rucc_safety::redirect(module, names);
578 let crossings = rucc_safety::witness(module, names);
581 match rucc_ir::verify(module, names) {
582 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
583 Err(errors) => Err(errors
584 .iter()
585 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
586 .collect()),
587 }
588}
589
590#[derive(Clone, Copy, Debug, Default)]
596struct Instrumented {
597 checks: rucc_safety::Counts,
599 interposed: usize,
601 crossings: rucc_safety::Sites,
603}
604
605fn optimize(
617 module: &mut rucc_ir::Module,
618 names: &Interner,
619 target: &TargetInfo,
620 opts: &Options,
621 file: &str,
622 dumps: &mut Vec<rucc_opt::Dump>,
623 remarks: &mut String,
624) -> Result<(), Vec<Diagnostic>> {
625 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
626 settings.interposition = match opts.interposition {
632 true => replaceable(target, opts),
633 false => IrPic::Executable,
634 };
635 settings.toggles.clone_from(&opts.passes);
636 settings.fuel = opts.pass_fuel.iter().cloned().collect();
637 settings.global_fuel = opts.pass_fuel_global;
638 settings.verify |= opts.verify_each;
639 for (on, spec) in &opts.pass_gates {
640 if let Err(why) = settings.gates.add(*on, spec) {
643 return Err(vec![internal(&why)]);
644 }
645 }
646 for spec in &opts.dump_ir {
647 if let Err(why) = settings.dumps.add(spec) {
650 return Err(vec![internal(&why)]);
651 }
652 }
653 let mut wants = rucc_opt::Wants::none();
654 for spec in &opts.opt_info {
655 if let Err(why) = wants.add(spec) {
658 return Err(vec![internal(&why)]);
659 }
660 }
661 let report = rucc_opt::run(module, names, &settings);
662 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
663 dumps.extend(report.dumps);
664 match report.broke.is_empty() {
665 true => Ok(()),
666 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
667 }
668}
669
670fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
704 match (target.tuple.os().object_format(), opts.pic) {
705 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
706 _ => IrPic::Executable,
707 }
708}
709
710fn generate(
711 module: &mut rucc_ir::Module,
712 names: &mut Interner,
713 target: &TargetInfo,
714 opts: &Options,
715 recording: &mut Recording<'_>,
716 assembly: &mut Option<String>,
717) -> Result<Artifact, Vec<Diagnostic>> {
718 let Some(machine) = Machine::for_target(target) else {
719 return Err(vec![unsupported(&format!(
720 "there is no back end for {} in this compiler yet, so there is nothing to generate",
721 target.tuple
722 ))]);
723 };
724 if opts.protector != Protector::None && machine.conv.guard.is_none() {
729 return Err(vec![unsupported(&format!(
730 "{} is not supported for {} yet, because the stack protector on that target is not \
731 the one this compiler writes",
732 opts.protector, target.tuple
733 ))]);
734 }
735 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
741 return Err(vec![unsupported(&format!(
742 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
743 for it there is not the note this compiler writes",
744 opts.control, target.tuple
745 ))]);
746 }
747 let profile = match machine.conv.trace {
753 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
754 None if opts.profile => {
755 return Err(vec![unsupported(&format!(
756 "-pg is not supported for {} yet, because the profiler's hook on that target is \
757 not the one this compiler calls",
758 target.tuple
759 ))]);
760 }
761 None => None,
762 };
763 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
768 return Err(vec![unsupported(&format!(
769 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
770 the room is there is not the section this compiler writes",
771 target.tuple
772 ))]);
773 }
774 let flags = pipeline::Flags {
775 frame_pointer: opts.frame_pointer,
776 red_zone: opts.red_zone,
777 stack_clash: opts.stack_clash,
778 landing: opts.control.branch(),
779 profile: match profile {
780 None => pipeline::Profile::No,
781 Some(true) => pipeline::Profile::Early,
782 Some(false) => pipeline::Profile::Late,
783 },
784 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
785 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
792 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
797 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
802 accurate: opts.cycle_accurate_model,
804 verify: opts.verify_each,
807 goal: Goal::for_size(opts.opt_level.is_size()),
812 };
813
814 if opts.safety.instruments() {
823 rucc_opt::heap::annotate(module, names);
833 rucc_safety::handover::arrange(module);
840 rucc_safety::lower(module, names);
841 if let Err(errors) = rucc_ir::verify(module, names) {
842 return Err(errors
843 .iter()
844 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
845 .collect());
846 }
847 }
848
849 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
858
859 let mut funcs = Vec::new();
860 let mut complaints = Vec::new();
861 for id in module.funcs() {
862 if module[id].is_declaration() {
863 continue;
864 }
865 match pipeline::compile_recording(
866 &mut module[id],
867 names,
868 &machine,
869 &elsewhere,
870 flags,
871 recording,
872 ) {
873 Ok(func) => funcs.push(func),
874 Err(why) => {
875 let name = names.resolve(module[id].name).to_owned();
876 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
879 let said = format!("cannot generate code for '{name}': {why}");
880 complaints.push(unsupported_at(&said, span));
881 }
882 }
883 }
884 if !complaints.is_empty() {
885 return Err(complaints);
886 }
887 let (globals, aliases) = match opts.emit {
893 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
894 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
895 rucc_asm::aliases(module, names).map_err(refused)?,
896 ),
897 _ => (rucc_asm::Globals::default(), Vec::new()),
898 };
899 let unwind = opts.unwinds();
903 match opts.emit {
904 EmitKind::Asm => {
905 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
906 .map(Artifact::Text)
907 .map_err(refused)
908 }
909 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
913 if opts.save_temps.wanted() {
914 let listing = rucc_asm::print(
915 &funcs,
916 &globals,
917 &aliases,
918 names,
919 target,
920 unwind,
921 output(opts, target),
922 );
923 *assembly = Some(listing.map_err(refused)?);
924 }
925 let text = rucc_asm::assemble(&funcs, names, target, unwind).map_err(refused)?;
926 let data = globals.image();
927 let bytes = rucc_object::write(&text, &data, &aliases, target, output(opts, target))
930 .map_err(wrote)?;
931 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
936 Ok(Artifact::Object { bytes, defines })
937 }
938 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
939 }
940}
941
942fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
954 let mut features = 0;
955 if target.tuple.arch() == Arch::X86_64 {
956 if opts.control.branch() {
957 features |= rucc_object::Property::IBT;
958 }
959 if opts.control.ret() {
960 features |= rucc_object::Property::SHSTK;
961 }
962 }
963 rucc_object::Output {
964 sections: rucc_object::Sections {
965 functions: opts.function_sections,
966 data: opts.data_sections,
967 },
968 property: rucc_object::Property { features },
969 }
970}
971
972fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
978 match why {
979 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
980 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
981 }
982}
983
984fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
991 match why {
992 rucc_asm::Error::Thread { .. }
993 | rucc_asm::Error::IFunc { .. }
994 | rucc_asm::Error::Frame { .. } => {
995 vec![unsupported(&why.to_string())]
996 }
997 _ => vec![internal(&why.to_string())],
998 }
999}
1000
1001fn unsupported(message: &str) -> Diagnostic {
1007 unsupported_at(message, Span::DUMMY)
1008}
1009
1010fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1016 Diagnostic::error(message.to_owned(), span)
1017 .with_code("E0653")
1018 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1019}
1020
1021fn invalid(message: &str) -> Diagnostic {
1023 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1024}
1025
1026fn internal(message: &str) -> Diagnostic {
1028 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1029 .with_code("E0652")
1030 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1031}
1032
1033fn failure(message: String) -> Compiled {
1036 Compiled {
1037 artifact: Artifact::Nothing,
1038 messages: vec![format!("rucc: error: {message}")],
1039 errors: 1,
1040 fired: Fired::new(),
1041 pressure: Pressure::new(),
1042 lowerings: Lowerings::new(),
1043 dumps: Vec::new(),
1044 remarks: String::new(),
1045 deps: Vec::new(),
1046 temps: Temps::default(),
1047 }
1048}
1049
1050#[cfg(test)]
1051mod tests {
1052 use rucc_session::{MemoryFileSystem, Std};
1053 use rucc_target::Triple;
1054
1055 use super::*;
1056
1057 fn options() -> Options {
1058 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1059 opts.emit = EmitKind::Tast;
1060 opts
1061 }
1062
1063 fn run(opts: &Options, source: &str) -> Compiled {
1064 let mut fs = MemoryFileSystem::new();
1065 fs.insert("/main.c", source.to_owned().into_bytes());
1066 compile(opts, "/main.c", &fs)
1067 }
1068
1069 fn freestanding() -> Options {
1073 let mut opts = options();
1074 opts.hosted = false;
1075 opts.search.push_system(rucc_session::runtime::DIR);
1076 opts
1077 }
1078
1079 fn shipped(source: &str) -> String {
1081 let result = run(&freestanding(), source);
1082 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1083 result.text().to_owned()
1084 }
1085
1086 fn tast(source: &str) -> String {
1088 let result = run(&options(), source);
1089 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1090 result.text().to_owned()
1091 }
1092
1093 #[test]
1094 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1095 let text = shipped(concat!(
1096 "#include <stdarg.h>\n",
1097 "int sum(int n, ...) {\n",
1098 " va_list ap, copy;\n",
1099 " va_start(ap, n);\n",
1100 " va_copy(copy, ap);\n",
1101 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1102 " va_end(ap);\n",
1103 " va_end(copy);\n",
1104 " return total;\n",
1105 "}\n",
1106 ));
1107 assert!(text.contains("va-start"), "{text}");
1108 assert!(text.contains("va-copy"), "{text}");
1109 assert!(text.contains("va-arg"), "{text}");
1110 assert!(text.contains("va-end"), "{text}");
1111 }
1112
1113 #[test]
1117 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1118 let text = shipped(concat!(
1119 "#define __need___va_list\n",
1120 "#include <stdarg.h>\n",
1121 "int vprint(const char *f, __gnuc_va_list ap);\n",
1122 "#ifdef va_start\n",
1123 "#error va_start should not be defined\n",
1124 "#endif\n",
1125 "#ifdef _VA_LIST_DEFINED\n",
1126 "#error va_list should not have been made\n",
1127 "#endif\n",
1128 ));
1129 assert!(text.contains("vprint"), "{text}");
1130 }
1131
1132 #[test]
1135 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1136 let text = shipped(concat!(
1137 "#define __need_size_t\n",
1138 "#include <stddef.h>\n",
1139 "#ifdef offsetof\n",
1140 "#error offsetof should not be defined yet\n",
1141 "#endif\n",
1142 "#define __need_ptrdiff_t\n",
1143 "#include <stddef.h>\n",
1144 "#include <stddef.h>\n",
1145 "size_t a;\n",
1146 "ptrdiff_t b;\n",
1147 "wchar_t c;\n",
1148 "max_align_t d;\n",
1149 "void *e = NULL;\n",
1150 "struct P { int x; long y; };\n",
1151 "size_t f = offsetof(struct P, y);\n",
1152 ));
1153 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1154 assert!(text.contains("decl #1 b : long"), "{text}");
1155 }
1156
1157 #[test]
1158 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1159 let text = shipped(concat!(
1160 "#include <limits.h>\n",
1161 "#include <float.h>\n",
1162 "int bits = CHAR_BIT;\n",
1163 "long big = LONG_MAX;\n",
1164 "int low = INT_MIN;\n",
1165 "int radix = FLT_RADIX;\n",
1166 "int digits = DBL_MANT_DIG;\n",
1167 ));
1168 assert!(text.contains("const 8 : int"), "{text}");
1169 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1170 assert!(text.contains("const 2 : int"), "{text}");
1171 assert!(text.contains("const 53 : int"), "{text}");
1172 }
1173
1174 #[test]
1178 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1179 let text = shipped(concat!(
1180 "#include <stdint.h>\n",
1181 "int64_t a = INT64_C(1);\n",
1182 "uint_least16_t b;\n",
1183 "intptr_t c;\n",
1184 "uintmax_t d = UINTMAX_MAX;\n",
1185 "int wide = sizeof(int_fast64_t);\n",
1186 ));
1187 assert!(text.contains("decl #0 a : long"), "{text}");
1188 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1189 assert!(text.contains("decl #2 c : long"), "{text}");
1190 }
1191
1192 #[test]
1203 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1204 let text = shipped(concat!(
1205 "#include <mmintrin.h>\n",
1206 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1207 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1208 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1209 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1210 "void done(void) { _mm_empty(); }\n",
1211 ));
1212 assert!(text.contains("add"), "{text}");
1213 assert!(text.contains("pack"), "{text}");
1214 assert!(text.contains("shift"), "{text}");
1215 }
1216
1217 #[test]
1222 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1223 let text = shipped(concat!(
1224 "#include <mm_malloc.h>\n",
1225 "void *get(void) { return _mm_malloc(64, 16); }\n",
1226 "void put(void *p) { _mm_free(p); }\n",
1227 ));
1228 assert!(text.contains("get"), "{text}");
1229 assert!(text.contains("put"), "{text}");
1230 }
1231
1232 #[test]
1244 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1245 let text = shipped(concat!(
1246 "#include <xmmintrin.h>\n",
1247 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1248 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1249 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1250 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1251 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1252 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1253 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1254 "void *room(void) { return _mm_malloc(64, 16); }\n",
1255 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1256 ));
1257 assert!(text.contains("add"), "{text}");
1258 assert!(text.contains("mask"), "{text}");
1259 assert!(text.contains("pick"), "{text}");
1260 assert!(text.contains("wide"), "{text}");
1261 }
1262
1263 #[test]
1270 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1271 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1272 for absent in [
1273 "_mm_sqrt_ps",
1274 "_mm_sqrt_ss",
1275 "_mm_rsqrt_ps",
1276 "_mm_rsqrt_ss",
1277 "_mm_getcsr",
1278 "_mm_setcsr",
1279 ] {
1280 let defined = text.contains(&format!("{absent}("));
1281 assert!(!defined, "{absent} is defined and the header says it is not");
1282 assert!(text.contains(absent), "{absent} is absent and unexplained");
1283 }
1284 }
1285
1286 #[test]
1287 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1288 let text = shipped(concat!(
1289 "#include <emmintrin.h>\n",
1290 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1291 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1292 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1293 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1294 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1295 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1296 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1297 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1298 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1299 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1300 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1301 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1302 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1303 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1304 ));
1305 assert!(text.contains("wide"), "{text}");
1306 assert!(text.contains("pack"), "{text}");
1307 assert!(text.contains("near"), "{text}");
1308 assert!(text.contains("half"), "{text}");
1309 }
1310
1311 #[test]
1315 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1316 let text = shipped(concat!(
1317 "#include <immintrin.h>\n",
1318 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1319 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1320 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1321 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1322 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1323 "}\n",
1324 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1325 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1326 ));
1327 assert!(text.contains("matching"), "{text}");
1328 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1329 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1330 }
1331
1332 #[test]
1336 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1337 let text = shipped(concat!(
1338 "#include <x86intrin.h>\n",
1339 "void barriers(void *p) {\n",
1340 " _mm_lfence();\n",
1341 " _mm_sfence();\n",
1342 " _mm_mfence();\n",
1343 " _mm_pause();\n",
1344 " _mm_clflush(p);\n",
1345 "}\n",
1346 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1347 ));
1348 assert!(text.contains("barriers"), "{text}");
1349 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1350 }
1351
1352 #[test]
1356 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1357 let text = shipped(concat!(
1358 "#include <immintrin.h>\n",
1359 "#include <emmintrin.h>\n",
1360 "#include <immintrin.h>\n",
1361 "#include <x86intrin.h>\n",
1362 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1363 ));
1364 assert!(text.contains("twice"), "{text}");
1365 }
1366
1367 #[test]
1371 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1372 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1373 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1374 let defined = text.contains(&format!("{absent}("));
1375 assert!(!defined, "{absent} is defined and the header says it is not");
1376 assert!(text.contains(absent), "{absent} is absent and unexplained");
1377 }
1378 }
1379
1380 #[test]
1381 fn the_three_formality_headers_still_have_to_work() {
1382 let text = shipped(concat!(
1383 "#include <stdbool.h>\n",
1384 "#include <stdalign.h>\n",
1385 "#include <iso646.h>\n",
1386 "#include <stdnoreturn.h>\n",
1387 "int t = true and not false;\n",
1388 "_Alignas(16) char buf[16];\n",
1389 "int a = alignof(long);\n",
1390 ));
1391 assert!(text.contains("decl #0 t : int"), "{text}");
1392 assert!(text.contains("const 8 : unsigned long"), "{text}");
1393 }
1394
1395 #[test]
1403 fn every_shipped_header_can_be_included_twice() {
1404 let once: String = rucc_session::runtime::names()
1405 .iter()
1406 .map(|name| format!("#include <{name}>\n"))
1407 .collect();
1408 let twice = once.repeat(2);
1409 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1410 }
1411
1412 #[test]
1413 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1414 let fs = MemoryFileSystem::new();
1415 let result = compile(&options(), "/nope.c", &fs);
1416 assert!(result.failed());
1417 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1418 assert!(result.text().is_empty());
1419 }
1420
1421 #[test]
1422 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1423 let text = tast("int x = 1;\n");
1424 let expected = "\
1425decl #0 x : int object external static defined
1426 init
1427 +0
1428 const 1 : int
1429";
1430 assert_eq!(text, expected);
1431 }
1432
1433 #[test]
1434 fn the_macros_are_expanded_before_anything_is_parsed() {
1435 let text = tast("#define N 2\nint a[N];\n");
1439 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1440 }
1441
1442 #[test]
1448 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1449 let text = tast(concat!(
1450 "#pragma pack(4)\n",
1451 "struct s { int a; };\n",
1452 "#pragma pack()\n",
1453 "int b;\n",
1454 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1455 ));
1456 assert!(text.contains("decl #0 b : int"), "{text}");
1457 assert!(text.contains("decl #1 c : int"), "{text}");
1458 }
1459
1460 #[test]
1468 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1469 tast(concat!(
1470 "struct A { char c; int i; } __attribute__((packed));\n",
1471 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1472 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1473 "struct B { char c; int i; } __attribute__((aligned));\n",
1476 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1477 "struct C { char c; int i __attribute__((packed)); };\n",
1478 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1479 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1480 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1481 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1482 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1483 "struct E { char c; _Alignas(8) int i; };\n",
1484 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1485 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1486 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1487 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1488 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1491 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1492 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1493 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1494 "struct I { [[gnu::packed]] char c; int i; };\n",
1497 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1498 "struct J { char c; [[gnu::packed]] int i; };\n",
1499 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1500 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1501 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1502 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1503 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1504 "union L { char c; int i; } __attribute__((packed));\n",
1505 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1506 "struct O { char c; int i; } __attribute__((__packed__));\n",
1510 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
1511 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
1512 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
1513 ));
1514 }
1515
1516 #[test]
1529 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
1530 let text = tast(concat!(
1531 "struct one { int x; };\n",
1532 "struct two { long y; };\n",
1533 "typedef union { struct one *a; struct two *b; void *any; }\n",
1534 " __attribute__((__transparent_union__)) arg;\n",
1535 "int takes(arg v);\n",
1536 "int f(struct one *p, struct two *q, char *c) {\n",
1537 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
1538 "}\n",
1539 "int takes(struct one *p);\n",
1541 "int (*as_a_member)(struct one *) = takes;\n",
1542 "int (*as_the_union)(arg) = takes;\n",
1543 ));
1544 assert!(text.contains("compound-literal"), "{text}");
1545 }
1546
1547 #[test]
1553 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
1554 let text = tast(concat!(
1555 "struct sockaddr { int family; };\n",
1556 "struct sockaddr_in { int family; int addr; };\n",
1557 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
1558 " addr_arg __attribute__((__transparent_union__));\n",
1559 "int bind_to(int fd, addr_arg where);\n",
1560 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
1561 ));
1562 assert!(text.contains("compound-literal"), "{text}");
1563 }
1564
1565 #[test]
1573 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
1574 let result = run(
1575 &options(),
1576 concat!(
1577 "union wider { int small; double large; } __attribute__((transparent_union));\n",
1578 "struct plain { int x; } __attribute__((transparent_union));\n",
1579 ),
1580 );
1581 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
1582 assert!(!result.failed(), "{:?}", result.messages);
1583 for message in &result.messages {
1584 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
1585 }
1586 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
1587 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
1588 }
1589
1590 #[test]
1599 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
1600 let packed = body(concat!(
1601 "struct P { char c; int v; } __attribute__((packed));\n",
1602 "int f(struct P *p) { return p->v; }\n",
1603 ));
1604 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
1605 let plain = body(concat!(
1607 "struct P { char c; int v; };\n",
1608 "int f(struct P *p) { return p->v; }\n",
1609 ));
1610 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
1611 }
1612
1613 #[test]
1620 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
1621 let stepped = body(concat!(
1622 "struct P { char c; int v[4]; } __attribute__((packed));\n",
1623 "int f(struct P *p, int i) { return p->v[i]; }\n",
1624 ));
1625 assert!(stepped.contains(", align 1,"), "{stepped}");
1626 assert!(!stepped.contains(", align 4,"), "{stepped}");
1627 let nested = body(concat!(
1628 "struct Inner { int v; };\n",
1629 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
1630 "int f(struct P *p) { return p->in.v; }\n",
1631 ));
1632 assert!(nested.contains(", align 1,"), "{nested}");
1633 assert!(!nested.contains(", align 4,"), "{nested}");
1634 }
1635
1636 #[test]
1652 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
1653 let through = body(concat!(
1654 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1655 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
1656 ));
1657 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
1658 let stepped = body(concat!(
1661 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1662 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
1663 ));
1664 assert!(stepped.contains(", align 1,"), "{stepped}");
1665 assert!(!stepped.contains(", align 4,"), "{stepped}");
1666 let plain = body(concat!(
1669 "typedef unsigned int word;\n",
1670 "unsigned int f(const void *p) { return *(const word *)p; }\n",
1671 ));
1672 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
1673 }
1674
1675 #[test]
1686 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
1687 let prefix = concat!(
1688 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
1689 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
1690 );
1691 let loaded =
1692 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
1693 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
1694 assert!(!loaded.contains("align 16"), "{loaded}");
1695 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
1698 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
1699 let aligned =
1701 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
1702 assert!(aligned.contains("align 16"), "{aligned}");
1703 }
1704
1705 #[test]
1714 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
1715 tast(concat!(
1716 "int v __attribute__((aligned(64)));\n",
1717 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
1718 "__attribute__((aligned(32))) int w;\n",
1721 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
1722 "[[gnu::aligned(16)]] int x;\n",
1723 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
1724 "int y __attribute__((aligned(2)));\n",
1727 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
1728 "void f(void) { int a __attribute__((aligned(128)));\n",
1730 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
1731 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1734 "void g(void) __attribute__((aligned(256)));\n",
1737 "void g(void) {}\n",
1738 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
1739 ));
1740 }
1741
1742 #[test]
1746 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
1747 let text = asm(concat!(
1748 "int v __attribute__((aligned(64)));\n",
1749 "void g(void) __attribute__((aligned(256)));\n",
1750 "void g(void) {}\n",
1751 "void plain(void) {}\n",
1752 ));
1753 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
1754 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1755 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
1756 }
1757
1758 #[test]
1767 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
1768 tast(concat!(
1769 "typedef int L __attribute__((aligned(2)));\n",
1770 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
1771 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
1772 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
1774 "struct T { char c; L x; };\n",
1775 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
1776 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
1777 "typedef int H __attribute__((aligned(16)));\n",
1779 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
1780 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
1781 "struct U { char c; H x; };\n",
1782 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
1783 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
1784 "typedef L M __attribute__((aligned(8)));\n",
1787 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
1788 "typedef L N;\n",
1791 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
1792 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1794 ));
1795 let text = asm(concat!(
1796 "typedef int L __attribute__((aligned(2)));\n",
1797 "typedef int H __attribute__((aligned(16)));\n",
1798 "L low;\n",
1799 "H high;\n",
1800 ));
1801 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
1802 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
1803 }
1804
1805 #[test]
1813 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
1814 tast(concat!(
1815 "typedef int __attribute__((vector_size(16))) v4si;\n",
1816 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
1817 "typedef char __attribute__((vector_size(16))) v16qi;\n",
1818 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
1819 "typedef int __attribute__((vector_size(4))) v1si;\n",
1822 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
1823 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
1825 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
1826 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
1827 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
1828 "v4si g;\n",
1831 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
1832 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
1833 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
1836 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
1838 ));
1839 }
1840
1841 #[test]
1851 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
1852 tast(concat!(
1853 "typedef int __attribute__((vector_size(8))) v2si;\n",
1854 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
1855 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
1856 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
1858 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
1859 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
1862 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
1863 ));
1864 }
1865
1866 #[test]
1874 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
1875 let result = run(
1876 &options(),
1877 concat!(
1878 "typedef int __attribute__((vector_size(16))) v4si;\n",
1879 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
1880 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
1881 " v4si v = { 1, 2, 3, 4 };\n",
1882 " v[0] = n;\n",
1883 " v[1] += n;\n",
1884 " v[2]++;\n",
1885 " *&v[3] = n;\n",
1886 " v4ui shifted = a >> b;\n",
1888 " shifted <<= b;\n",
1889 " *out = v + (v4si)shifted + (1 << b);\n",
1892 "}\n",
1893 "void refused(const v4si c) {\n",
1896 " c[0] = 1;\n",
1897 "}\n",
1898 ),
1899 );
1900 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
1901 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
1902 }
1903
1904 #[test]
1911 fn a_record_that_asks_for_the_other_byte_order_is_refused_rather_than_laid_out_in_this_one() {
1912 let opts = options();
1913 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
1914 assert_eq!(
1915 run(&opts, big).messages,
1916 ["/main.c:1:36: error: 'scalar_storage_order' is not implemented yet [E0688]\n\
1917 /main.c:1:36: note: every scalar in this record would be read in the wrong byte \
1918 order"]
1919 );
1920
1921 let armoured =
1922 "struct s { int i; } __attribute__((__scalar_storage_order__(\"little-endian\")));\n";
1923 let messages = run(&opts, armoured).messages;
1924 assert!(messages[0].contains("[E0688]"), "{messages:?}");
1925
1926 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
1929 assert!(run(&opts, front).messages[0].contains("[E0688]"), "{front}");
1930 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
1931 assert!(run(&opts, standard).messages[0].contains("[E0688]"), "{standard}");
1932 }
1933
1934 #[test]
1944 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
1945 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
1947 assert_eq!(
1948 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
1949 1
1950 );
1951 assert_eq!(
1952 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
1953 1
1954 );
1955 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
1956 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
1958 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
1959 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
1961 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
1962 }
1963
1964 fn bit_field_byte(record: &str) -> u64 {
1966 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
1967 let body = body(&source);
1968 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
1969 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
1970 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
1971 }
1972
1973 #[test]
1979 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
1980 tast(concat!(
1981 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
1982 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
1983 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
1984 "struct b { char c; __attribute__((packed)) int i; };\n",
1985 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
1986 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
1987 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
1988 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
1989 ));
1990 }
1991
1992 #[test]
1998 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
1999 tast(concat!(
2000 "#pragma pack(1)\n",
2001 "struct A { char c; int i; };\n",
2002 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2003 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2004 "#pragma pack()\n",
2005 "struct B { char c; int i; };\n",
2006 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2007 "#pragma pack(2)\n",
2008 "struct C { char c; int i; double d; };\n",
2009 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2010 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2011 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2013 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2014 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2015 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2017 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2018 "#pragma pack()\n",
2019 "#pragma pack(push, 1)\n",
2020 "struct D { char c; short s; };\n",
2021 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2022 "#pragma pack(pop)\n",
2023 "struct E { char c; short s; };\n",
2024 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2025 "struct H { char c;\n",
2027 "#pragma pack(1)\n",
2028 " int i; };\n",
2029 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2030 "#pragma pack(1)\n",
2031 "struct I { char c;\n",
2032 "#pragma pack()\n",
2033 " int i; };\n",
2034 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2035 "#pragma pack()\n",
2036 "#pragma pack(push, 8)\n",
2038 "#pragma pack(push, 1)\n",
2039 "struct P { char c; int i; };\n",
2040 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2041 "#pragma pack(pop)\n",
2042 "struct Q { char c; int i; };\n",
2043 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2044 "#pragma pack(pop)\n",
2045 "#pragma pack(16)\n",
2047 "struct R { char c; int i; };\n",
2048 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2049 "#pragma pack()\n",
2050 "#pragma pack(1)\n",
2051 "struct S { char c; int i : 5; int j : 20; };\n",
2052 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2053 "union T { char c; int i; };\n",
2054 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2055 "#pragma pack()\n",
2056 ));
2057 }
2058
2059 #[test]
2063 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2064 let result = run(
2065 &options(),
2066 concat!(
2067 "#pragma pack 4\n",
2068 "#pragma pack(pop)\n",
2069 "#pragma pack(3)\n",
2070 "#pragma pack(1) junk\n",
2071 "#pragma pack(push, 1\n",
2072 "#pragma pack(x)\n",
2073 "#pragma pack(0)\n",
2076 "#pragma pack(push)\n",
2077 "struct s { char c; int i; };\n",
2078 "#pragma pack(pop)\n",
2079 "#pragma pack(pop, foo)\n",
2080 ),
2081 );
2082 let expected = [
2083 "missing `(` after `#pragma pack` - ignored",
2084 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2085 "alignment must be a small power of two, not 3",
2086 "junk at end of `#pragma pack`",
2087 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2088 "unknown action `x` for `#pragma pack` - ignored",
2089 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2090 ];
2091 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2092 for (message, want) in result.messages.iter().zip(expected) {
2093 assert!(message.contains(want), "expected {want:?} in {message:?}");
2094 }
2095 }
2096
2097 #[test]
2104 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2105 let result = run(
2106 &options(),
2107 concat!(
2108 "#pragma pack(push, 1)\n",
2109 "#pragma pack(pop)\n",
2110 "#define API\n",
2111 "API const char version[] = \"3.53.4\";\n",
2112 "const char *get(void) { return version; }\n",
2113 ),
2114 );
2115 assert!(result.messages.is_empty(), "{:?}", result.messages);
2116 }
2117
2118 #[test]
2122 fn the_wide_integer_answers_to_all_three_of_its_names() {
2123 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2124 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2125 assert!(text.contains("decl #1 b : __int128"), "{text}");
2126 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2127 }
2128
2129 #[test]
2130 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2131 let text = tast("long f(int a, long b) { return a + b; }\n");
2135 assert!(text.contains("convert arithmetic"), "{text}");
2136 }
2137
2138 #[test]
2139 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2140 for source in [
2141 "#error stop\n",
2142 "int f(void) { return 1 + ; }\n",
2143 "int f(void) { return undeclared; }\n",
2144 ] {
2145 let result = run(&options(), source);
2146 assert!(result.failed(), "expected this to fail:\n{source}");
2147 assert!(
2148 result.text().is_empty(),
2149 "a file that did not compile wrote a tree:\n{source}"
2150 );
2151 }
2152 }
2153
2154 #[test]
2155 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2156 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2160 assert_eq!(result.errors, 1, "{:?}", result.messages);
2161 }
2162
2163 #[test]
2164 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2165 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2169 assert_eq!(result.errors, 1, "{:?}", result.messages);
2170 }
2171
2172 #[test]
2173 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2174 let source = "int f(void) { char c = 300; return c; }\n";
2175 let plain = run(&options(), source);
2176 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2177 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2178 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2179
2180 let mut opts = options();
2181 opts.warnings_are_errors = true;
2182 let strict = run(&opts, source);
2183 assert!(strict.failed());
2184 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2185 for message in &strict.messages {
2186 assert!(!message.contains("warning:"), "{message}");
2187 }
2188 }
2189
2190 #[test]
2191 fn w_drops_the_warning_before_werror_can_promote_it() {
2192 let source = "int f(void) { char c = 300; return c; }\n";
2193 let mut opts = options();
2194 opts.warnings = false;
2195 let quiet = run(&opts, source);
2196 assert_eq!(quiet.messages, Vec::<String>::new());
2197 assert_eq!(quiet.errors, 0);
2198 assert!(!quiet.text().is_empty(), "and the file still compiles");
2199
2200 opts.warnings_are_errors = true;
2203 let both = run(&opts, source);
2204 assert_eq!(both.messages, Vec::<String>::new());
2205 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2206 }
2207
2208 #[test]
2209 fn the_dialect_reaches_the_keywords_and_the_checking() {
2210 let source = "typeof(1) x;\n";
2213 let mut opts = options();
2214 opts.std = Std::C23;
2215 opts.gnu_extensions = false;
2216 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2217
2218 opts.std = Std::C17;
2219 assert!(run(&opts, source).failed());
2220 }
2221
2222 #[test]
2223 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2224 let mut opts = options();
2225 opts.emit = EmitKind::Object;
2226 let result = run(&opts, "int x = 1;\n");
2227 assert!(!result.failed(), "{:?}", result.messages);
2228 assert!(result.text().is_empty());
2229 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2232 }
2233
2234 fn mir(source: &str) -> String {
2236 let mut opts = options();
2237 opts.emit = EmitKind::MirFinal;
2238 let result = run(&opts, source);
2239 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2240 result.text().to_owned()
2241 }
2242
2243 #[test]
2249 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2250 let text = mir("int add(int a, int b) { return a + b; }\n");
2251 assert!(text.starts_with("mfunc @add {"), "{text}");
2252 assert!(text.contains("x64.add_rr_32"), "{text}");
2253 assert!(text.contains("x64.ret"), "{text}");
2254 assert!(!text.contains('%'), "{text}");
2257 }
2258
2259 #[test]
2261 fn a_function_with_no_body_produces_no_machine_function() {
2262 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2263 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2264 assert!(text.contains("mfunc @f {"), "{text}");
2265 assert!(text.contains("x64.call"), "{text}");
2266 }
2267
2268 #[test]
2270 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2271 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2272 let first = text.find("mfunc @a").expect("the first function");
2273 let second = text.find("mfunc @b").expect("the second function");
2274 assert!(first < second, "{text}");
2275 }
2276
2277 #[test]
2279 fn the_target_decides_which_convention_the_generated_code_follows() {
2280 let mut opts = options();
2281 opts.emit = EmitKind::MirFinal;
2282 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2283 assert!(linux.contains("$rdi"), "{linux}");
2284
2285 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2286 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2287 assert!(windows.contains("$rcx"), "{windows}");
2288 assert!(!windows.contains("$rdi"), "{windows}");
2289 }
2290
2291 #[test]
2299 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2300 let source = concat!(
2301 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2302 "int size(void) { return sizeof(struct S); }\n",
2303 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2304 );
2305
2306 let mut opts = options();
2307 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2308 let windows = run(&opts, source);
2309 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2310
2311 let linux = run(&options(), source);
2312 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2313 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2314
2315 let mut opts = options();
2318 opts.ms_extensions = Some(true);
2319 let asked = run(&opts, source);
2320 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2321 }
2322
2323 #[test]
2325 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2326 let mut opts = options();
2327 opts.emit = EmitKind::MirFinal;
2328 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2329 let result = run(&opts, "int f(int a) { return a; }\n");
2330 assert!(result.failed());
2331 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2332 assert!(result.text().is_empty());
2333 }
2334
2335 #[test]
2342 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2343 let mut opts = options();
2344 opts.emit = EmitKind::MirFinal;
2345 let source = "void a(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n\
2346 void b(int n) { int v[n] __attribute__((aligned(32))); v[0] = 1; }\n";
2347 let result = run(&opts, source);
2348 assert!(result.failed());
2349 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2350 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2351 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2352 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2353 assert!(result.text().is_empty());
2354 }
2355
2356 #[test]
2364 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2365 let mut opts = options();
2366 opts.emit = EmitKind::MirFinal;
2367 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2368 let plain = run(&opts, source);
2369 assert!(!plain.failed(), "{:?}", plain.messages);
2370 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2371
2372 opts.stack_clash = true;
2373 let result = run(&opts, source);
2374 assert!(!result.failed(), "{:?}", result.messages);
2375 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2376 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2377 }
2378
2379 #[test]
2389 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
2390 let mut opts = options();
2391 opts.emit = EmitKind::Object;
2392 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2393 let source = concat!(
2394 "void use(void *p);\n",
2395 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
2396 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
2397 );
2398 let result = run(&opts, source);
2399 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2400 let bytes = match result.artifact {
2401 Artifact::Object { bytes, .. } => bytes,
2402 other => panic!("expected an object, got {other:?}"),
2403 };
2404 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2405
2406 let mut opts = options();
2409 opts.emit = EmitKind::Object;
2410 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2411 }
2412
2413 #[test]
2424 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
2425 let source = concat!(
2426 "void other(void *p);\n",
2427 "void takes(void (*f)(void *));\n",
2428 "void (*held)(void *);\n",
2429 "void pass(void) { takes(other); }\n",
2430 "void keep(void) { held = other; }\n",
2431 "void call(void) { other(0); }\n",
2432 );
2433 let mut opts = options();
2434 opts.emit = EmitKind::Object;
2435 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2436 let result = run(&opts, source);
2437 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2438 let bytes = match result.artifact {
2439 Artifact::Object { bytes, .. } => bytes,
2440 other => panic!("expected an object, got {other:?}"),
2441 };
2442 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2443
2444 let mut opts = options();
2447 opts.emit = EmitKind::Object;
2448 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2449 }
2450
2451 #[test]
2465 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
2466 let mut opts = options();
2467 opts.emit = EmitKind::MirFinal;
2468 let source =
2469 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
2470 let result = run(&opts, source);
2471 assert!(result.failed());
2472 assert!(
2473 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
2474 "{result:?}"
2475 );
2476 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
2477 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
2478 }
2479
2480 #[test]
2482 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
2483 let mut opts = options();
2484 opts.emit = EmitKind::MirFinal;
2485 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
2486 let result = run(&opts, source);
2487 assert!(result.failed());
2488 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
2489 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
2490 assert!(!note.contains("spec/17-milestones.md"), "{note}");
2491 }
2492
2493 #[test]
2495 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
2496 let source = "int f(int a) { return a; }\n";
2497 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
2498
2499 let mut opts = options();
2500 opts.emit = EmitKind::MirFinal;
2501 opts.frame_pointer = true;
2502 let kept = run(&opts, source).text().to_owned();
2503 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
2504 }
2505
2506 fn asm(source: &str) -> String {
2508 let mut opts = options();
2509 opts.emit = EmitKind::Asm;
2510 let result = run(&opts, source);
2511 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2512 result.text().to_owned()
2513 }
2514
2515 #[test]
2522 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
2523 let text = asm("int add(int a, int b) { return a + b; }\n");
2524 assert!(text.contains("\t.globl\tadd\n"), "{text}");
2525 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
2526 assert!(text.contains("\nadd:\n"), "{text}");
2527 assert!(text.contains("\taddl\t"), "{text}");
2528 assert!(text.contains("\tret\n"), "{text}");
2529 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
2530 assert!(text.contains(".note.GNU-stack"), "{text}");
2533 }
2534
2535 #[test]
2541 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
2542 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
2543 assert!(text.contains("\tcall\t*%"), "{text}");
2544 assert!(text.contains("\tcall\tg\n"), "{text}");
2545 assert!(text.contains("%rdi"), "{text}");
2549 }
2550
2551 #[test]
2555 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
2556 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
2557 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
2558 }
2559
2560 #[test]
2569 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
2570 let arms = "return 1; return 2;";
2571 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
2572 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
2573 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
2574 assert!(
2575 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2576 "{operator}: {text}"
2577 );
2578 assert!(!text.contains("\tset"), "{operator}: {text}");
2579 assert!(!text.contains("\ttest"), "{operator}: {text}");
2580 }
2581 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
2582 for (operator, jump) in unsigned {
2583 let source =
2584 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
2585 let text = asm(&source);
2586 assert!(
2587 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2588 "{operator}: {text}"
2589 );
2590 }
2591
2592 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
2595 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
2596 }
2597
2598 #[test]
2604 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
2605 let text = asm("int f(int a, int b) { return a < b; }\n");
2606 assert!(text.contains("\tsetl\t"), "{text}");
2607 }
2608
2609 fn optimized(source: &str) -> String {
2611 let mut opts = options();
2612 opts.emit = EmitKind::Asm;
2613 opts.opt_level = rucc_session::OptLevel::O2;
2614 let result = run(&opts, source);
2615 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2616 result.text().to_owned()
2617 }
2618
2619 #[test]
2629 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
2630 let arms: String =
2631 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
2632 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2633 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
2634 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
2635 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
2636 }
2637
2638 #[test]
2645 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
2646 let arms: String = (0..16)
2647 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
2648 .collect::<Vec<_>>()
2649 .join(" ");
2650 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2651 assert!(text.matches("\tcmp").count() > 1, "{text}");
2652 }
2653
2654 #[test]
2662 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
2663 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
2664 assert!(text.contains("movl\t$2, %eax"), "{text}");
2665 assert!(!text.contains("cvttsd2si"), "{text}");
2666 }
2667
2668 #[test]
2676 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
2677 let text =
2678 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
2679 assert!(text.contains("movl\t$30, %eax"), "{text}");
2680 assert!(!text.contains("t(%rip)"), "{text}");
2681 }
2682
2683 #[test]
2686 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
2687 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
2688 assert!(text.contains("movl\t$98, %eax"), "{text}");
2689 }
2690
2691 #[test]
2695 fn a_table_that_is_not_read_only_keeps_its_load() {
2696 let text = optimized(
2697 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
2698 );
2699 assert!(!text.contains("movl\t$30, %eax"), "{text}");
2700 }
2701
2702 #[test]
2709 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
2710 let text = optimized(
2711 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
2712 );
2713 assert!(!text.contains("call\tlink_error"), "{text}");
2714 }
2715
2716 #[test]
2718 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2719 let text = asm("long f(void *p) { return (long)p; }\n");
2720 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2725 let mnemonic = line.split_whitespace().next().unwrap_or("");
2726 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2727 }
2728 }
2729
2730 #[test]
2734 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2735 let six = "long a, long b, long c, long d, long e, long f";
2736 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2737
2738 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2745 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2746
2747 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2751 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2752 let eight =
2753 "double a, double b, double c, double d, double e, double f, double g, double h";
2754 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2755 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2756 }
2757
2758 #[test]
2761 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2762 let six = "1, 2, 3, 4, 5, 6";
2763 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2764 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2765
2766 assert!(text.contains("\tmovq\t%"), "{text}");
2767 assert!(text.contains(", (%rsp)\n"), "{text}");
2768 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2769 assert!(text.contains("\tsubq\t$"), "{text}");
2771
2772 let narrow = "int g(int, int, int, int, int, int, int);\n";
2774 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2775 assert!(text.contains("\tmovl\t%"), "{text}");
2776 assert!(text.contains(", (%rsp)\n"), "{text}");
2777 }
2778
2779 #[test]
2782 fn a_variadic_call_counts_registers_and_not_arguments() {
2783 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2784 let decl = "int g(int, ...);\n";
2785 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2786
2787 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2788 assert!(text.contains("\tmovsd\t%"), "{text}");
2789 assert!(text.contains(", (%rsp)\n"), "{text}");
2790 }
2791
2792 #[test]
2797 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2798 let body =
2799 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2800 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2801
2802 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2805 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2806 assert!(!text.contains(", 0(%r"), "{text}");
2807 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2810 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2811
2812 assert!(text.contains("\tsubq\t$"), "{text}");
2814 }
2815
2816 #[test]
2819 fn va_start_writes_the_four_fields_the_psabi_describes() {
2820 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2821 let params = "int a, int b, int c, double d";
2822 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2823
2824 assert!(text.contains(" movl $24, "), "{text}");
2828 assert!(text.contains(" movl $64, "), "{text}");
2829 assert!(text.contains(", 8(%r"), "{text}");
2833 assert!(text.contains(", 16(%r"), "{text}");
2834 let frame: u32 = text
2835 .lines()
2836 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
2837 .expect("a variadic function takes a frame for the save area");
2838 let above = |line: &str| {
2839 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
2840 Some(at > frame)
2841 };
2842 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
2843 }
2844
2845 #[test]
2848 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
2849 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
2850 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
2851 let text = asm(&ints);
2852
2853 assert!(text.contains("$40, "), "{text}");
2856 assert!(text.contains(" cmpl "), "{text}");
2857 assert!(text.contains(" ja "), "{text}");
2861
2862 let arg = "__builtin_va_arg(ap, double)";
2863 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
2864 assert!(text.contains("$160, "), "the last vector slot: {text}");
2865 }
2866
2867 #[test]
2870 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
2871 let decl = "struct pair { long a, b; };\n";
2872 let body = "struct pair p = *q; return p.a + p.b;";
2873 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
2874
2875 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
2876 assert!(!text.contains("\tcall"), "{text}");
2877 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
2879 }
2880
2881 #[test]
2884 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
2885 let decl = "struct bytes { char a[8]; };\n";
2886 let body = "struct bytes p = *q; return p.a[0];";
2887 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
2888
2889 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
2891 }
2892
2893 #[test]
2896 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
2897 let decl = "struct wide { long a, b, c; };\n";
2898 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
2899
2900 assert!(!text.contains("memset"), "nothing calls the library: {text}");
2901 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
2906 }
2907
2908 #[test]
2911 fn a_copy_too_large_to_unroll_calls_the_runtime() {
2912 let decl = "struct huge { char a[4096]; };\n";
2913 let mut opts = options();
2914 opts.emit = EmitKind::Asm;
2915 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
2916 let result = run(&opts, &source);
2917 assert!(!result.failed(), "{:?}", result.messages);
2918 let text = result.text();
2919 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
2920 assert!(text.contains("4096"), "the size travels: {text}");
2923 }
2924
2925 #[test]
2932 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
2933 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
2934 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
2935
2936 let copy = text.find("call\tmemcpy").expect("the copy");
2937 let call = text.find("call\ttake").expect("the call");
2938 assert!(copy < call, "the copy comes first: {text}");
2939 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
2944 assert!(text.contains("$4096, %edx"), "the size: {text}");
2945 }
2946
2947 #[test]
2950 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
2951 let six = "long a, long b, long c, long d, long e, long f";
2952 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
2953 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
2954
2955 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
2959 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
2960 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
2961 }
2962
2963 #[test]
2965 fn the_target_decides_how_the_assembly_is_spelled() {
2966 let mut opts = options();
2967 opts.emit = EmitKind::Asm;
2968 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
2969 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
2970 assert!(text.contains("__TEXT,__text"), "{text}");
2971 assert!(text.contains("\n_f:\n"), "{text}");
2972 assert!(!text.contains(".note.GNU-stack"), "{text}");
2973 }
2974
2975 fn obj(source: &str) -> Vec<u8> {
2977 let mut opts = options();
2978 opts.emit = EmitKind::Object;
2979 let result = run(&opts, source);
2980 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2981 match result.artifact {
2982 Artifact::Object { bytes, .. } => bytes,
2983 other => panic!("expected an object, got {other:?}"),
2984 }
2985 }
2986
2987 #[test]
2993 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
2994 let bytes = obj("int add(int a, int b) { return a + b; }\n");
2995 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
2996 let text = asm("int add(int a, int b) { return a + b; }\n");
2997 assert!(
2998 text.contains("\taddl\t"),
2999 "and the listing of it is the same instructions:\n{text}"
3000 );
3001 }
3002
3003 #[test]
3005 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3006 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3007 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3008 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3009 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3010 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3013 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3014 assert!(!text.contains(".globl\thidden"), "{text}");
3015 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3018 }
3019
3020 #[test]
3027 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3028 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3029 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3030 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3031
3032 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3035 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3036
3037 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3040 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3041
3042 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3044 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3045 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3046 }
3047
3048 #[test]
3050 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3051 let text = asm("const char *f(void) { return \"hi\"; }\n");
3052 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3053 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3054 let label = text
3055 .lines()
3056 .find(|line| line.starts_with(".Lstr"))
3057 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3058 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3059 }
3060
3061 #[test]
3063 fn an_address_in_an_initializer_is_left_to_the_linker() {
3064 let source = "int counter;\nint *p = &counter;\n";
3065 let text = asm(source);
3066 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3067 let bytes = obj(source);
3070 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3071 }
3072
3073 #[test]
3082 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3083 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3086 struct m { void (*x)(void); void (*y)(void); };\n\
3087 const struct m t = { a, b };\n");
3088 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3089 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3090
3091 let text =
3094 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3095 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3096
3097 let text = asm("const int fixed = 7;\n");
3099 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3100 }
3101
3102 #[test]
3109 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3110 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3111 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3114 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3115 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3118 assert!(text.contains("%fs:0"), "{text}");
3119 }
3120
3121 #[test]
3127 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3128 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3129 assert!(text.contains("movq\t%fs:0, "), "{text}");
3130 assert!(!text.contains("GOTTPOFF"), "{text}");
3132 }
3133
3134 #[test]
3145 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3146 for (locality, wanted) in
3147 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3148 {
3149 let source =
3150 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3151 let text = asm(&source);
3152 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3153 }
3154 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3156 assert!(text.contains("\tprefetcht0\t"), "{text}");
3157 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3160 assert!(text.contains("\tprefetcht0\t"), "{text}");
3161 assert!(!text.contains("prefetchw"), "{text}");
3162 }
3163
3164 #[test]
3175 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3176 let text = asm("void stop(void) { __builtin_trap(); }\n");
3177 assert!(text.contains("\tud2\n"), "{text}");
3178 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3179
3180 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3181 assert!(text.contains("\tud2\n"), "{text}");
3182 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3183 }
3184
3185 #[test]
3197 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3198 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3199 assert!(!text.contains("assume_aligned"), "{text}");
3200 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3201
3202 let source = "unsigned long width(void);\n\
3203 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3204 let text = asm(source);
3205 assert!(!text.contains("assume_aligned"), "{text}");
3206 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3207 }
3208
3209 #[test]
3219 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3220 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3221 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3222 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3223 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3224
3225 let walk = |depth: u32| {
3226 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3227 asm(&source).matches("movq\t(%r").count()
3228 };
3229 assert_eq!(walk(1), 1, "one link is one load");
3230 assert_eq!(walk(3), 3, "three links are three loads");
3231 }
3232
3233 #[test]
3243 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3244 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3245 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3246 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3247 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3248
3249 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3250 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3251 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3252 }
3253
3254 #[test]
3265 fn a_depth_that_is_not_a_small_constant_is_refused() {
3266 let mut opts = options();
3267 opts.emit = EmitKind::Ir;
3268 for source in [
3269 "void *up(int n) { return __builtin_return_address(n); }\n",
3270 "void *up(void) { return __builtin_frame_address(1000); }\n",
3271 ] {
3272 let messages = run(&opts, source).messages;
3273 let named = messages.iter().any(|m| m.contains("E0705"));
3274 assert!(named, "expected a refusal in {messages:?}");
3275 }
3276 }
3277
3278 #[test]
3290 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3291 let text =
3292 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3293 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
3294 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
3295 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3296
3297 let plain = concat!(
3300 "extern void *alloca(__SIZE_TYPE__);\n",
3301 "void use(void *p);\n",
3302 "void f(unsigned long n) { use(alloca(n)); }\n",
3303 );
3304 let text = asm(plain);
3305 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
3306 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
3307
3308 let own = concat!(
3311 "static void *alloca(unsigned long n) { return 0; }\n",
3312 "void *f(unsigned long n) { return alloca(n); }\n",
3313 );
3314 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3315 }
3316
3317 #[test]
3327 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3328 let inner = "{ use(__builtin_alloca(n)); }";
3329 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3330 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3331 let text = asm(&source);
3332 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3336 let taking = line.contains("subq");
3337 let leaving = line.contains("%rbp");
3338 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3339 }
3340 }
3341 }
3342
3343 #[test]
3345 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3346 let source = "int callee(void); int g(void) { return callee(); }\n";
3350 let bytes = obj(source);
3351 assert!(
3352 bytes.windows(7).any(|w| w == b"callee\0"),
3353 "the object has to name the callee for the linker to find it"
3354 );
3355 let text = asm(source);
3356 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3357 }
3358
3359 #[test]
3365 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3366 let mut opts = options();
3367 opts.emit = EmitKind::Executable;
3369 let result = run(&opts, "int main(void) { return 0; }\n");
3370 assert_eq!(result.messages, Vec::<String>::new());
3371 match result.artifact {
3372 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3373 other => panic!("expected an object, got {other:?}"),
3374 }
3375 }
3376
3377 #[test]
3379 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3380 let mut opts = options();
3381 opts.emit = EmitKind::Object;
3382 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3383 let result = run(&opts, "int f(void) { return 0; }\n");
3384 assert!(result.failed(), "an object nobody can read is worse than a message");
3385 assert!(
3386 result.messages.iter().any(|m| m.contains("no object writer")),
3387 "{:?}",
3388 result.messages
3389 );
3390 }
3391
3392 fn ir(source: &str) -> String {
3394 let mut opts = options();
3395 opts.emit = EmitKind::Ir;
3396 let result = run(&opts, source);
3397 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3398 result.text().to_owned()
3399 }
3400
3401 fn errors(source: &str) -> Vec<String> {
3403 let mut opts = options();
3404 opts.emit = EmitKind::Ir;
3405 let result = run(&opts, source);
3406 assert!(result.failed(), "expected this to be refused:\n{source}");
3407 result.messages
3408 }
3409
3410 fn body(source: &str) -> String {
3412 let text = ir(source);
3413 let (_, rest) = text.split_once("{\n").expect("a function definition");
3414 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3415 body.to_owned()
3416 }
3417
3418 #[test]
3426 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3427 let source = "inline int f(int x) { return x + 1; }\n";
3428 let with = |flag: bool| {
3429 let mut opts = options();
3430 opts.emit = EmitKind::Ir;
3431 opts.gnu89_inline = flag;
3432 let result = run(&opts, source);
3433 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3434 result.text().to_owned()
3435 };
3436
3437 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3440
3441 assert!(with(true).contains("block0"), "a body: {}", with(true));
3444 }
3445
3446 #[test]
3453 fn an_access_through_a_type_names_the_type_it_went_through() {
3454 let source = "\
3455struct s { int a; float b; };\n\
3456union u { int i; float f; };\n\
3457int scalar(int *p) { return *p; }\n\
3458float member(struct s *p) { p->a = 1; return p->b; }\n\
3459int element(int *a, long i) { return a[i]; }\n\
3460float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3461 let text = ir(source);
3462 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3463 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3464 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3465 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3468 assert_eq!(named, 6, "six accesses: {text}");
3469 }
3470
3471 #[test]
3478 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3479 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3480 let mut opts = options();
3481 opts.emit = EmitKind::Ir;
3482 opts.strict_aliasing = false;
3483 let result = run(&opts, source);
3484 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3485 let text = result.text().to_owned();
3486 assert!(!text.contains("tbaa"), "not even the root: {text}");
3487 }
3488
3489 #[test]
3497 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3498 let mut opts = options();
3499 opts.emit = EmitKind::Ir;
3500 opts.std = Std::C89;
3501 let compiled = |source: &str| {
3502 let result = run(&opts, source);
3503 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3504 result.text().to_owned()
3505 };
3506
3507 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3508 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3509 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3510
3511 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3513 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3514 }
3515
3516 #[test]
3524 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3525 let mut opts = options();
3526 opts.emit = EmitKind::Ir;
3527 opts.std = Std::C89;
3528 let compiled = |source: &str| {
3529 let result = run(&opts, source);
3530 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3531 result.text().to_owned()
3532 };
3533
3534 let text = compiled("int f(void) { return g(); }\n");
3536 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3537 assert!(text.contains("i32"), "and it gives back an int: {text}");
3538
3539 let text = compiled("int f(char c) { return g(c); }\n");
3542 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3543
3544 let mut opts = options();
3547 opts.std = Std::C89;
3548 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3549 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3550 }
3551
3552 #[test]
3562 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3563 let mut opts = options();
3564 opts.emit = EmitKind::Ir;
3565 opts.std = Std::C89;
3566 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3567 .text()
3568 .to_owned();
3569 assert!(text.contains("func @f()"), "the caller is there: {text}");
3570 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3571 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3572 }
3573
3574 #[test]
3582 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3583 let mut opts = options();
3584 opts.emit = EmitKind::Ir;
3585 opts.std = Std::C89;
3586 let compiled = |source: &str| run(&opts, source).text().to_owned();
3587
3588 let text = compiled("f (c) unsigned char c; { return c; }\n");
3589 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3590 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3591 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3592
3593 let text = compiled("f (s) short s; { return s; }\n");
3595 assert!(text.contains("trunc.i16"), "cut down: {text}");
3596 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3597
3598 let text = compiled("f (x) float x; { return x * 2; }\n");
3601 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3602 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3603
3604 let text = compiled("int f(unsigned char c) { return c; }\n");
3607 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3608 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3609 }
3610
3611 #[test]
3620 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3621 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3623 let cases = [
3624 ("static counted;\n", ["", "error", "warning", "error"]),
3625 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3626 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3627 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3628 (
3629 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3630 ["warning", "error", "warning", "error"],
3631 ),
3632 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3633 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3634 ];
3635
3636 for (source, wanted) in cases {
3637 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3638 let mut opts = options();
3639 opts.std = std;
3640 opts.permissive = permissive;
3641 let said = run(&opts, source).messages.join("\n");
3642 let severity = if said.contains(": error: ") {
3643 "error"
3644 } else if said.contains(": warning: ") {
3645 "warning"
3646 } else {
3647 ""
3648 };
3649 let how = if permissive { " -fpermissive" } else { "" };
3650 assert_eq!(
3651 severity,
3652 wanted,
3653 "under -std={}{how}, {source} was answered with `{said}`",
3654 std.as_str()
3655 );
3656 if wanted.is_empty() {
3657 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3658 }
3659 }
3660 }
3661 }
3662
3663 #[test]
3672 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3673 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3674 let cases = [
3675 (
3676 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3677 "first argument to 'va_arg' not of type 'va_list'",
3678 ["error", "error", "error", "error"],
3679 ),
3680 (
3681 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3682 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3683 ["warning", "error", "warning", "error"],
3684 ),
3685 (
3686 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3687 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3688 cast",
3689 ["warning", "error", "warning", "error"],
3690 ),
3691 (
3692 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3693 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3694 ["warning", "error", "warning", "error"],
3695 ),
3696 ];
3697
3698 for (source, message, wanted) in cases {
3699 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3700 let mut opts = options();
3701 opts.std = std;
3702 opts.permissive = permissive;
3703 let said = run(&opts, source).messages.join("\n");
3704 let how = if permissive { " -fpermissive" } else { "" };
3705 assert!(
3706 said.contains(&format!(": {wanted}: {message}")),
3707 "under -std={}{how}, {source} was answered with `{said}`",
3708 std.as_str()
3709 );
3710 }
3711 }
3712 }
3713
3714 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3716 let mut opts = options();
3717 opts.emit = EmitKind::Ir;
3718 opts.safety = tier;
3719 let result = run(&opts, source);
3720 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3721 result.text().to_owned()
3722 }
3723
3724 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3725
3726 fn padded_ir(padding: Padding, source: &str) -> String {
3728 let mut opts = options();
3729 opts.emit = EmitKind::Ir;
3730 opts.safety = rucc_session::Safety::Detect;
3731 opts.padding = padding;
3732 let result = run(&opts, source);
3733 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3734 result.text().to_owned()
3735 }
3736
3737 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3738 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3739
3740 #[test]
3741 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3742 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3746 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3747 }
3748
3749 #[test]
3750 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3751 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3754 assert!(!text.contains("owns"), "{text}");
3755 }
3756
3757 #[test]
3758 fn a_member_of_a_union_owns_nothing_after_it() {
3759 let text = padded_ir(
3763 Padding::Ignored,
3764 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3765 );
3766 assert!(!text.contains("owns"), "{text}");
3767 }
3768
3769 #[test]
3770 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3771 let text = padded_ir(
3776 Padding::Ignored,
3777 "struct inner { char c; };\n\
3778 struct outer { struct inner in; int x; };\n\
3779 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3780 );
3781 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3782 }
3783
3784 #[test]
3785 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3786 let text = ir(READS_THROUGH_A_POINTER);
3790 assert!(!text.contains("check_"), "{text}");
3791 assert!(!text.contains("cap_of"), "{text}");
3792 }
3793
3794 #[test]
3795 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3796 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3797 assert!(text.contains("cap_of"), "{text}");
3798 assert!(text.contains("check_bounds"), "{text}");
3799 assert!(text.contains("check_live"), "{text}");
3800 assert!(text.contains("check_deriv"), "{text}");
3802 assert!(text.contains("check_type"), "{text}");
3804 }
3805
3806 #[test]
3807 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3808 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3812 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3813 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3814 }
3815 }
3816
3817 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3819 let mut opts = options();
3820 opts.emit = EmitKind::SafetySummary;
3821 opts.safety = tier;
3822 let result = run(&opts, source);
3823 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3824 result.text().to_owned()
3825 }
3826
3827 #[test]
3828 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3829 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3830 assert!(text.contains("\"tier\": \"detect\""), "{text}");
3831 assert!(
3833 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
3834 "{text}"
3835 );
3836 assert!(
3837 text.contains(
3838 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
3839 ),
3840 "{text}"
3841 );
3842 }
3843
3844 #[test]
3845 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
3846 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
3850 assert!(text.contains("\"tier\": \"off\""), "{text}");
3851 assert!(
3852 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
3853 "{text}"
3854 );
3855 }
3856
3857 #[test]
3858 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
3859 let text = summary(
3860 rucc_session::Safety::Detect,
3861 "void *memcpy(void *, const void *, unsigned long);\n\
3862 int puts(const char *);\n\
3863 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
3864 );
3865 assert!(text.contains("\"interposed\": 1"), "{text}");
3866 assert!(text.contains("\"puts\""), "{text}");
3867 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
3871 }
3872
3873 #[test]
3874 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
3875 let text = summary(
3880 rucc_session::Safety::Detect,
3881 "void *memcpy(void *, const void *, unsigned long);\n\
3882 int puts(const char *);\n\
3883 void *table[2] = { (void *)memcpy, (void *)puts };\n\
3884 void *f(int i) { return table[i]; }\n",
3885 );
3886 assert!(text.contains("\"interposed\": 1"), "{text}");
3887 assert!(text.contains("\"puts\""), "{text}");
3888 assert!(!text.contains("\"memcpy\""), "{text}");
3889 }
3890
3891 #[test]
3892 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3893 let text = summary(
3897 rucc_session::Safety::Detect,
3898 "void *notes_open(void);\n\
3899 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3900 );
3901 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3902 assert!(text.contains("\"notes_open\""), "{text}");
3903 }
3904
3905 #[test]
3906 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3907 let text = summary(
3910 rucc_session::Safety::Detect,
3911 "static int len(const char *p) { return p ? 1 : 0; }\n\
3912 int f(void) { return len(\"x\"); }\n",
3913 );
3914 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3915 }
3916
3917 fn granules(source: &str) -> String {
3919 let mut opts = options();
3920 opts.emit = EmitKind::TypeGranules;
3921 let result = run(&opts, source);
3922 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3923 result.text().to_owned()
3924 }
3925
3926 #[test]
3927 fn the_granule_report_names_every_record_and_both_keyings() {
3928 let text = granules(
3929 "struct hot { char *p; int a; int b; };\n\
3930 int f(struct hot *h) { return h->a; }\n",
3931 );
3932 assert!(text.contains("struct hot"), "{text}");
3933 assert!(text.contains("every type distinct"), "{text}");
3936 assert!(text.contains("every pointer one type"), "{text}");
3937 assert!(text.contains("budget"), "{text}");
3938 }
3939
3940 #[test]
3941 fn a_record_nothing_uses_is_still_measured() {
3942 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3945 assert!(text.contains("struct unused"), "{text}");
3946 }
3947
3948 #[test]
3949 fn the_granule_report_stops_before_anything_is_lowered() {
3950 let text = granules(
3954 "struct wide { long double d; };\n\
3955 long double f(long double x) { return x * x; }\n",
3956 );
3957 assert!(text.contains("struct wide"), "{text}");
3958 }
3959
3960 #[test]
3961 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
3962 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
3965 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
3966 }
3967
3968 #[test]
3969 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
3970 let text = summary(
3971 rucc_session::Safety::Detect,
3972 "unsigned long f(int *p) { return (unsigned long) p; }\n",
3973 );
3974 assert!(text.contains("\"exposed\": 1"), "{text}");
3975 }
3976
3977 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
3979 let mut opts = options();
3980 opts.emit = EmitKind::Asm;
3981 opts.safety = tier;
3982 let result = run(&opts, source);
3983 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3984 result.text().to_owned()
3985 }
3986
3987 #[test]
3988 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
3989 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3990 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
3991 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
3992 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
3993 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
3994 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
3995 }
3996
3997 #[test]
3998 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
3999 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4003 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4004 assert_eq!(text.matches(§ion).count(), 5, "{text}");
4005 for index in 0..5 {
4006 let name = format!("__rucc_safety_desc_{index}");
4007 assert!(text.contains(&format!("{name}:\n")), "{text}");
4010 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4011 }
4012 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
4013 }
4014
4015 #[test]
4023 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4024 let text = ir(concat!(
4025 "int g;\n",
4026 "int a = __builtin_constant_p(1);\n",
4027 "int b = __builtin_constant_p(g);\n",
4028 "int c = __builtin_constant_p(\"abc\");\n",
4029 "int d = __builtin_constant_p(&g);\n",
4030 "int e = __builtin_constant_p(1.5);\n",
4031 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4032 ));
4033 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4034 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4035 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4036 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4037 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4038 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4039 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4040
4041 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4045 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4046 }
4047
4048 #[test]
4057 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4058 let text = body("void f(void) { __builtin_abort(); }\n");
4059 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4060
4061 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4064 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4065 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4066 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4067 }
4068
4069 #[test]
4082 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4083 let text = ir(concat!(
4084 "char d[8];\n",
4085 "void f(const char *s, unsigned long n) {\n",
4086 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4087 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4088 " __builtin___memset_chk(d, 0, n, 8);\n",
4089 "}\n",
4090 ));
4091 assert!(text.contains("call @__memcpy_chk("), "{text}");
4092 assert!(text.contains("call @__strcpy_chk("), "{text}");
4093 assert!(text.contains("call @__memset_chk("), "{text}");
4094 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4095 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4096 }
4097
4098 #[test]
4106 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4107 let text = ir(concat!(
4108 "extern char *p;\n",
4109 "char d[8];\n",
4110 "void f(const char *s, unsigned long n) {\n",
4111 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4112 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4113 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4114 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4115 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4116 "}\n",
4117 ));
4118
4119 assert!(
4121 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4122 "{text}"
4123 );
4124
4125 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4128 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4129 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4130
4131 assert!(text.contains("call @__sprintf_chk("), "{text}");
4134
4135 let asm = asm(concat!(
4138 "void f(char *p, const char *s, unsigned long n) {\n",
4139 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4140 "}\n",
4141 ));
4142 assert!(asm.contains("call\tmemcpy"), "{asm}");
4143 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4144 }
4145
4146 #[test]
4154 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4155 let text = ir(concat!(
4156 "char d[64];\n",
4157 "int f(const char *fmt, ...) {\n",
4158 " __builtin_va_list ap;\n",
4159 " __builtin_va_start(ap, fmt);\n",
4160 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4161 " __builtin_va_end(ap);\n",
4162 " return n;\n",
4163 "}\n",
4164 ));
4165 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4166 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4167 }
4168
4169 #[test]
4180 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4181 let text = body(concat!(
4182 "long long llabs(long long);\n",
4183 "long long f(long long x) { return llabs(x); }\n",
4184 ));
4185 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4186 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4187 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4188 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4189 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4190
4191 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4194 assert!(text.contains("iconst.i32 31"), "{text}");
4195 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4196 assert!(text.contains("iconst.i64 63"), "{text}");
4197
4198 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4201 assert!(!text.contains("call"), "{text}");
4202
4203 let text = ir(concat!(
4205 "long long llabs(long long b);\n",
4206 "long long g(long long x) { return llabs(x); }\n",
4207 "long long llabs(long long b) { return 7; }\n",
4208 ));
4209 assert!(!text.contains("call @llabs"), "{text}");
4210 }
4211
4212 #[test]
4219 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4220 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4221 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4222
4223 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4226 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4227 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4228 }
4229
4230 #[test]
4236 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4237 for (name, ty, width) in [
4238 ("__builtin_bswap16", "unsigned short", "i16"),
4239 ("__builtin_bswap32", "unsigned", "i32"),
4240 ("__builtin_bswap64", "unsigned long long", "i64"),
4241 ] {
4242 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4243 let text = body(&source);
4244 assert_eq!(
4245 text,
4246 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4247 "{name}"
4248 );
4249 }
4250 }
4251
4252 #[test]
4259 fn the_bit_counts_are_instructions_and_not_calls() {
4260 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4261 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4262
4263 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4264 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4265
4266 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4267 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4268 }
4269
4270 #[test]
4279 fn the_bit_counts_ask_about_the_width_their_name_says() {
4280 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4281 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4282 assert!(text.contains("%1 = ctlz %0"), "{text}");
4283 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4284
4285 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4288 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4289 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4290
4291 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4292 assert!(text.contains("%1 = ctpop %0"), "{text}");
4293 assert!(!text.contains("call"), "{text}");
4294 }
4295
4296 #[test]
4301 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4302 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4303 assert!(text.contains("%1 = ctpop %0"), "{text}");
4304 assert!(text.contains("iconst.i32 1"), "{text}");
4305 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4306 }
4307
4308 #[test]
4314 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4315 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4316 assert!(text.contains("%1 = cttz %0"), "{text}");
4317 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4318 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4319 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4320 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4321 assert!(!text.contains("br_if"), "no branch: {text}");
4322 }
4323
4324 #[test]
4334 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4335 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4336 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4337 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4338 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4339 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4340 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4341 assert!(text.contains("%7 = ctlz %6"), "{text}");
4342 assert!(!text.contains("call"), "{text}");
4343 assert!(!text.contains("br_if"), "no branch: {text}");
4344 }
4345
4346 #[test]
4352 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4353 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4354 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4355 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4356 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4357
4358 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4359 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4360
4361 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4364 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4365 }
4366
4367 #[test]
4375 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4376 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4377 assert!(text.contains("iconst.i64 63"), "{text}");
4378 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4379 assert!(!text.contains("call"), "{text}");
4380
4381 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4382 assert!(text.contains("iconst.i64 63"), "{text}");
4383 assert!(!text.contains("call"), "{text}");
4384 }
4385
4386 #[test]
4394 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4395 let text =
4396 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4397 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4398 assert!(!text.contains("store"), "nothing is written: {text}");
4399 assert!(!text.contains("call"), "{text}");
4400
4401 let text =
4404 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4405 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4406 assert!(!text.contains("store"), "{text}");
4407
4408 let text = body(concat!(
4411 "int g(void);\n",
4412 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4413 ));
4414 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4415 }
4416
4417 #[test]
4427 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4428 let text =
4429 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4430 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4431 assert!(text.contains("store %3 -> %2"), "{text}");
4432 assert!(!text.contains("call"), "{text}");
4433
4434 let text =
4435 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4436 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4437
4438 let text =
4439 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4440 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4441
4442 let text = body(
4445 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4446 );
4447 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4448 }
4449
4450 #[test]
4458 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4459 let text = body(
4460 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4461 );
4462 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4463 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4464 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4465
4466 let text = body(
4469 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4470 );
4471 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4472 assert!(!text.contains("sext."), "{text}");
4473 assert!(!text.contains("zext.i64"), "{text}");
4475 }
4476
4477 #[test]
4485 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4486 let text =
4487 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4488 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4489 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4490 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4491 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4492 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4493 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4494 }
4495
4496 #[test]
4503 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4504 for name in ["add", "sub", "mul"] {
4505 let source = format!(
4506 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4507 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4508 );
4509 let mut opts = options();
4510 opts.emit = EmitKind::MirFinal;
4511 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4512 }
4513 }
4514
4515 #[test]
4518 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4519 let messages =
4520 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4521 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4522
4523 let messages =
4524 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4525 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4526 }
4527
4528 #[test]
4539 fn an_ordered_access_is_ordered_in_the_ir() {
4540 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4541 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4542
4543 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4544 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4545
4546 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4547 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4548
4549 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4550 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4551
4552 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4555 assert!(text.contains("trunc.i8 %1"), "{text}");
4556 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4557 }
4558
4559 #[test]
4568 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4569 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4570 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4571 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4572
4573 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4574 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4575 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4576
4577 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4578 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4579 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4580 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4581 }
4582
4583 #[test]
4593 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4594 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4595 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4596
4597 for weaker in ["1", "2", "3", "4"] {
4598 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4599 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4600 }
4601 }
4602
4603 #[test]
4613 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
4614 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
4615 let source = format!("void f(void) {{ {name}(); }}\n");
4616 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
4617 let text = body(&source);
4618 assert!(text.contains("fence seq_cst"), "{name}: {text}");
4619 }
4620
4621 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
4622 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4623 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
4624 }
4625
4626 #[test]
4632 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4633 let text =
4636 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4637 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4638 assert!(text.contains("return %3"), "the value it found: {text}");
4639
4640 let text =
4641 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4642 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4643 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4644
4645 let text = body(
4648 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4649 );
4650 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4651 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4652 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4653 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4654
4655 let text = body(
4658 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4659 );
4660 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4661 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4662 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4663 }
4664
4665 #[test]
4672 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4673 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4674 for (ty, suffix, reg) in widths {
4675 let source = format!(
4676 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4677 );
4678 let text = asm(&source);
4679 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4680 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4681 assert!(text.contains("sete\t"), "{ty}: {text}");
4682 }
4683 let source =
4684 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4685 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4686
4687 for order in ["0", "2", "3", "4", "5"] {
4691 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4692 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4693 let text = asm(&source);
4694 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4695 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4696 }
4697 }
4698
4699 #[test]
4711 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4712 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4713 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4714 assert!(text.contains("return %2"), "the value that was there: {text}");
4715
4716 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4717 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4718 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4719
4720 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4721 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4722 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4723
4724 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4726 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4727
4728 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4731 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4732
4733 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4734 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4735
4736 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4739 assert!(text.contains("release"), "{text}");
4740 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4741
4742 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4746 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4747 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4748
4749 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4752 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4753
4754 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4755 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4756 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4757
4758 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4761 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4762 assert!(text.contains("%3 = and %2, %1"), "{text}");
4763 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4764 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4765 }
4766
4767 #[test]
4778 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4779 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4780 for (ty, suffix, reg) in widths {
4781 for (name, call, insn) in [
4782 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4783 ("or", "__sync_fetch_and_or(p, v)", "or"),
4784 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4785 ] {
4786 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4787 let text = asm(&source);
4788 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4789 assert!(
4790 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4791 "{ty} {name}: {text}"
4792 );
4793 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4794 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4796 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4797 }
4798 }
4799 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4800 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4801
4802 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4806 assert!(text.contains("cmpxchgl\t"), "{text}");
4807 assert!(text.contains("andl\t"), "{text}");
4808 assert!(text.contains("notl\t"), "{text}");
4809 }
4810
4811 #[test]
4820 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4821 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4822 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4823 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4824
4825 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4826 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4827 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4828
4829 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4832 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4833 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4834 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4835 }
4836
4837 #[test]
4848 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4849 for pointer in ["char", "int", "void"] {
4850 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4851 let text = body(&source);
4852 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4853 assert!(
4854 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4855 "{pointer}: {text}"
4856 );
4857 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4858
4859 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4860 let text = body(&source);
4861 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4862 }
4863
4864 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4867 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4868 assert!(text.contains("setne\t"), "{text}");
4869 }
4870
4871 #[test]
4879 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4880 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4881 for (ty, suffix, reg) in widths {
4882 let source =
4883 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4884 let text = asm(&source);
4885 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4886 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4887
4888 let source =
4889 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4890 let text = asm(&source);
4891 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4892 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4893 }
4894 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4895 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4896
4897 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4900 let text = asm(source);
4901 assert!(text.contains("negl\t"), "{text}");
4902 assert!(text.contains("xaddl\t"), "{text}");
4903
4904 for order in ["0", "2", "3", "4", "5"] {
4907 let source =
4908 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4909 let text = asm(&source);
4910 assert!(text.contains("xaddl\t"), "{order}: {text}");
4911 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4912 }
4913
4914 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4918 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4919 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4926 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
4927 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4928 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4929 }
4930
4931 #[test]
4943 fn the_lock_free_questions_are_answered_as_constants() {
4944 for size in ["1", "2", "4", "8"] {
4945 let source =
4946 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4947 let text = asm(&source);
4948 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4949 assert!(!text.contains("call"), "and is not a call: {text}");
4950 }
4951 for size in ["3", "16", "sizeof(long double)"] {
4952 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4953 let text = asm(&source);
4954 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
4955 assert!(!text.contains("call"), "and is not a call either: {text}");
4956 }
4957
4958 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
4962 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
4963 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
4964 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
4965 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
4966 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
4967 }
4968
4969 #[test]
4981 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
4982 let mut opts = options();
4983 opts.emit = EmitKind::Ir;
4984
4985 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
4986 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
4987 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
4988
4989 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
4990 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
4991 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
4992
4993 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
4994 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
4995 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
4996 }
4997
4998 #[test]
5010 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5011 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5012 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5013 assert!(text.contains("shrq"), "with the value halved first: {text}");
5014 assert!(text.contains("addsd"), "and doubled after: {text}");
5015 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5016
5017 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5018 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5019 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5020 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5021 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5022 }
5023
5024 #[test]
5035 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5036 let taken = concat!(
5037 "static long long llabs(long long b) { return 7; }\n",
5038 "long long f(long long x) { return llabs(x); }\n",
5039 );
5040 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5041
5042 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5043 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5044
5045 let plain = concat!(
5046 "long long llabs(long long b);\n",
5047 "long long f(long long x) { return llabs(x); }\n",
5048 );
5049 let mut opts = options();
5050 opts.emit = EmitKind::Ir;
5051 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5052
5053 opts.builtins = false;
5054 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5055
5056 opts.builtins = true;
5057 opts.no_builtin = vec!["llabs".to_owned()];
5058 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5059 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5060 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5061
5062 opts.no_builtin = Vec::new();
5065 opts.builtins = false;
5066 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5067 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5068 }
5069
5070 #[test]
5083 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5084 let text = ir(concat!(
5085 "long a = __builtin_expect(7, 1);\n",
5086 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5087 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5088 ));
5089 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5090 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5091 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5092 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5093
5094 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5097 assert!(text.contains("sext"), "{text}");
5098
5099 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5103 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5104 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5105 assert_eq!(body(source), one);
5106
5107 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5112 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5113 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5114 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5115 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5116 }
5117
5118 #[test]
5130 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5131 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5132 let text = ir(promised);
5133 assert!(text.contains(" unreachable_hint\n"), "{text}");
5134 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5135
5136 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5140 assert!(after.contains("return"), "{after}");
5141
5142 let text = asm(promised);
5145 let mine = text.split_once("\nf:\n").expect("a definition").1;
5146 let mine = mine.split_once("\t.size").expect("a definition").0;
5147 let plain = asm("int f(int x) { if (x) return 1; }\n");
5148 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5149 let plain = plain.split_once("\t.size").expect("a definition").0;
5150 assert_eq!(mine, plain);
5151 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5154 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5155 assert!(!mine.contains("ud2"), "{mine}");
5156 }
5157
5158 #[test]
5165 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5166 let mut opts = options();
5167 opts.emit = EmitKind::Ir;
5168 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5169 assert!(
5170 messages.iter().any(|m| m.contains("__builtin_abort")),
5171 "expected the written name in {messages:?}"
5172 );
5173 }
5174
5175 #[test]
5183 fn a_builtin_nothing_lowers_is_refused_by_name() {
5184 let mut opts = options();
5185 opts.emit = EmitKind::Ir;
5186 let builtin = "__atomic_signal_fence";
5187 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5188 let messages = run(&opts, &source).messages;
5189 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5190 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5191 }
5192
5193 #[test]
5202 fn what_is_refused_is_the_call_and_not_the_name() {
5203 let text = ir(concat!(
5204 "void __atomic_signal_fence(int order) { (void)order; }\n",
5205 "void f(void) { __atomic_signal_fence(5); }\n",
5206 ));
5207 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5208 }
5209
5210 #[test]
5219 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5220 let text = ir(concat!(
5221 "struct S { char a[8]; int n; char b[12]; };\n",
5222 "char g[32];\n",
5223 "struct S gs;\n",
5224 "unsigned long whole = __builtin_object_size(g, 0);\n",
5225 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5226 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5227 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5228 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5229 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5230 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5231 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5232 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5233 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5234 ));
5235 for (name, size) in [
5236 ("whole", 32),
5237 ("moved", 28),
5238 ("back", 4),
5239 ("outer", 24),
5240 ("inner", 8),
5241 ("scalar", 4),
5242 ("after", 16),
5243 ("into", 10),
5244 ("text", 6),
5245 ("dyn", 12),
5246 ] {
5247 let said = format!("global @{name} : i64 = {size},");
5248 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5249 }
5250 }
5251
5252 #[test]
5260 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5261 let text = body(concat!(
5262 "struct S { char a[8]; int n; char b[12]; };\n",
5263 "unsigned long f(void) {\n",
5264 " char loc[20];\n",
5265 " struct S ls;\n",
5266 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5267 "}\n",
5268 ));
5269 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5270 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5271 }
5272
5273 #[test]
5283 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5284 let text = ir(concat!(
5285 "struct T { int n; char f[]; };\n",
5286 "extern char *p;\n",
5287 "extern struct T *t;\n",
5288 "unsigned long largest = __builtin_object_size(p, 0);\n",
5289 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5290 "unsigned long least = __builtin_object_size(p, 2);\n",
5291 "unsigned long tight = __builtin_object_size(p, 3);\n",
5292 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5293 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5294 ));
5295 for name in ["largest", "nearest", "flex"] {
5296 let said = format!("global @{name} : i64 = -1,");
5300 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5301 }
5302 for name in ["least", "tight"] {
5303 let said = format!("global @{name} : i64 = 0,");
5304 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5305 }
5306 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5307 }
5308
5309 #[test]
5316 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5317 let text = body(concat!(
5318 "extern char *side(void);\n",
5319 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5320 ));
5321 assert!(!text.contains("call"), "nothing is called: {text}");
5322 }
5323
5324 #[test]
5329 fn a_kind_that_is_not_one_of_the_four_is_refused() {
5330 for source in [
5331 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
5332 + "{ return __builtin_object_size(p, k); }\n",
5333 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
5334 .to_owned(),
5335 "extern char *p;\nunsigned long f(void) ".to_owned()
5336 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
5337 ] {
5338 let messages = errors(&source);
5339 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
5340 assert!(named, "expected a complaint about the kind in {messages:?}");
5341 }
5342 }
5343
5344 #[test]
5350 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
5351 let text = ir(concat!(
5352 "void *buf[5];\n",
5353 "int f(void) {\n",
5354 " if (__builtin_setjmp(buf)) return 2;\n",
5355 " return 1;\n",
5356 "}\n",
5357 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5358 ));
5359 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5360 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5361 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5362 }
5363
5364 #[test]
5372 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5373 let text = ir(concat!(
5374 "void *buf[5];\n",
5375 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5376 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5377 ));
5378 let (saves, plain) = text.split_once("func @g").expect("both functions");
5379 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5380 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5381 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5382 }
5383
5384 #[test]
5393 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5394 let text =
5395 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5396 let body = text.split_once("\nf:\n").expect("the function").1;
5397 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5398 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5399 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5400 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5401 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5402 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5403 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5404 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5405 }
5406
5407 #[test]
5415 fn a_save_destroys_every_register_the_allocator_hands_out() {
5416 let text =
5417 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5418 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5419 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5420 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5421 }
5422 }
5423
5424 #[test]
5431 fn the_restore_puts_the_frame_back_before_it_jumps() {
5432 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5433 let mut opts = options();
5434 opts.emit = EmitKind::Asm;
5435 opts.opt_level = level;
5436 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5437 let result = run(&opts, source);
5438 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5439 let text = result.text().to_owned();
5440 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5441 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5442 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5443 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5444 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5445 }
5446 }
5447
5448 #[test]
5454 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5455 for source in [
5456 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5457 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5458 ] {
5459 let messages = errors(source);
5460 let named = messages.iter().any(|m| m.contains("E0710"));
5461 assert!(named, "expected a complaint about the value in {messages:?}");
5462 }
5463 }
5464
5465 #[test]
5470 fn a_static_function_nothing_refers_to_is_not_emitted() {
5471 let text = ir("static int dropped(void) { return 1; }\n\
5472 static int kept(void) { return 2; }\n\
5473 int main(void) { return kept(); }\n");
5474 assert!(text.contains("func @kept"), "{text}");
5475 assert!(!text.contains("dropped"), "{text}");
5476 }
5477
5478 #[test]
5484 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5485 let text = ir("static int ping(void);\n\
5486 static int pong(void) { return ping(); }\n\
5487 static int ping(void) { return pong(); }\n\
5488 int main(void) { return 0; }\n");
5489 assert!(!text.contains("ping"), "{text}");
5490 assert!(!text.contains("pong"), "{text}");
5491 }
5492
5493 #[test]
5499 fn naming_a_static_function_anywhere_keeps_it() {
5500 let text = ir("static int by_address(void) { return 1; }\n\
5501 static int in_an_image(void) { return 2; }\n\
5502 static int deeper(void) { return 3; }\n\
5503 static int reaches_deeper(void) { return deeper(); }\n\
5504 static int (*table[1])(void) = {in_an_image};\n\
5505 int main(void) {\n\
5506 int (*p)(void) = by_address;\n\
5507 return p() + table[0]() + reaches_deeper();\n\
5508 }\n");
5509 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5510 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5511 }
5512 }
5513
5514 #[test]
5520 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5521 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5522 let source = format!(
5523 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5524 int main(void) {{ return 0; }}\n"
5525 );
5526 let text = ir(&source);
5527 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5528 }
5529 }
5530
5531 #[test]
5534 fn a_function_anything_could_call_is_emitted_without_being_called() {
5535 let text =
5536 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5537 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5538 }
5539
5540 #[test]
5547 fn a_classification_c_has_an_operator_for_is_that_operator() {
5548 for (builtin, operator) in [
5549 ("__builtin_isgreater", "binary >"),
5550 ("__builtin_isgreaterequal", "binary >="),
5551 ("__builtin_isless", "binary <"),
5552 ("__builtin_islessequal", "binary <="),
5553 ] {
5554 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5555 let text = tast(&source);
5556 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5557 }
5558 }
5559
5560 #[test]
5569 fn the_classification_builtins_are_comparisons_and_not_calls() {
5570 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5571 assert_eq!(
5572 text,
5573 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5574 %2\n return %3\n"
5575 );
5576
5577 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5579 assert!(text.contains("fcmp one %0, %1"), "{text}");
5580
5581 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5582 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5583
5584 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5585 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5586 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5587 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5588 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5589 assert!(text.contains("%5 = or %3, %4"), "{text}");
5590
5591 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5594 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5595 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5596 assert!(text.contains("%5 = and %3, %4"), "{text}");
5597
5598 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5599 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5600 assert!(text.contains("icmp slt %1, %2"), "{text}");
5601
5602 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5605 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5606
5607 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5610 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5611 }
5612
5613 #[test]
5620 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5621 let text = ir(concat!(
5622 "int a = __builtin_isinff(1e300);\n",
5623 "int b = __builtin_isinf(1e300);\n",
5624 "int c = __builtin_isnan(0.0);\n",
5628 "int d = __builtin_signbit(-0.0);\n",
5629 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5630 ));
5631 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5632 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5633 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5634 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5635 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5636 }
5637
5638 #[test]
5640 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5641 let mut opts = options();
5642 opts.emit = EmitKind::Ir;
5643 let source = concat!(
5644 "int a(int x) { return __builtin_isnan(x); }\n",
5645 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5646 "int c(double x) { return __builtin_isnan(x, x); }\n",
5647 );
5648 let messages = run(&opts, source).messages;
5649 assert_eq!(
5650 messages,
5651 [
5652 "/main.c:1:23: error: non-floating-point argument in call to function \
5653 '__builtin_isnan' [E0685]",
5654 "/main.c:2:30: error: non-floating-point arguments in call to function \
5655 '__builtin_isunordered' [E0685]",
5656 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5657 ]
5658 );
5659 }
5660
5661 #[test]
5670 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5671 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5672 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5676 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5677 assert!(text.contains("%3 = and %1, %2"), "{text}");
5678 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5679 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5680 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5681 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5682 assert!(text.contains("%8 = and %6, %7"), "{text}");
5683
5684 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5688 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5689 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5690
5691 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5692 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5693 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5694 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5695
5696 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5697 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5698 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5699 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5703 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5704 assert!(!text.contains("call"), "{text}");
5705
5706 let text = body(concat!(
5709 "double g(void);\n",
5710 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5711 ));
5712 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5713 }
5714
5715 #[test]
5722 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5723 let text = ir(concat!(
5724 "int a = __builtin_isnormal(1.0);\n",
5725 "int b = __builtin_isnormal(0.0);\n",
5726 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5727 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5728 "int e = __builtin_isinf_sign(1.0);\n",
5729 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5730 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5731 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5732 ));
5733 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5734 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5735 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5736 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5737 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5738 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5739 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5740 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5741 }
5742
5743 #[test]
5749 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5750 let mut opts = options();
5751 opts.emit = EmitKind::Ir;
5752 let source = concat!(
5753 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5754 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5755 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5756 );
5757 let messages = run(&opts, source).messages;
5758 assert_eq!(
5759 messages,
5760 [
5761 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5762 '__builtin_fpclassify' [E0687]",
5763 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5764 [E0511]",
5765 "/main.c:3:23: error: non-floating-point argument in call to function \
5766 '__builtin_fpclassify' [E0685]",
5767 ]
5768 );
5769 }
5770
5771 #[test]
5779 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5780 let text = ir(concat!(
5781 "double a = __builtin_inf();\n",
5782 "float b = __builtin_huge_valf();\n",
5783 "long double c = __builtin_infl();\n",
5784 "double d = __builtin_huge_val();\n",
5785 ));
5786 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5787 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5788 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5789 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5790 assert!(!text.contains("call"), "{text}");
5791 }
5792
5793 #[test]
5802 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5803 let text = ir(concat!(
5804 "double a = __builtin_nan(\"\");\n",
5805 "double b = __builtin_nan(\"0x1\");\n",
5806 "double c = __builtin_nan(\"010\");\n",
5808 "double d = __builtin_nans(\"\");\n",
5809 "double e = __builtin_nans(\"0x1\");\n",
5810 "float f = __builtin_nanf(\"0x1\");\n",
5811 "float g = __builtin_nansf(\"\");\n",
5812 "long double h = __builtin_nansl(\"\");\n",
5813 ));
5814 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5815 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5816 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5817 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5818 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5819 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5820 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5821 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5822
5823 let text = ir(concat!(
5826 "double f(const char *p) { return __builtin_nan(p); }\n",
5827 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5828 ));
5829 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5830 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5831 }
5832
5833 #[test]
5841 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5842 let text = ir(concat!(
5843 "unsigned long a = __builtin_strlen(\"hello\");\n",
5844 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5845 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5846 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5847 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5848 ));
5849 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5850 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5851 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5852 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5853 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5854 assert!(!text.contains("call"), "{text}");
5855
5856 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5858 assert!(text.contains("call @strlen("), "{text}");
5859 }
5860
5861 #[test]
5868 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5869 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5870 assert!(text.contains("bitcast.i64 %0"), "{text}");
5871 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5872 assert!(text.contains("and %1, %2"), "{text}");
5873 assert!(text.contains("bitcast.f64 %3"), "{text}");
5874 assert!(!text.contains("call"), "{text}");
5875
5876 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5877 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5878 assert!(text.contains("%8 = or %4, %7"), "{text}");
5879 assert!(!text.contains("call"), "{text}");
5880
5881 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5884 assert!(text.contains("bitcast.i80 %0"), "{text}");
5885 assert!(text.contains("bitcast.f80"), "{text}");
5886
5887 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5890 assert!(text.contains("fpext.f64 %0"), "{text}");
5891 assert!(text.contains("bitcast.i64 %1"), "{text}");
5892 }
5893
5894 #[test]
5903 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5904 let text =
5905 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5906 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5907 assert!(!text.contains("call"), "{text}");
5908
5909 let text =
5910 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5911 assert!(text.contains("bitcast.i32 %0"), "{text}");
5912 assert!(!text.contains("call"), "{text}");
5913
5914 let text = body(concat!(
5915 "double copysign(double x, double y);\n",
5916 "double f(double x, double y) { return copysign(x, y); }\n",
5917 ));
5918 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5919 assert!(!text.contains("call"), "{text}");
5920
5921 let text = body(concat!(
5922 "float copysignf(float x, float y);\n",
5923 "float f(float x, float y) { return copysignf(x, y); }\n",
5924 ));
5925 assert!(!text.contains("call"), "{text}");
5926
5927 let text = ir(concat!(
5931 "long double fabsl(long double x);\n",
5932 "long double f(long double x) { return fabsl(x); }\n",
5933 ));
5934 assert!(text.contains("call @fabsl"), "{text}");
5935 }
5936
5937 #[test]
5945 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5946 let taken = concat!(
5947 "static double fabs(double b) { return 7; }\n",
5948 "double f(double x) { return fabs(x); }\n",
5949 );
5950 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5951
5952 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5953 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5954
5955 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
5956 let mut opts = options();
5957 opts.emit = EmitKind::Ir;
5958 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
5959
5960 opts.builtins = false;
5961 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
5962
5963 opts.builtins = true;
5964 opts.no_builtin = vec!["fabs".to_owned()];
5965 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
5966 let one = concat!(
5967 "double copysign(double a, double b);\n",
5968 "double f(double x) { return copysign(x, 1.0); }\n",
5969 );
5970 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
5971
5972 opts.no_builtin = Vec::new();
5974 opts.builtins = false;
5975 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
5976 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
5977 }
5978
5979 #[test]
5988 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
5989 let text = ir(concat!(
5990 "double a = __builtin_fabs(-3.5);\n",
5991 "double b = __builtin_copysign(1.0, -0.0);\n",
5992 "double c = __builtin_copysign(0.0, -2.0);\n",
5993 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
5995 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
5996 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
5997 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
5998 "long double i = __builtin_fabsl(-__builtin_infl());\n",
5999 ));
6000 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6001 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6002 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6003 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6004 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6005 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6006 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6007 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6008 }
6009
6010 #[test]
6018 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6019 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6020 assert!(!text.contains("call"), "{text}");
6021 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6022 assert!(!text.contains("call"), "{text}");
6023
6024 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6027 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6028 assert!(!text.contains("call"), "{text}");
6029 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6030 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6031
6032 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6035 assert_eq!(written, text, "the name and the operator are the same thing");
6036
6037 let text = body(concat!(
6039 "double creal(_Complex double z);\n",
6040 "double f(_Complex double z) { return creal(z); }\n",
6041 ));
6042 assert!(!text.contains("call"), "{text}");
6043 let text = body(concat!(
6044 "_Complex float conjf(_Complex float z);\n",
6045 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6046 ));
6047 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6048 assert!(!text.contains("call"), "{text}");
6049
6050 let taken = concat!(
6053 "static double creal(_Complex double z) { return 7; }\n",
6054 "double f(_Complex double z) { return creal(z); }\n",
6055 );
6056 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6057 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6058 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6059 let plain = concat!(
6060 "double cimag(_Complex double z);\n",
6061 "double f(_Complex double z) { return cimag(z); }\n",
6062 );
6063 let mut opts = options();
6064 opts.emit = EmitKind::Ir;
6065 opts.builtins = false;
6066 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6067 opts.builtins = true;
6068 opts.no_builtin = vec!["cimag".to_owned()];
6069 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6070
6071 let text = ir(concat!(
6073 "double a = __builtin_creal(1.5 + 2.5i);\n",
6074 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6075 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6076 ));
6077 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6078 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6079 assert!(
6080 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6081 "the conjugate of a constant is the constant with the second half negated: {text}"
6082 );
6083 assert!(!text.contains("call"), "{text}");
6084 }
6085
6086 #[test]
6094 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6095 let text = ir(concat!(
6096 "double a = __builtin_ceil(1.5);\n",
6097 "double b = __builtin_floor(1.5);\n",
6098 "double c = __builtin_trunc(-1.5);\n",
6099 "double d = __builtin_round(2.5);\n",
6102 "double e = __builtin_ceil(-0.5);\n",
6104 "double f = __builtin_fmax(1.0, 2.0);\n",
6105 "double g = __builtin_fmin(1.0, 2.0);\n",
6106 "float h = __builtin_ceilf(1.25f);\n",
6107 "double ceil(double x);\n",
6110 "double i = ceil(2.25);\n",
6111 ));
6112 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6113 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6114 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6115 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6116 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6117 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6118 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6119 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6120 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6121 assert!(!text.contains("call"), "{text}");
6122 }
6123
6124 #[test]
6132 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6133 let text = ir(concat!(
6134 "double f(double x) { return __builtin_ceil(x); }\n",
6135 "float g(float x) { return __builtin_floorf(x); }\n",
6136 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6137 ));
6138 assert!(text.contains("call @ceil("), "{text}");
6139 assert!(text.contains("call @floorf("), "{text}");
6140 assert!(text.contains("call @fmax("), "{text}");
6141
6142 let text = ir(concat!(
6146 "double f(void) { return __builtin_rint(2.5); }\n",
6147 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6148 ));
6149 assert!(text.contains("call @rint("), "{text}");
6150 assert!(text.contains("call @nearbyint("), "{text}");
6151
6152 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6155 assert!(text.contains("call @fmin("), "{text}");
6156
6157 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6160 let mut opts = options();
6161 opts.emit = EmitKind::Ir;
6162 opts.no_builtin = vec!["ceil".to_owned()];
6163 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6164 }
6165
6166 #[test]
6173 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6174 let text = ir(concat!(
6175 "constexpr int side = 4;\n",
6176 "constexpr int wider = side + 1;\n",
6177 "constexpr double half = 1.5;\n",
6178 "struct point { int x; int y; };\n",
6179 "constexpr struct point origin = { 5, 6 };\n",
6180 "int square[side * side];\n",
6181 "int rectangle[wider];\n",
6182 "int rounded[(int)half * 2];\n",
6183 "int across[origin.y];\n",
6184 "enum named { four = side };\n",
6185 "int e = four;\n",
6186 ));
6187 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6188 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6189 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6190 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6191 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6192
6193 let mut opts = options();
6196 opts.emit = EmitKind::Ir;
6197 let konst = "const int n = 1;\nint a[n];\n";
6198 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6199 assert_eq!(run(&opts, konst).messages, [message]);
6200
6201 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6203 assert_eq!(run(&opts, subscript).messages, [message]);
6204
6205 let address = "constexpr int c = 3;\nint *p = &c;\n";
6207 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6208 pointer target type [E0514]";
6209 assert_eq!(run(&opts, address).messages, [warning]);
6210 }
6211
6212 #[test]
6227 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
6228 let mut opts = options();
6229 opts.emit = EmitKind::Ir;
6230 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
6231
6232 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
6234 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
6235
6236 let plain = concat!(
6239 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
6240 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
6241 );
6242 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
6243
6244 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
6247 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
6248 [E0514]";
6249 assert_eq!(run(&opts, &dropping).messages, [warning]);
6250
6251 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
6254 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
6255 incompatible return type 'const unsigned int (*)[4]' [E0512]";
6256 assert_eq!(run(&opts, wrong).messages, [error]);
6257 }
6258
6259 #[test]
6268 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
6269 let mut opts = options();
6272 opts.std = Std::C17;
6273 let source = concat!(
6274 "int add(a, b)\n",
6275 "int a;\n",
6276 "int b;\n",
6277 "{ return a + b; }\n",
6278 "int promoted(c)\n",
6279 "char c;\n",
6280 "{ return c; }\n",
6281 "int narrow(char);\n",
6282 "int narrow(c)\n",
6283 "char c;\n",
6284 "{ return c; }\n",
6285 "int first(a)\n",
6286 "int a[4];\n",
6287 "{ return a[0]; }\n",
6288 );
6289 let result = run(&opts, source);
6290 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6291 let text = result.text();
6292 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6293 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6294 assert!(text.contains("c : char object automatic defined"), "{text}");
6296 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
6297 assert!(text.contains("first : int(int *) function external defined"), "{text}");
6299 }
6300
6301 #[test]
6308 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
6309 let mut opts = options();
6310 opts.std = Std::C17;
6311 for (source, message) in [
6312 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
6313 (
6314 "int f(a)\nint a;\nint b;\n{ return a; }\n",
6315 "3:5: error: declaration for parameter 'b' but no such parameter",
6316 ),
6317 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
6318 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
6319 (
6320 "int f(a)\nstatic int a;\n{ return a; }\n",
6321 "2:12: error: storage class specified for parameter 'a'",
6322 ),
6323 (
6324 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
6325 "2:7: error: argument 'a' doesn't match prototype",
6326 ),
6327 ] {
6328 let result = run(&opts, source);
6329 assert!(result.failed(), "expected this to fail:\n{source}");
6330 assert!(result.messages[0].contains(message), "{:?}", result.messages);
6331 }
6332
6333 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
6336 let mut older = options();
6337 older.std = Std::C89;
6338 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
6339 let result = run(&opts, implicit);
6340 assert!(
6341 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
6342 "{:?}",
6343 result.messages
6344 );
6345
6346 let mut newer = options();
6350 newer.std = Std::C23;
6351 let plain = "int f(a)\nint a;\n{ return a; }\n";
6352 let result = run(&newer, plain);
6353 assert!(!result.failed(), "{:?}", result.messages);
6354 assert_eq!(
6355 result.messages,
6356 ["/main.c:1:5: warning: old-style function definition [E0412]"]
6357 );
6358 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
6359 }
6360
6361 #[test]
6368 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
6369 let array = "int a[8] = { [3] 7 };\n";
6370 let member = "struct s { int x; } v = { x: 7 };\n";
6371 for source in [array, member] {
6372 let result = run(&options(), source);
6373 assert!(!result.failed(), "{:?}", result.messages);
6374 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
6375 }
6376
6377 let mut asked = options();
6378 asked.pedantic = true;
6379 assert_eq!(
6380 run(&asked, array).messages,
6381 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
6382 );
6383 assert_eq!(
6384 run(&asked, member).messages,
6385 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
6386 );
6387 }
6388
6389 #[test]
6396 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
6397 let text = ir(concat!(
6398 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
6399 "struct brim { char buf[9223372036854775807L]; };\n",
6400 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
6401 "unsigned long h = sizeof(struct huge_struct);\n",
6402 "unsigned long b = sizeof(struct brim);\n",
6403 "unsigned long y = sizeof(struct bitty);\n",
6404 ));
6405 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6406 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6407 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6408
6409 let mut opts = options();
6410 opts.emit = EmitKind::Ir;
6411 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6412 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6413 assert_eq!(run(&opts, over).messages, [message]);
6414 let array = "struct wide { short buf[1L << 62]; };\n";
6415 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6416 maximum object size '9223372036854775807' [E0537]";
6417 assert_eq!(run(&opts, array).messages[0], message);
6418 }
6419
6420 fn compile_bytes(source: &[u8]) -> Compiled {
6425 let mut opts = options();
6426 opts.emit = EmitKind::Ir;
6427 let mut fs = MemoryFileSystem::new();
6428 fs.insert("/main.c", source.to_vec());
6429 compile(&opts, "/main.c", &fs)
6430 }
6431
6432 #[test]
6439 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6440 let mut source = b"char s[] = \"a".to_vec();
6441 source.push(0xff);
6442 source.extend_from_slice(b"b\";\nchar c = '");
6443 source.push(0xff);
6444 source.extend_from_slice(b"';\n");
6445 let result = compile_bytes(&source);
6446 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6447 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6448 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6450
6451 let mut stray = b"int a".to_vec();
6452 stray.push(0xff);
6453 stray.extend_from_slice(b" = 1;\n");
6454 let result = compile_bytes(&stray);
6455 assert!(
6456 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6457 "{:?}",
6458 result.messages
6459 );
6460 }
6461
6462 #[test]
6463 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6464 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6465 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6466 let expected = "\
6467func @add(i32, i32) -> i32, linkage(external) {
6468block0(%0: i32, %1: i32):
6469 %2 = add.nsw %0, %1
6470 return %2
6471}
6472";
6473 assert!(text.contains(expected), "{text}");
6474 }
6475
6476 #[test]
6477 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6478 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6479 assert!(!text.contains("alloca"), "{text}");
6480 assert!(!text.contains("load"), "{text}");
6481 assert!(!text.contains("store"), "{text}");
6482 }
6483
6484 #[test]
6485 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6486 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6487 let expected = "\
6488block0:
6489 %0 = alloca, size 4, align 4
6490 %1 = iconst.i32 1
6491 store %1 -> %0, align 4, tbaa !1
6492 %2 = call @g(%0) : (ptr) -> i32
6493 return %2
6494";
6495 assert_eq!(text, expected);
6496 }
6497
6498 #[test]
6499 fn a_loop_carries_what_it_changes_as_block_parameters() {
6500 let text = body(
6503 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6504 return total;\n}\n",
6505 );
6506 assert!(!text.contains("alloca"), "{text}");
6507 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6508 assert!(text.contains("jump block1("), "{text}");
6509 }
6510
6511 #[test]
6512 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6513 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6514 assert!(text.contains("icmp slt %0, %1"), "{text}");
6515 assert!(!text.contains("zext"), "{text}");
6516 }
6517
6518 #[test]
6519 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6520 let text = body("int f(int a, int b) { return a && b; }\n");
6521 let expected = "\
6522block0(%0: i32, %1: i32):
6523 %2 = iconst.i32 0
6524 %3 = icmp ne %0, %2
6525 %4 = iconst.i1 0
6526 br_if %3, block1, block2(%4)
6527
6528block1:
6529 %5 = iconst.i32 0
6530 %6 = icmp ne %1, %5
6531 jump block2(%6)
6532
6533block2(%7: i1):
6534 %8 = zext.i32 %7
6535 return %8
6536";
6537 assert_eq!(text, expected);
6538 }
6539
6540 #[test]
6541 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6542 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6543 assert!(!text.contains("block3"), "{text}");
6546 assert!(!text.contains("iconst.i32 3"), "{text}");
6547 }
6548
6549 #[test]
6550 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6551 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6552 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6553 assert!(body("int f(void) { }\n").contains("unreachable"));
6554 }
6555
6556 #[test]
6557 fn a_structure_is_copied_rather_than_held_in_a_value() {
6558 let text = body(
6559 "struct point { int x, y; };\n\
6560 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6561 );
6562 assert!(text.contains("memcpy"), "{text}");
6563 }
6564
6565 #[test]
6566 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6567 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6568 assert!(text.contains("memset"), "{text}");
6569 }
6570
6571 #[test]
6572 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6573 let text = body(
6574 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6575 default: r = 4; } return r; }\n",
6576 );
6577 let expected = "\
6578block0(%0: i32):
6579 %1 = iconst.i32 0
6580 switch %0, block1, [1 => block2, 2 => block3(%1)]
6581
6582block1:
6583 %2 = iconst.i32 4
6584 jump block4(%2)
6585
6586block2:
6587 %3 = iconst.i32 1
6588 jump block3(%3)
6589
6590block3(%4: i32):
6591 %5 = iconst.i32 2
6592 %6 = add.nsw %4, %5
6593 jump block4(%6)
6594
6595block4(%7: i32):
6596 return %7
6597";
6598 assert_eq!(text, expected);
6599 }
6600
6601 #[test]
6602 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6603 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6606 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6607 assert!(text.contains("icmp ule"), "{text}");
6608 assert!(!text.contains("switch"), "{text}");
6609 }
6610
6611 #[test]
6612 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6613 let text = body(
6614 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6615 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6616 );
6617 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6620 assert!(text.contains("block5:\n jump block7("), "{text}");
6621 assert!(text.contains("block6:\n jump block8("), "{text}");
6622 }
6623
6624 #[test]
6625 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6626 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6627 }
6628
6629 #[test]
6630 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6631 let text = body(
6636 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6637 return n; }\n",
6638 );
6639 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6642 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6643 assert!(text.contains("block4:\n jump block3("), "{text}");
6644 }
6645
6646 #[test]
6647 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6648 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6651 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6652 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6653 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6654 }
6655
6656 #[test]
6657 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6658 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6659 assert!(!text.contains("alloca"), "{text}");
6663 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6664 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6665 }
6666
6667 #[test]
6668 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6669 let text =
6670 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6671 assert!(!text.contains("alloca"), "{text}");
6672 assert!(text.contains("block1(%2: i32):"), "{text}");
6673 assert!(text.contains("jump block1(%5)"), "{text}");
6674 }
6675
6676 #[test]
6677 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6678 assert_eq!(
6681 body("int f(int x) { return x; spare: return 0; }\n"),
6682 "block0(%0: i32):\n return %0\n"
6683 );
6684 }
6685
6686 #[test]
6687 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6688 let text = body(
6689 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6690 );
6691 assert_eq!(
6694 text,
6695 "\
6696block0(%0: ptr):
6697 %1 = load.i8 %0, align 1
6698 %2 = iconst.i8 3
6699 %3 = ashr %1, %2
6700 %4 = sext.i32 %3
6701 return %4
6702"
6703 );
6704 }
6705
6706 #[test]
6707 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6708 let text =
6712 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6713 assert_eq!(
6714 text,
6715 "\
6716block0(%0: ptr, %1: i32):
6717 %2 = iconst.i32 16777215
6718 %3 = and %1, %2
6719 %4 = trunc.i16 %3
6720 store %4 -> %0, align 2
6721 %5 = iconst.i32 16
6722 %6 = lshr %3, %5
6723 %7 = trunc.i8 %6
6724 %8 = iconst.i64 2
6725 %9 = ptr_add %0, %8
6726 store %7 -> %9, align 1
6727 return
6728"
6729 );
6730 }
6731
6732 #[test]
6733 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6734 let text =
6735 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6736 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6739 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6740 }
6741
6742 #[test]
6743 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6744 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6747 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6748 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6749 }
6750
6751 #[test]
6752 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6753 let text = body(
6757 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6758 );
6759 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6760 }
6761
6762 #[test]
6763 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6764 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6767 assert!(
6768 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6769 "{text}"
6770 );
6771 }
6772
6773 #[test]
6774 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6775 let text = ir(concat!(
6780 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6781 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6782 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6783 "char s[2] = \"hi\";\n",
6784 ));
6785 assert!(
6786 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6787 "{text}"
6788 );
6789 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6790 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6791 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6794 }
6795
6796 #[test]
6797 fn a_definition_takes_a_parameter_it_left_unnamed() {
6798 let text = ir("int f(int a, int) { return a; }\n");
6802 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6803 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6804
6805 let text = ir("int g(int, int n) { return n; }\n");
6808 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6809 }
6810
6811 #[test]
6812 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6813 let text = body(concat!(
6818 "struct s { int f; int g; };\n",
6819 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6820 "{ *d = *e = a[0] = *c; }\n",
6821 ));
6822 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6823 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6824 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6825 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6826 }
6827
6828 #[test]
6829 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6830 let mut opts = options();
6835 opts.emit = EmitKind::Ir;
6836 let result = run(
6837 &opts,
6838 concat!(
6839 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6840 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6841 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6842 "const union u c = { { \"1234\", \"567\" } };\n",
6843 ),
6844 );
6845 let text = result.text();
6846 assert_eq!(
6847 result.messages,
6848 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6849 (5 chars into 3 available) [E0637]"]
6850 );
6851 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6852 assert!(
6853 text.contains(
6854 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6855 bytes \"9\\00\", zero 3 }"
6856 ),
6857 "{text}"
6858 );
6859 assert!(
6862 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6863 "{text}"
6864 );
6865 }
6866
6867 #[test]
6868 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6869 let text = body(concat!(
6873 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6874 "void g(struct v *);\n",
6875 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6876 ));
6877 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6878 }
6879
6880 #[test]
6881 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6882 let text = ir(concat!(
6887 "struct s { int x; };\n",
6888 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6889 "int n = (int){ 7 };\n",
6890 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6891 ));
6892 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6893 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6894 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6897 }
6898
6899 #[test]
6900 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6901 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6905 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6906 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6907 }
6908
6909 #[test]
6910 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6911 let text = ir("unsigned char foo[1][0];\n");
6915 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6916 }
6917
6918 #[test]
6919 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
6920 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
6923 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
6924 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
6925 }
6926
6927 #[test]
6928 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
6929 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
6933 assert!(
6934 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
6935 "{text}"
6936 );
6937 }
6938
6939 #[test]
6940 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
6941 let text = body(
6946 "\
6947struct s { int a, b; };
6948struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
6949",
6950 );
6951 assert!(text.contains("block3(%7: ptr)"), "{text}");
6953 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
6954 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
6955 }
6956
6957 #[test]
6965 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
6966 let text = body("int f(int i) { return ++i ?: 10; }\n");
6967 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
6968 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6969
6970 let text = body("long f(int i) { return ++i ?: 10L; }\n");
6973 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
6974 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6975
6976 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
6978 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
6979
6980 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
6983 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
6984 }
6985
6986 #[test]
6987 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
6988 let text = ir("\
6992struct pair { int a, b; };
6993struct pair make(int a, int b);
6994struct pair twice(struct pair p) { return make(p.a, p.b); }
6995");
6996 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
6997 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
6998 }
6999
7000 #[test]
7001 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7002 let text = ir("\
7006struct big { double v[8]; };
7007struct big grow(struct big b);
7008struct big twice(struct big b) { return grow(grow(b)); }
7009");
7010 assert!(
7011 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7012 "{text}"
7013 );
7014 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7015 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7018 }
7019
7020 #[test]
7021 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7022 let text = ir("\
7027struct big { double v[8]; };
7028struct pair { int a, b; };
7029int p(const char *, ...);
7030int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7031");
7032 assert!(
7033 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7034 "{text}"
7035 );
7036 }
7037
7038 #[test]
7039 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7040 let body = body(
7043 "\
7044struct pair { int a, b; };
7045struct pair make(int a, int b);
7046int second(void) { return make(1, 2).b; }
7047",
7048 );
7049 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7050 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7051 }
7052
7053 #[test]
7054 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7055 let source = "\
7059struct hfa { float x, y, z; };
7060int take(struct hfa h);
7061int give(struct hfa h) { return take(h); }
7062";
7063 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7064 let mut opts = options();
7065 opts.emit = EmitKind::Ir;
7066 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7067 let result = run(&opts, source);
7068 assert_eq!(result.messages, Vec::<String>::new());
7069 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7070 }
7071
7072 #[test]
7073 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7074 let source = "\
7077int use(int *);
7078void f(int n) {
7079 {
7080 int a[n];
7081 use(a);
7082 }
7083 use(0);
7084}
7085";
7086 let body = body(source);
7087 assert!(body.contains("mul.nsw"), "{body}");
7088 assert!(body.contains("stacksave"), "{body}");
7089 assert!(body.contains("alloca %"), "{body}");
7090 assert!(body.contains("stackrestore"), "{body}");
7091 }
7092
7093 #[test]
7094 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7095 let source = "\
7100int use(int *);
7101int f(int n) {
7102 {
7103 int a[n];
7104 if (use(a)) goto out;
7105 use(0);
7106 }
7107out:
7108 return 0;
7109}
7110";
7111 let body = body(source);
7112 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7114 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7115 assert!(after.starts_with(" %4\n jump block"), "{body}");
7116 }
7117
7118 #[test]
7119 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7120 let source = "\
7124int use(int *);
7125int f(int n) {
7126 int a[n];
7127again:
7128 if (use(a)) goto again;
7129 return 0;
7130}
7131";
7132 let body = body(source);
7133 assert!(body.contains("stacksave"), "{body}");
7134 assert!(!body.contains("stackrestore"), "{body}");
7135 }
7136
7137 #[test]
7138 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7139 let source = "\
7144int use(int *);
7145int f(int n) {
7146again:
7147 {
7148 int a[n];
7149 if (use(a)) goto again;
7150 }
7151 return 0;
7152}
7153";
7154 let body = body(source);
7155 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7156 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7157 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7158 }
7159
7160 #[test]
7161 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
7162 let source = "\
7168int f(void);
7169void t(void) {
7170 int count = 10;
7171 for (; count--;) {
7172 int b[f()];
7173 int i;
7174 for (i = 0; i < f(); i++) {
7175 b[i] = count;
7176 }
7177 }
7178}
7179";
7180 let body = body(source);
7181 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7185 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7186 let next = after.split("\n\n").next().expect("the block the restore is in");
7189 assert!(next.contains("jump block1("), "{body}");
7190 }
7191
7192 #[test]
7193 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
7194 let source = "\
7197unsigned long f(int n) {
7198 int a[n];
7199 n = 0;
7200 return sizeof a;
7201}
7202";
7203 let body = body(source);
7204 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
7206 }
7207
7208 #[test]
7209 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
7210 let source = "\
7213int use(int);
7214int f(int x) {
7215 return ({
7216 int t = use(x);
7217 t * t;
7218 });
7219}
7220";
7221 let expected = "\
7222block0(%0: i32):
7223 %1 = call @use(%0) : (i32) -> i32
7224 %2 = mul.nsw %1, %1
7225 return %2
7226";
7227 assert_eq!(body(source), expected);
7228 }
7229
7230 #[test]
7231 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
7232 let source = "int f(int x) { return ({ return x; 0; }); }\n";
7236 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
7237 }
7238
7239 #[test]
7240 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
7241 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
7245 let expected = "\
7246block0(%0: ptr):
7247 %1 = va_arg.f64 %0
7248 %2 = va_arg.f64 %0
7249 %3 = fadd %1, %2
7250 return %3
7251";
7252 assert_eq!(body(source), expected);
7253 }
7254
7255 #[test]
7256 fn one_that_reads_a_structure_answers_where_the_object_is() {
7257 let source = "\
7271struct s { int a; long b; };
7272long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
7273";
7274 let expected = "\
7275block0(%0: ptr):
7276 %1 = alloca, size 16, align 16
7277 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
7278 memcpy %1, %2, size 16, align 8
7279 %3 = iconst.i64 8
7280 %4 = ptr_add %1, %3
7281 %5 = load.i64 %4, align 8, tbaa !1
7282 return %5
7283";
7284 assert_eq!(body(source), expected);
7285 }
7286
7287 #[test]
7291 fn the_classification_says_which_registers_the_object_arrived_in() {
7292 let source = "\
7293struct s { double a; double b; };
7294double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
7295";
7296 assert!(
7297 body(source)
7298 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
7299 "{}",
7300 body(source)
7301 );
7302
7303 let big = "\
7304struct s { long a[4]; };
7305long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
7306";
7307 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
7308 }
7309
7310 #[test]
7311 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
7312 let source = "\
7316int f(int c) {
7317 void *p = c ? &&one : &&two;
7318 goto *p;
7319one:
7320 return 1;
7321two:
7322 return 2;
7323}
7324";
7325 let expected = "\
7326block0(%0: i32):
7327 %1 = iconst.i32 0
7328 %2 = icmp ne %0, %1
7329 br_if %2, block1, block2
7330
7331block1:
7332 %3 = block_addr block3
7333 jump block4(%3)
7334
7335block2:
7336 %4 = block_addr block5
7337 jump block4(%4)
7338
7339block3:
7340 %5 = iconst.i32 1
7341 return %5
7342
7343block4(%6: ptr):
7344 indirect_br %6, block3, block5
7345
7346block5:
7347 %7 = iconst.i32 2
7348 return %7
7349";
7350 assert_eq!(body(source), expected);
7351 }
7352
7353 #[test]
7354 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
7355 let source = "void **next(void);
7358void f(void) { goto *next(); }
7359";
7360 let expected = "\
7361block0:
7362 %0 = call @next() : () -> ptr
7363 unreachable
7364";
7365 assert_eq!(body(source), expected);
7366 }
7367
7368 #[test]
7369 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
7370 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
7373 let expected = "\
7374block0:
7375 inline_asm.volatile \"mfence\", \"\", \"memory\"()
7376 return
7377";
7378 assert_eq!(body(source), expected);
7379 }
7380
7381 #[test]
7382 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
7383 let source = "\
7386int f(int x, int y) {
7387 int r;
7388 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
7389 return r + y;
7390}
7391";
7392 let expected = "\
7393block0(%0: i32, %1: i32):
7394 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
7395 %4 = add.nsw %2, %3
7396 return %4
7397";
7398 assert_eq!(body(source), expected);
7399 }
7400
7401 #[test]
7402 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
7403 let source = "\
7408struct pair { int a, b; };
7409int f(int x) {
7410 int slot = x;
7411 struct pair p = { x, x };
7412 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7413 return slot + p.a;
7414}
7415";
7416 let text = body(source);
7417 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7418 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7419 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7420 }
7421
7422 #[test]
7423 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7424 let source = "\
7429int f(int x) {
7430 int r = 7;
7431 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7432 return r;
7433away:
7434 return r;
7435}
7436";
7437 let expected = "\
7438block0(%0: i32):
7439 %1 = iconst.i32 7
7440 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7441
7442block1:
7443 return %2
7444
7445block2:
7446 return %1
7447";
7448 assert_eq!(body(source), expected);
7449 }
7450
7451 #[test]
7452 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7453 let mut opts = options();
7457 opts.emit = EmitKind::Ir;
7458 for (source, expected) in [
7459 (
7460 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7461 "output operand constraint lacks '='",
7462 ),
7463 (
7464 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7465 "lvalue required in 'asm' statement",
7466 ),
7467 (
7468 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7469 "read-only variable 'g' used as 'asm' output",
7470 ),
7471 (
7472 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7473 "input operand constraint contains '='",
7474 ),
7475 (
7476 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7477 "memory input 0 is not directly addressable",
7478 ),
7479 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7480 (
7481 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7482 "duplicate asm operand name 'a'",
7483 ),
7484 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7485 ] {
7486 let result = run(&opts, source);
7487 assert!(result.failed(), "expected this to be reported:\n{source}");
7488 assert!(
7489 result.messages.iter().any(|m| m.contains(expected)),
7490 "{expected}\n{:?}",
7491 result.messages
7492 );
7493 }
7494 }
7495
7496 #[test]
7501 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7502 let text = ir(concat!(
7503 "__asm__(\n",
7504 " \".section .rodata\\n\"\n",
7505 " \".globl first\\n\"\n",
7506 " \".balign 8\\n\"\n",
7507 " \"first:\\n\"\n",
7508 " \".long 1\\n\"\n",
7509 " \".long 2\\n\"\n",
7510 " \".globl last\\n\"\n",
7511 " \"last:\\n\"\n",
7512 " \".quad last - first\\n\");\n",
7513 "extern const int first[];\n",
7514 "extern const long last;\n",
7515 ));
7516 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7517 assert!(text.contains("global @last : i64 = 8"), "{text}");
7518 }
7519
7520 #[test]
7524 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7525 let text = ir(concat!(
7526 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7527 "extern int counter;\n",
7528 "int read(void) { return counter; }\n",
7529 ));
7530 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7531 }
7532
7533 #[test]
7536 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7537 let mut opts = options();
7538 opts.emit = EmitKind::Ir;
7539 let mut fs = MemoryFileSystem::new();
7540 fs.insert(
7541 "/main.c",
7542 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7543 );
7544 fs.insert("seed", b"hi".to_vec());
7545 let result = compile(&opts, "/main.c", &fs);
7546 assert_eq!(result.messages, Vec::<String>::new());
7547 let text = result.text();
7548 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7549 }
7550
7551 #[test]
7554 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7555 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7556 assert!(
7557 messages
7558 .iter()
7559 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7560 "{messages:?}"
7561 );
7562 }
7563
7564 #[test]
7567 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7568 for source in [
7569 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7570 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7571 ] {
7572 let messages = errors(source);
7573 assert!(
7574 messages
7575 .iter()
7576 .any(|m| m.contains("not supported yet")
7577 && m.contains("in an `asm` at file scope")),
7578 "{source}\n{messages:?}"
7579 );
7580 }
7581 }
7582
7583 #[test]
7584 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7585 let mut opts = options();
7586 opts.emit = EmitKind::Ir;
7587 for source in [
7588 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7589 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7590 ] {
7591 let result = run(&opts, source);
7592 assert!(result.failed(), "expected this to be reported:\n{source}");
7593 assert!(
7594 result.messages.iter().any(|m| m.contains("not supported yet")),
7595 "{:?}",
7596 result.messages
7597 );
7598 }
7599 }
7600
7601 fn round_trip(source: &str) -> (String, String) {
7603 let printed = ir(source);
7604 let mut opts = options();
7605 opts.emit = EmitKind::Ir;
7606 let mut fs = MemoryFileSystem::new();
7607 fs.insert("/main.ir", printed.clone().into_bytes());
7608 let result = compile_ir(&opts, "/main.ir", &fs);
7609 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7610 (printed, result.text().to_owned())
7611 }
7612
7613 #[test]
7614 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7615 let (printed, again) = round_trip(
7619 "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",
7620 );
7621 assert_eq!(printed, again);
7622 }
7623
7624 #[test]
7625 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7626 let mut opts = options();
7627 opts.emit = EmitKind::Ir;
7628 let mut fs = MemoryFileSystem::new();
7629 let text = "\
7630; ModuleID = 'a.c'
7631; format 0
7632target triple = \"x86_64-unknown-linux-gnu\"
7633target datalayout = \"e-p:64:64-i64:64-S128\"
7634
7635func @f(), linkage(external) {
7636block0:
7637 frobnicate
7638}
7639";
7640 fs.insert("/main.ir", text.as_bytes().to_vec());
7641 let result = compile_ir(&opts, "/main.ir", &fs);
7642 assert!(result.failed());
7643 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7644 }
7645
7646 #[test]
7647 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7648 let mut opts = options();
7651 opts.emit = EmitKind::Ir;
7652 let mut fs = MemoryFileSystem::new();
7653 let text = "\
7654; ModuleID = 'a.c'
7655; format 0
7656target triple = \"x86_64-unknown-linux-gnu\"
7657target datalayout = \"e-p:64:64-i64:64-S128\"
7658
7659func @f(), linkage(external) {
7660block0:
7661 %0 = iconst.i32 1
7662 return %0
7663}
7664";
7665 fs.insert("/main.ir", text.as_bytes().to_vec());
7666 let result = compile_ir(&opts, "/main.ir", &fs);
7667 assert!(result.failed());
7668 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7669 }
7670
7671 #[test]
7672 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7673 let mut fs = MemoryFileSystem::new();
7675 fs.insert("/main.ir", Vec::new());
7676 let result = compile_ir(&options(), "/main.ir", &fs);
7677 assert!(result.failed());
7678 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7679 }
7680
7681 #[test]
7682 fn the_printed_ir_reads_back_as_the_same_module() {
7683 let text = ir("\
7686struct point { int x, y; };
7687static const char greeting[] = \"hi\";
7688int table[4] = { 1, 2, 3 };
7689int puts(const char *);
7690double half(double x) { return x / 2.0; }
7691int f(int n) {
7692 int total = 0;
7693 for (int i = 0; i < n; i++) {
7694 if (i == 3) continue;
7695 total += table[i];
7696 }
7697 switch (n) {
7698 case 0: total = 1;
7699 case 1: total++; break;
7700 default: total = -total;
7701 }
7702 struct point p = { total, 1 };
7703 int *q = &p.y;
7704 puts(greeting);
7705 return p.x + *q;
7706}
7707int dispatch(int c) {
7708 void *p = c ? &&one : &&two;
7709 goto *p;
7710one:
7711 return 1;
7712two:
7713 return 2;
7714}
7715int assembly(int x, int *p) {
7716 int r;
7717 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7718 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7719 return r;
7720away:
7721 return 0;
7722}
7723");
7724 let mut names = Interner::new();
7725 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7726 assert_eq!(rucc_ir::print(&module, &names), text);
7727 }
7728
7729 #[test]
7730 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7731 let mut opts = options();
7735 opts.emit = EmitKind::Object;
7736 opts.save_temps = rucc_session::SaveTemps::Object;
7737 let result = run(&opts, "#define N 2\nint a[N];\n");
7738 assert_eq!(result.messages, Vec::<String>::new());
7739 let text = result.temps.preprocessed.expect("the preprocessed text");
7740 assert!(text.contains("int a[2];"), "{text}");
7741 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7742 let asm = result.temps.assembly.expect("the assembly");
7743 assert!(asm.contains("a:"), "{asm}");
7744 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7745 }
7746
7747 #[test]
7748 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7749 let mut opts = options();
7752 opts.emit = EmitKind::Object;
7753 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7754 }
7755
7756 #[test]
7757 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7758 let mut opts = options();
7761 opts.emit = EmitKind::Ir;
7762 opts.save_temps = rucc_session::SaveTemps::Cwd;
7763 let result = run(&opts, "int a;\n");
7764 assert!(result.temps.preprocessed.is_some());
7765 assert_eq!(result.temps.assembly, None);
7766 }
7767}