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 the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
3875 let text = summary(
3879 rucc_session::Safety::Detect,
3880 "void *notes_open(void);\n\
3881 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
3882 );
3883 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
3884 assert!(text.contains("\"notes_open\""), "{text}");
3885 }
3886
3887 #[test]
3888 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
3889 let text = summary(
3892 rucc_session::Safety::Detect,
3893 "static int len(const char *p) { return p ? 1 : 0; }\n\
3894 int f(void) { return len(\"x\"); }\n",
3895 );
3896 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
3897 }
3898
3899 fn granules(source: &str) -> String {
3901 let mut opts = options();
3902 opts.emit = EmitKind::TypeGranules;
3903 let result = run(&opts, source);
3904 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3905 result.text().to_owned()
3906 }
3907
3908 #[test]
3909 fn the_granule_report_names_every_record_and_both_keyings() {
3910 let text = granules(
3911 "struct hot { char *p; int a; int b; };\n\
3912 int f(struct hot *h) { return h->a; }\n",
3913 );
3914 assert!(text.contains("struct hot"), "{text}");
3915 assert!(text.contains("every type distinct"), "{text}");
3918 assert!(text.contains("every pointer one type"), "{text}");
3919 assert!(text.contains("budget"), "{text}");
3920 }
3921
3922 #[test]
3923 fn a_record_nothing_uses_is_still_measured() {
3924 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
3927 assert!(text.contains("struct unused"), "{text}");
3928 }
3929
3930 #[test]
3931 fn the_granule_report_stops_before_anything_is_lowered() {
3932 let text = granules(
3936 "struct wide { long double d; };\n\
3937 long double f(long double x) { return x * x; }\n",
3938 );
3939 assert!(text.contains("struct wide"), "{text}");
3940 }
3941
3942 #[test]
3943 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
3944 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
3947 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
3948 }
3949
3950 #[test]
3951 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
3952 let text = summary(
3953 rucc_session::Safety::Detect,
3954 "unsigned long f(int *p) { return (unsigned long) p; }\n",
3955 );
3956 assert!(text.contains("\"exposed\": 1"), "{text}");
3957 }
3958
3959 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
3961 let mut opts = options();
3962 opts.emit = EmitKind::Asm;
3963 opts.safety = tier;
3964 let result = run(&opts, source);
3965 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3966 result.text().to_owned()
3967 }
3968
3969 #[test]
3970 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
3971 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3972 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
3973 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
3974 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
3975 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
3976 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
3977 }
3978
3979 #[test]
3980 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
3981 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3985 let section = format!("\t.section\t{},", rucc_safety::SECTION);
3986 assert_eq!(text.matches(§ion).count(), 5, "{text}");
3987 for index in 0..5 {
3988 let name = format!("__rucc_safety_desc_{index}");
3989 assert!(text.contains(&format!("{name}:\n")), "{text}");
3992 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
3993 }
3994 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
3995 }
3996
3997 #[test]
4005 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4006 let text = ir(concat!(
4007 "int g;\n",
4008 "int a = __builtin_constant_p(1);\n",
4009 "int b = __builtin_constant_p(g);\n",
4010 "int c = __builtin_constant_p(\"abc\");\n",
4011 "int d = __builtin_constant_p(&g);\n",
4012 "int e = __builtin_constant_p(1.5);\n",
4013 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4014 ));
4015 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4016 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4017 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4018 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4019 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4020 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4021 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4022
4023 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4027 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4028 }
4029
4030 #[test]
4039 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4040 let text = body("void f(void) { __builtin_abort(); }\n");
4041 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4042
4043 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4046 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4047 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4048 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4049 }
4050
4051 #[test]
4064 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4065 let text = ir(concat!(
4066 "char d[8];\n",
4067 "void f(const char *s, unsigned long n) {\n",
4068 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4069 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4070 " __builtin___memset_chk(d, 0, n, 8);\n",
4071 "}\n",
4072 ));
4073 assert!(text.contains("call @__memcpy_chk("), "{text}");
4074 assert!(text.contains("call @__strcpy_chk("), "{text}");
4075 assert!(text.contains("call @__memset_chk("), "{text}");
4076 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4077 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4078 }
4079
4080 #[test]
4088 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4089 let text = ir(concat!(
4090 "extern char *p;\n",
4091 "char d[8];\n",
4092 "void f(const char *s, unsigned long n) {\n",
4093 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4094 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4095 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4096 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4097 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4098 "}\n",
4099 ));
4100
4101 assert!(
4103 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4104 "{text}"
4105 );
4106
4107 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4110 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4111 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4112
4113 assert!(text.contains("call @__sprintf_chk("), "{text}");
4116
4117 let asm = asm(concat!(
4120 "void f(char *p, const char *s, unsigned long n) {\n",
4121 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4122 "}\n",
4123 ));
4124 assert!(asm.contains("call\tmemcpy"), "{asm}");
4125 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4126 }
4127
4128 #[test]
4136 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4137 let text = ir(concat!(
4138 "char d[64];\n",
4139 "int f(const char *fmt, ...) {\n",
4140 " __builtin_va_list ap;\n",
4141 " __builtin_va_start(ap, fmt);\n",
4142 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4143 " __builtin_va_end(ap);\n",
4144 " return n;\n",
4145 "}\n",
4146 ));
4147 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4148 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4149 }
4150
4151 #[test]
4162 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4163 let text = body(concat!(
4164 "long long llabs(long long);\n",
4165 "long long f(long long x) { return llabs(x); }\n",
4166 ));
4167 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4168 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4169 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4170 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4171 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4172
4173 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4176 assert!(text.contains("iconst.i32 31"), "{text}");
4177 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4178 assert!(text.contains("iconst.i64 63"), "{text}");
4179
4180 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4183 assert!(!text.contains("call"), "{text}");
4184
4185 let text = ir(concat!(
4187 "long long llabs(long long b);\n",
4188 "long long g(long long x) { return llabs(x); }\n",
4189 "long long llabs(long long b) { return 7; }\n",
4190 ));
4191 assert!(!text.contains("call @llabs"), "{text}");
4192 }
4193
4194 #[test]
4201 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4202 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4203 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4204
4205 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4208 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4209 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4210 }
4211
4212 #[test]
4218 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4219 for (name, ty, width) in [
4220 ("__builtin_bswap16", "unsigned short", "i16"),
4221 ("__builtin_bswap32", "unsigned", "i32"),
4222 ("__builtin_bswap64", "unsigned long long", "i64"),
4223 ] {
4224 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4225 let text = body(&source);
4226 assert_eq!(
4227 text,
4228 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4229 "{name}"
4230 );
4231 }
4232 }
4233
4234 #[test]
4241 fn the_bit_counts_are_instructions_and_not_calls() {
4242 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4243 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4244
4245 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4246 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4247
4248 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4249 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4250 }
4251
4252 #[test]
4261 fn the_bit_counts_ask_about_the_width_their_name_says() {
4262 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4263 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4264 assert!(text.contains("%1 = ctlz %0"), "{text}");
4265 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4266
4267 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4270 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4271 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4272
4273 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4274 assert!(text.contains("%1 = ctpop %0"), "{text}");
4275 assert!(!text.contains("call"), "{text}");
4276 }
4277
4278 #[test]
4283 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4284 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4285 assert!(text.contains("%1 = ctpop %0"), "{text}");
4286 assert!(text.contains("iconst.i32 1"), "{text}");
4287 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4288 }
4289
4290 #[test]
4296 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4297 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4298 assert!(text.contains("%1 = cttz %0"), "{text}");
4299 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4300 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4301 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4302 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4303 assert!(!text.contains("br_if"), "no branch: {text}");
4304 }
4305
4306 #[test]
4316 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4317 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4318 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4319 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4320 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4321 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4322 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4323 assert!(text.contains("%7 = ctlz %6"), "{text}");
4324 assert!(!text.contains("call"), "{text}");
4325 assert!(!text.contains("br_if"), "no branch: {text}");
4326 }
4327
4328 #[test]
4334 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4335 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4336 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4337 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4338 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4339
4340 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4341 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4342
4343 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4346 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4347 }
4348
4349 #[test]
4357 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4358 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4359 assert!(text.contains("iconst.i64 63"), "{text}");
4360 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4361 assert!(!text.contains("call"), "{text}");
4362
4363 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4364 assert!(text.contains("iconst.i64 63"), "{text}");
4365 assert!(!text.contains("call"), "{text}");
4366 }
4367
4368 #[test]
4376 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4377 let text =
4378 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4379 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4380 assert!(!text.contains("store"), "nothing is written: {text}");
4381 assert!(!text.contains("call"), "{text}");
4382
4383 let text =
4386 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4387 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4388 assert!(!text.contains("store"), "{text}");
4389
4390 let text = body(concat!(
4393 "int g(void);\n",
4394 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4395 ));
4396 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4397 }
4398
4399 #[test]
4409 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4410 let text =
4411 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4412 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4413 assert!(text.contains("store %3 -> %2"), "{text}");
4414 assert!(!text.contains("call"), "{text}");
4415
4416 let text =
4417 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4418 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4419
4420 let text =
4421 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4422 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4423
4424 let text = body(
4427 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4428 );
4429 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4430 }
4431
4432 #[test]
4440 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4441 let text = body(
4442 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4443 );
4444 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4445 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4446 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4447
4448 let text = body(
4451 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4452 );
4453 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4454 assert!(!text.contains("sext."), "{text}");
4455 assert!(!text.contains("zext.i64"), "{text}");
4457 }
4458
4459 #[test]
4467 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4468 let text =
4469 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4470 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4471 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4472 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4473 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4474 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4475 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4476 }
4477
4478 #[test]
4485 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4486 for name in ["add", "sub", "mul"] {
4487 let source = format!(
4488 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4489 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4490 );
4491 let mut opts = options();
4492 opts.emit = EmitKind::MirFinal;
4493 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4494 }
4495 }
4496
4497 #[test]
4500 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4501 let messages =
4502 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4503 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4504
4505 let messages =
4506 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4507 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4508 }
4509
4510 #[test]
4521 fn an_ordered_access_is_ordered_in_the_ir() {
4522 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4523 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4524
4525 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4526 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4527
4528 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4529 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4530
4531 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4532 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4533
4534 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4537 assert!(text.contains("trunc.i8 %1"), "{text}");
4538 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4539 }
4540
4541 #[test]
4550 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4551 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4552 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4553 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4554
4555 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4556 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4557 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4558
4559 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4560 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4561 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4562 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4563 }
4564
4565 #[test]
4575 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4576 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4577 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4578
4579 for weaker in ["1", "2", "3", "4"] {
4580 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4581 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4582 }
4583 }
4584
4585 #[test]
4595 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
4596 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
4597 let source = format!("void f(void) {{ {name}(); }}\n");
4598 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
4599 let text = body(&source);
4600 assert!(text.contains("fence seq_cst"), "{name}: {text}");
4601 }
4602
4603 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
4604 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4605 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
4606 }
4607
4608 #[test]
4614 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4615 let text =
4618 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4619 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4620 assert!(text.contains("return %3"), "the value it found: {text}");
4621
4622 let text =
4623 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4624 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4625 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4626
4627 let text = body(
4630 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4631 );
4632 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4633 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4634 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4635 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4636
4637 let text = body(
4640 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4641 );
4642 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4643 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4644 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4645 }
4646
4647 #[test]
4654 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4655 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4656 for (ty, suffix, reg) in widths {
4657 let source = format!(
4658 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4659 );
4660 let text = asm(&source);
4661 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4662 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4663 assert!(text.contains("sete\t"), "{ty}: {text}");
4664 }
4665 let source =
4666 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4667 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4668
4669 for order in ["0", "2", "3", "4", "5"] {
4673 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4674 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4675 let text = asm(&source);
4676 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4677 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4678 }
4679 }
4680
4681 #[test]
4693 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4694 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4695 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4696 assert!(text.contains("return %2"), "the value that was there: {text}");
4697
4698 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4699 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4700 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4701
4702 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4703 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4704 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4705
4706 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4708 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4709
4710 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4713 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4714
4715 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4716 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4717
4718 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4721 assert!(text.contains("release"), "{text}");
4722 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4723
4724 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4728 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4729 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4730
4731 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4734 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4735
4736 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4737 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4738 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4739
4740 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4743 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4744 assert!(text.contains("%3 = and %2, %1"), "{text}");
4745 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4746 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4747 }
4748
4749 #[test]
4760 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4761 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4762 for (ty, suffix, reg) in widths {
4763 for (name, call, insn) in [
4764 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4765 ("or", "__sync_fetch_and_or(p, v)", "or"),
4766 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4767 ] {
4768 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4769 let text = asm(&source);
4770 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4771 assert!(
4772 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4773 "{ty} {name}: {text}"
4774 );
4775 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4776 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4778 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4779 }
4780 }
4781 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4782 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4783
4784 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4788 assert!(text.contains("cmpxchgl\t"), "{text}");
4789 assert!(text.contains("andl\t"), "{text}");
4790 assert!(text.contains("notl\t"), "{text}");
4791 }
4792
4793 #[test]
4802 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4803 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4804 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4805 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4806
4807 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4808 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4809 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4810
4811 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
4814 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4815 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
4816 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
4817 }
4818
4819 #[test]
4830 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
4831 for pointer in ["char", "int", "void"] {
4832 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
4833 let text = body(&source);
4834 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
4835 assert!(
4836 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
4837 "{pointer}: {text}"
4838 );
4839 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
4840
4841 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
4842 let text = body(&source);
4843 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
4844 }
4845
4846 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
4849 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
4850 assert!(text.contains("setne\t"), "{text}");
4851 }
4852
4853 #[test]
4861 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
4862 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
4863 for (ty, suffix, reg) in widths {
4864 let source =
4865 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
4866 let text = asm(&source);
4867 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4868 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4869
4870 let source =
4871 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
4872 let text = asm(&source);
4873 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4874 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
4875 }
4876 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
4877 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
4878
4879 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
4882 let text = asm(source);
4883 assert!(text.contains("negl\t"), "{text}");
4884 assert!(text.contains("xaddl\t"), "{text}");
4885
4886 for order in ["0", "2", "3", "4", "5"] {
4889 let source =
4890 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
4891 let text = asm(&source);
4892 assert!(text.contains("xaddl\t"), "{order}: {text}");
4893 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4894 }
4895
4896 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4900 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
4901 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
4908 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
4909 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
4910 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4911 }
4912
4913 #[test]
4925 fn the_lock_free_questions_are_answered_as_constants() {
4926 for size in ["1", "2", "4", "8"] {
4927 let source =
4928 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
4929 let text = asm(&source);
4930 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
4931 assert!(!text.contains("call"), "and is not a call: {text}");
4932 }
4933 for size in ["3", "16", "sizeof(long double)"] {
4934 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
4935 let text = asm(&source);
4936 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
4937 assert!(!text.contains("call"), "and is not a call either: {text}");
4938 }
4939
4940 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
4944 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
4945 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
4946 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
4947 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
4948 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
4949 }
4950
4951 #[test]
4963 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
4964 let mut opts = options();
4965 opts.emit = EmitKind::Ir;
4966
4967 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
4968 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
4969 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
4970
4971 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
4972 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
4973 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
4974
4975 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
4976 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
4977 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
4978 }
4979
4980 #[test]
4992 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
4993 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
4994 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
4995 assert!(text.contains("shrq"), "with the value halved first: {text}");
4996 assert!(text.contains("addsd"), "and doubled after: {text}");
4997 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
4998
4999 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5000 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5001 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5002 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5003 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5004 }
5005
5006 #[test]
5017 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5018 let taken = concat!(
5019 "static long long llabs(long long b) { return 7; }\n",
5020 "long long f(long long x) { return llabs(x); }\n",
5021 );
5022 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5023
5024 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5025 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5026
5027 let plain = concat!(
5028 "long long llabs(long long b);\n",
5029 "long long f(long long x) { return llabs(x); }\n",
5030 );
5031 let mut opts = options();
5032 opts.emit = EmitKind::Ir;
5033 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5034
5035 opts.builtins = false;
5036 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5037
5038 opts.builtins = true;
5039 opts.no_builtin = vec!["llabs".to_owned()];
5040 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5041 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5042 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5043
5044 opts.no_builtin = Vec::new();
5047 opts.builtins = false;
5048 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5049 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5050 }
5051
5052 #[test]
5065 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5066 let text = ir(concat!(
5067 "long a = __builtin_expect(7, 1);\n",
5068 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5069 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5070 ));
5071 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5072 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5073 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5074 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5075
5076 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5079 assert!(text.contains("sext"), "{text}");
5080
5081 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5085 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5086 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5087 assert_eq!(body(source), one);
5088
5089 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5094 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5095 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5096 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5097 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5098 }
5099
5100 #[test]
5112 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5113 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5114 let text = ir(promised);
5115 assert!(text.contains(" unreachable_hint\n"), "{text}");
5116 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5117
5118 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5122 assert!(after.contains("return"), "{after}");
5123
5124 let text = asm(promised);
5127 let mine = text.split_once("\nf:\n").expect("a definition").1;
5128 let mine = mine.split_once("\t.size").expect("a definition").0;
5129 let plain = asm("int f(int x) { if (x) return 1; }\n");
5130 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5131 let plain = plain.split_once("\t.size").expect("a definition").0;
5132 assert_eq!(mine, plain);
5133 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5136 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5137 assert!(!mine.contains("ud2"), "{mine}");
5138 }
5139
5140 #[test]
5147 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5148 let mut opts = options();
5149 opts.emit = EmitKind::Ir;
5150 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5151 assert!(
5152 messages.iter().any(|m| m.contains("__builtin_abort")),
5153 "expected the written name in {messages:?}"
5154 );
5155 }
5156
5157 #[test]
5165 fn a_builtin_nothing_lowers_is_refused_by_name() {
5166 let mut opts = options();
5167 opts.emit = EmitKind::Ir;
5168 let builtin = "__atomic_signal_fence";
5169 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5170 let messages = run(&opts, &source).messages;
5171 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5172 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5173 }
5174
5175 #[test]
5184 fn what_is_refused_is_the_call_and_not_the_name() {
5185 let text = ir(concat!(
5186 "void __atomic_signal_fence(int order) { (void)order; }\n",
5187 "void f(void) { __atomic_signal_fence(5); }\n",
5188 ));
5189 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5190 }
5191
5192 #[test]
5201 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5202 let text = ir(concat!(
5203 "struct S { char a[8]; int n; char b[12]; };\n",
5204 "char g[32];\n",
5205 "struct S gs;\n",
5206 "unsigned long whole = __builtin_object_size(g, 0);\n",
5207 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5208 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5209 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5210 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5211 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5212 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5213 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5214 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5215 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5216 ));
5217 for (name, size) in [
5218 ("whole", 32),
5219 ("moved", 28),
5220 ("back", 4),
5221 ("outer", 24),
5222 ("inner", 8),
5223 ("scalar", 4),
5224 ("after", 16),
5225 ("into", 10),
5226 ("text", 6),
5227 ("dyn", 12),
5228 ] {
5229 let said = format!("global @{name} : i64 = {size},");
5230 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5231 }
5232 }
5233
5234 #[test]
5242 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5243 let text = body(concat!(
5244 "struct S { char a[8]; int n; char b[12]; };\n",
5245 "unsigned long f(void) {\n",
5246 " char loc[20];\n",
5247 " struct S ls;\n",
5248 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5249 "}\n",
5250 ));
5251 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5252 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5253 }
5254
5255 #[test]
5265 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5266 let text = ir(concat!(
5267 "struct T { int n; char f[]; };\n",
5268 "extern char *p;\n",
5269 "extern struct T *t;\n",
5270 "unsigned long largest = __builtin_object_size(p, 0);\n",
5271 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5272 "unsigned long least = __builtin_object_size(p, 2);\n",
5273 "unsigned long tight = __builtin_object_size(p, 3);\n",
5274 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5275 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5276 ));
5277 for name in ["largest", "nearest", "flex"] {
5278 let said = format!("global @{name} : i64 = -1,");
5282 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5283 }
5284 for name in ["least", "tight"] {
5285 let said = format!("global @{name} : i64 = 0,");
5286 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5287 }
5288 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5289 }
5290
5291 #[test]
5298 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5299 let text = body(concat!(
5300 "extern char *side(void);\n",
5301 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5302 ));
5303 assert!(!text.contains("call"), "nothing is called: {text}");
5304 }
5305
5306 #[test]
5311 fn a_kind_that_is_not_one_of_the_four_is_refused() {
5312 for source in [
5313 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
5314 + "{ return __builtin_object_size(p, k); }\n",
5315 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
5316 .to_owned(),
5317 "extern char *p;\nunsigned long f(void) ".to_owned()
5318 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
5319 ] {
5320 let messages = errors(&source);
5321 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
5322 assert!(named, "expected a complaint about the kind in {messages:?}");
5323 }
5324 }
5325
5326 #[test]
5332 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
5333 let text = ir(concat!(
5334 "void *buf[5];\n",
5335 "int f(void) {\n",
5336 " if (__builtin_setjmp(buf)) return 2;\n",
5337 " return 1;\n",
5338 "}\n",
5339 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5340 ));
5341 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5342 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5343 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5344 }
5345
5346 #[test]
5354 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5355 let text = ir(concat!(
5356 "void *buf[5];\n",
5357 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5358 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5359 ));
5360 let (saves, plain) = text.split_once("func @g").expect("both functions");
5361 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5362 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5363 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5364 }
5365
5366 #[test]
5375 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5376 let text =
5377 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5378 let body = text.split_once("\nf:\n").expect("the function").1;
5379 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5380 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5381 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5382 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5383 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5384 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5385 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5386 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5387 }
5388
5389 #[test]
5397 fn a_save_destroys_every_register_the_allocator_hands_out() {
5398 let text =
5399 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5400 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5401 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5402 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5403 }
5404 }
5405
5406 #[test]
5413 fn the_restore_puts_the_frame_back_before_it_jumps() {
5414 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5415 let mut opts = options();
5416 opts.emit = EmitKind::Asm;
5417 opts.opt_level = level;
5418 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5419 let result = run(&opts, source);
5420 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5421 let text = result.text().to_owned();
5422 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5423 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5424 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5425 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5426 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5427 }
5428 }
5429
5430 #[test]
5436 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5437 for source in [
5438 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5439 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5440 ] {
5441 let messages = errors(source);
5442 let named = messages.iter().any(|m| m.contains("E0710"));
5443 assert!(named, "expected a complaint about the value in {messages:?}");
5444 }
5445 }
5446
5447 #[test]
5452 fn a_static_function_nothing_refers_to_is_not_emitted() {
5453 let text = ir("static int dropped(void) { return 1; }\n\
5454 static int kept(void) { return 2; }\n\
5455 int main(void) { return kept(); }\n");
5456 assert!(text.contains("func @kept"), "{text}");
5457 assert!(!text.contains("dropped"), "{text}");
5458 }
5459
5460 #[test]
5466 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5467 let text = ir("static int ping(void);\n\
5468 static int pong(void) { return ping(); }\n\
5469 static int ping(void) { return pong(); }\n\
5470 int main(void) { return 0; }\n");
5471 assert!(!text.contains("ping"), "{text}");
5472 assert!(!text.contains("pong"), "{text}");
5473 }
5474
5475 #[test]
5481 fn naming_a_static_function_anywhere_keeps_it() {
5482 let text = ir("static int by_address(void) { return 1; }\n\
5483 static int in_an_image(void) { return 2; }\n\
5484 static int deeper(void) { return 3; }\n\
5485 static int reaches_deeper(void) { return deeper(); }\n\
5486 static int (*table[1])(void) = {in_an_image};\n\
5487 int main(void) {\n\
5488 int (*p)(void) = by_address;\n\
5489 return p() + table[0]() + reaches_deeper();\n\
5490 }\n");
5491 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5492 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5493 }
5494 }
5495
5496 #[test]
5502 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5503 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5504 let source = format!(
5505 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5506 int main(void) {{ return 0; }}\n"
5507 );
5508 let text = ir(&source);
5509 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5510 }
5511 }
5512
5513 #[test]
5516 fn a_function_anything_could_call_is_emitted_without_being_called() {
5517 let text =
5518 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5519 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5520 }
5521
5522 #[test]
5529 fn a_classification_c_has_an_operator_for_is_that_operator() {
5530 for (builtin, operator) in [
5531 ("__builtin_isgreater", "binary >"),
5532 ("__builtin_isgreaterequal", "binary >="),
5533 ("__builtin_isless", "binary <"),
5534 ("__builtin_islessequal", "binary <="),
5535 ] {
5536 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5537 let text = tast(&source);
5538 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5539 }
5540 }
5541
5542 #[test]
5551 fn the_classification_builtins_are_comparisons_and_not_calls() {
5552 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5553 assert_eq!(
5554 text,
5555 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5556 %2\n return %3\n"
5557 );
5558
5559 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5561 assert!(text.contains("fcmp one %0, %1"), "{text}");
5562
5563 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5564 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5565
5566 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5567 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5568 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5569 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5570 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5571 assert!(text.contains("%5 = or %3, %4"), "{text}");
5572
5573 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5576 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5577 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5578 assert!(text.contains("%5 = and %3, %4"), "{text}");
5579
5580 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5581 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5582 assert!(text.contains("icmp slt %1, %2"), "{text}");
5583
5584 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5587 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5588
5589 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5592 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5593 }
5594
5595 #[test]
5602 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5603 let text = ir(concat!(
5604 "int a = __builtin_isinff(1e300);\n",
5605 "int b = __builtin_isinf(1e300);\n",
5606 "int c = __builtin_isnan(0.0);\n",
5610 "int d = __builtin_signbit(-0.0);\n",
5611 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5612 ));
5613 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5614 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5615 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5616 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5617 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5618 }
5619
5620 #[test]
5622 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5623 let mut opts = options();
5624 opts.emit = EmitKind::Ir;
5625 let source = concat!(
5626 "int a(int x) { return __builtin_isnan(x); }\n",
5627 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5628 "int c(double x) { return __builtin_isnan(x, x); }\n",
5629 );
5630 let messages = run(&opts, source).messages;
5631 assert_eq!(
5632 messages,
5633 [
5634 "/main.c:1:23: error: non-floating-point argument in call to function \
5635 '__builtin_isnan' [E0685]",
5636 "/main.c:2:30: error: non-floating-point arguments in call to function \
5637 '__builtin_isunordered' [E0685]",
5638 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5639 ]
5640 );
5641 }
5642
5643 #[test]
5652 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5653 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5654 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5658 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5659 assert!(text.contains("%3 = and %1, %2"), "{text}");
5660 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5661 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5662 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5663 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5664 assert!(text.contains("%8 = and %6, %7"), "{text}");
5665
5666 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5670 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5671 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5672
5673 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5674 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5675 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5676 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5677
5678 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5679 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5680 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5681 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5685 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5686 assert!(!text.contains("call"), "{text}");
5687
5688 let text = body(concat!(
5691 "double g(void);\n",
5692 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5693 ));
5694 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5695 }
5696
5697 #[test]
5704 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5705 let text = ir(concat!(
5706 "int a = __builtin_isnormal(1.0);\n",
5707 "int b = __builtin_isnormal(0.0);\n",
5708 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5709 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5710 "int e = __builtin_isinf_sign(1.0);\n",
5711 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5712 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5713 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5714 ));
5715 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5716 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5717 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5718 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5719 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5720 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5721 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5722 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5723 }
5724
5725 #[test]
5731 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5732 let mut opts = options();
5733 opts.emit = EmitKind::Ir;
5734 let source = concat!(
5735 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5736 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5737 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5738 );
5739 let messages = run(&opts, source).messages;
5740 assert_eq!(
5741 messages,
5742 [
5743 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5744 '__builtin_fpclassify' [E0687]",
5745 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5746 [E0511]",
5747 "/main.c:3:23: error: non-floating-point argument in call to function \
5748 '__builtin_fpclassify' [E0685]",
5749 ]
5750 );
5751 }
5752
5753 #[test]
5761 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5762 let text = ir(concat!(
5763 "double a = __builtin_inf();\n",
5764 "float b = __builtin_huge_valf();\n",
5765 "long double c = __builtin_infl();\n",
5766 "double d = __builtin_huge_val();\n",
5767 ));
5768 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5769 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5770 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5771 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5772 assert!(!text.contains("call"), "{text}");
5773 }
5774
5775 #[test]
5784 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5785 let text = ir(concat!(
5786 "double a = __builtin_nan(\"\");\n",
5787 "double b = __builtin_nan(\"0x1\");\n",
5788 "double c = __builtin_nan(\"010\");\n",
5790 "double d = __builtin_nans(\"\");\n",
5791 "double e = __builtin_nans(\"0x1\");\n",
5792 "float f = __builtin_nanf(\"0x1\");\n",
5793 "float g = __builtin_nansf(\"\");\n",
5794 "long double h = __builtin_nansl(\"\");\n",
5795 ));
5796 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5797 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5798 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5799 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5800 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5801 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5802 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5803 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5804
5805 let text = ir(concat!(
5808 "double f(const char *p) { return __builtin_nan(p); }\n",
5809 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5810 ));
5811 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5812 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
5813 }
5814
5815 #[test]
5823 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
5824 let text = ir(concat!(
5825 "unsigned long a = __builtin_strlen(\"hello\");\n",
5826 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
5827 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
5828 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
5829 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
5830 ));
5831 assert!(text.contains("global @a : i64 = 5,"), "{text}");
5832 assert!(text.contains("global @b : i64 = 1,"), "{text}");
5833 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5834 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5835 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5836 assert!(!text.contains("call"), "{text}");
5837
5838 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
5840 assert!(text.contains("call @strlen("), "{text}");
5841 }
5842
5843 #[test]
5850 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
5851 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
5852 assert!(text.contains("bitcast.i64 %0"), "{text}");
5853 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5854 assert!(text.contains("and %1, %2"), "{text}");
5855 assert!(text.contains("bitcast.f64 %3"), "{text}");
5856 assert!(!text.contains("call"), "{text}");
5857
5858 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
5859 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5860 assert!(text.contains("%8 = or %4, %7"), "{text}");
5861 assert!(!text.contains("call"), "{text}");
5862
5863 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
5866 assert!(text.contains("bitcast.i80 %0"), "{text}");
5867 assert!(text.contains("bitcast.f80"), "{text}");
5868
5869 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
5872 assert!(text.contains("fpext.f64 %0"), "{text}");
5873 assert!(text.contains("bitcast.i64 %1"), "{text}");
5874 }
5875
5876 #[test]
5885 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
5886 let text =
5887 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
5888 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
5889 assert!(!text.contains("call"), "{text}");
5890
5891 let text =
5892 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
5893 assert!(text.contains("bitcast.i32 %0"), "{text}");
5894 assert!(!text.contains("call"), "{text}");
5895
5896 let text = body(concat!(
5897 "double copysign(double x, double y);\n",
5898 "double f(double x, double y) { return copysign(x, y); }\n",
5899 ));
5900 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
5901 assert!(!text.contains("call"), "{text}");
5902
5903 let text = body(concat!(
5904 "float copysignf(float x, float y);\n",
5905 "float f(float x, float y) { return copysignf(x, y); }\n",
5906 ));
5907 assert!(!text.contains("call"), "{text}");
5908
5909 let text = ir(concat!(
5913 "long double fabsl(long double x);\n",
5914 "long double f(long double x) { return fabsl(x); }\n",
5915 ));
5916 assert!(text.contains("call @fabsl"), "{text}");
5917 }
5918
5919 #[test]
5927 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
5928 let taken = concat!(
5929 "static double fabs(double b) { return 7; }\n",
5930 "double f(double x) { return fabs(x); }\n",
5931 );
5932 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
5933
5934 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
5935 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
5936
5937 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
5938 let mut opts = options();
5939 opts.emit = EmitKind::Ir;
5940 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
5941
5942 opts.builtins = false;
5943 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
5944
5945 opts.builtins = true;
5946 opts.no_builtin = vec!["fabs".to_owned()];
5947 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
5948 let one = concat!(
5949 "double copysign(double a, double b);\n",
5950 "double f(double x) { return copysign(x, 1.0); }\n",
5951 );
5952 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
5953
5954 opts.no_builtin = Vec::new();
5956 opts.builtins = false;
5957 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
5958 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
5959 }
5960
5961 #[test]
5970 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
5971 let text = ir(concat!(
5972 "double a = __builtin_fabs(-3.5);\n",
5973 "double b = __builtin_copysign(1.0, -0.0);\n",
5974 "double c = __builtin_copysign(0.0, -2.0);\n",
5975 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
5977 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
5978 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
5979 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
5980 "long double i = __builtin_fabsl(-__builtin_infl());\n",
5981 ));
5982 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
5983 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
5984 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
5985 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
5986 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
5987 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
5988 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
5989 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5990 }
5991
5992 #[test]
6000 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6001 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6002 assert!(!text.contains("call"), "{text}");
6003 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6004 assert!(!text.contains("call"), "{text}");
6005
6006 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6009 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6010 assert!(!text.contains("call"), "{text}");
6011 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6012 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6013
6014 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6017 assert_eq!(written, text, "the name and the operator are the same thing");
6018
6019 let text = body(concat!(
6021 "double creal(_Complex double z);\n",
6022 "double f(_Complex double z) { return creal(z); }\n",
6023 ));
6024 assert!(!text.contains("call"), "{text}");
6025 let text = body(concat!(
6026 "_Complex float conjf(_Complex float z);\n",
6027 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6028 ));
6029 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6030 assert!(!text.contains("call"), "{text}");
6031
6032 let taken = concat!(
6035 "static double creal(_Complex double z) { return 7; }\n",
6036 "double f(_Complex double z) { return creal(z); }\n",
6037 );
6038 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6039 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6040 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6041 let plain = concat!(
6042 "double cimag(_Complex double z);\n",
6043 "double f(_Complex double z) { return cimag(z); }\n",
6044 );
6045 let mut opts = options();
6046 opts.emit = EmitKind::Ir;
6047 opts.builtins = false;
6048 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6049 opts.builtins = true;
6050 opts.no_builtin = vec!["cimag".to_owned()];
6051 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6052
6053 let text = ir(concat!(
6055 "double a = __builtin_creal(1.5 + 2.5i);\n",
6056 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6057 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6058 ));
6059 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6060 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6061 assert!(
6062 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6063 "the conjugate of a constant is the constant with the second half negated: {text}"
6064 );
6065 assert!(!text.contains("call"), "{text}");
6066 }
6067
6068 #[test]
6076 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6077 let text = ir(concat!(
6078 "double a = __builtin_ceil(1.5);\n",
6079 "double b = __builtin_floor(1.5);\n",
6080 "double c = __builtin_trunc(-1.5);\n",
6081 "double d = __builtin_round(2.5);\n",
6084 "double e = __builtin_ceil(-0.5);\n",
6086 "double f = __builtin_fmax(1.0, 2.0);\n",
6087 "double g = __builtin_fmin(1.0, 2.0);\n",
6088 "float h = __builtin_ceilf(1.25f);\n",
6089 "double ceil(double x);\n",
6092 "double i = ceil(2.25);\n",
6093 ));
6094 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6095 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6096 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6097 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6098 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6099 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6100 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6101 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6102 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6103 assert!(!text.contains("call"), "{text}");
6104 }
6105
6106 #[test]
6114 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6115 let text = ir(concat!(
6116 "double f(double x) { return __builtin_ceil(x); }\n",
6117 "float g(float x) { return __builtin_floorf(x); }\n",
6118 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6119 ));
6120 assert!(text.contains("call @ceil("), "{text}");
6121 assert!(text.contains("call @floorf("), "{text}");
6122 assert!(text.contains("call @fmax("), "{text}");
6123
6124 let text = ir(concat!(
6128 "double f(void) { return __builtin_rint(2.5); }\n",
6129 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6130 ));
6131 assert!(text.contains("call @rint("), "{text}");
6132 assert!(text.contains("call @nearbyint("), "{text}");
6133
6134 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6137 assert!(text.contains("call @fmin("), "{text}");
6138
6139 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6142 let mut opts = options();
6143 opts.emit = EmitKind::Ir;
6144 opts.no_builtin = vec!["ceil".to_owned()];
6145 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6146 }
6147
6148 #[test]
6155 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6156 let text = ir(concat!(
6157 "constexpr int side = 4;\n",
6158 "constexpr int wider = side + 1;\n",
6159 "constexpr double half = 1.5;\n",
6160 "struct point { int x; int y; };\n",
6161 "constexpr struct point origin = { 5, 6 };\n",
6162 "int square[side * side];\n",
6163 "int rectangle[wider];\n",
6164 "int rounded[(int)half * 2];\n",
6165 "int across[origin.y];\n",
6166 "enum named { four = side };\n",
6167 "int e = four;\n",
6168 ));
6169 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6170 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6171 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6172 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6173 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6174
6175 let mut opts = options();
6178 opts.emit = EmitKind::Ir;
6179 let konst = "const int n = 1;\nint a[n];\n";
6180 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6181 assert_eq!(run(&opts, konst).messages, [message]);
6182
6183 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6185 assert_eq!(run(&opts, subscript).messages, [message]);
6186
6187 let address = "constexpr int c = 3;\nint *p = &c;\n";
6189 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6190 pointer target type [E0514]";
6191 assert_eq!(run(&opts, address).messages, [warning]);
6192 }
6193
6194 #[test]
6209 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
6210 let mut opts = options();
6211 opts.emit = EmitKind::Ir;
6212 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
6213
6214 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
6216 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
6217
6218 let plain = concat!(
6221 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
6222 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
6223 );
6224 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
6225
6226 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
6229 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
6230 [E0514]";
6231 assert_eq!(run(&opts, &dropping).messages, [warning]);
6232
6233 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
6236 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
6237 incompatible return type 'const unsigned int (*)[4]' [E0512]";
6238 assert_eq!(run(&opts, wrong).messages, [error]);
6239 }
6240
6241 #[test]
6250 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
6251 let mut opts = options();
6254 opts.std = Std::C17;
6255 let source = concat!(
6256 "int add(a, b)\n",
6257 "int a;\n",
6258 "int b;\n",
6259 "{ return a + b; }\n",
6260 "int promoted(c)\n",
6261 "char c;\n",
6262 "{ return c; }\n",
6263 "int narrow(char);\n",
6264 "int narrow(c)\n",
6265 "char c;\n",
6266 "{ return c; }\n",
6267 "int first(a)\n",
6268 "int a[4];\n",
6269 "{ return a[0]; }\n",
6270 );
6271 let result = run(&opts, source);
6272 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6273 let text = result.text();
6274 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6275 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6276 assert!(text.contains("c : char object automatic defined"), "{text}");
6278 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
6279 assert!(text.contains("first : int(int *) function external defined"), "{text}");
6281 }
6282
6283 #[test]
6290 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
6291 let mut opts = options();
6292 opts.std = Std::C17;
6293 for (source, message) in [
6294 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
6295 (
6296 "int f(a)\nint a;\nint b;\n{ return a; }\n",
6297 "3:5: error: declaration for parameter 'b' but no such parameter",
6298 ),
6299 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
6300 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
6301 (
6302 "int f(a)\nstatic int a;\n{ return a; }\n",
6303 "2:12: error: storage class specified for parameter 'a'",
6304 ),
6305 (
6306 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
6307 "2:7: error: argument 'a' doesn't match prototype",
6308 ),
6309 ] {
6310 let result = run(&opts, source);
6311 assert!(result.failed(), "expected this to fail:\n{source}");
6312 assert!(result.messages[0].contains(message), "{:?}", result.messages);
6313 }
6314
6315 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
6318 let mut older = options();
6319 older.std = Std::C89;
6320 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
6321 let result = run(&opts, implicit);
6322 assert!(
6323 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
6324 "{:?}",
6325 result.messages
6326 );
6327
6328 let mut newer = options();
6332 newer.std = Std::C23;
6333 let plain = "int f(a)\nint a;\n{ return a; }\n";
6334 let result = run(&newer, plain);
6335 assert!(!result.failed(), "{:?}", result.messages);
6336 assert_eq!(
6337 result.messages,
6338 ["/main.c:1:5: warning: old-style function definition [E0412]"]
6339 );
6340 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
6341 }
6342
6343 #[test]
6350 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
6351 let array = "int a[8] = { [3] 7 };\n";
6352 let member = "struct s { int x; } v = { x: 7 };\n";
6353 for source in [array, member] {
6354 let result = run(&options(), source);
6355 assert!(!result.failed(), "{:?}", result.messages);
6356 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
6357 }
6358
6359 let mut asked = options();
6360 asked.pedantic = true;
6361 assert_eq!(
6362 run(&asked, array).messages,
6363 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
6364 );
6365 assert_eq!(
6366 run(&asked, member).messages,
6367 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
6368 );
6369 }
6370
6371 #[test]
6378 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
6379 let text = ir(concat!(
6380 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
6381 "struct brim { char buf[9223372036854775807L]; };\n",
6382 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
6383 "unsigned long h = sizeof(struct huge_struct);\n",
6384 "unsigned long b = sizeof(struct brim);\n",
6385 "unsigned long y = sizeof(struct bitty);\n",
6386 ));
6387 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6388 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6389 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6390
6391 let mut opts = options();
6392 opts.emit = EmitKind::Ir;
6393 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6394 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6395 assert_eq!(run(&opts, over).messages, [message]);
6396 let array = "struct wide { short buf[1L << 62]; };\n";
6397 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6398 maximum object size '9223372036854775807' [E0537]";
6399 assert_eq!(run(&opts, array).messages[0], message);
6400 }
6401
6402 fn compile_bytes(source: &[u8]) -> Compiled {
6407 let mut opts = options();
6408 opts.emit = EmitKind::Ir;
6409 let mut fs = MemoryFileSystem::new();
6410 fs.insert("/main.c", source.to_vec());
6411 compile(&opts, "/main.c", &fs)
6412 }
6413
6414 #[test]
6421 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6422 let mut source = b"char s[] = \"a".to_vec();
6423 source.push(0xff);
6424 source.extend_from_slice(b"b\";\nchar c = '");
6425 source.push(0xff);
6426 source.extend_from_slice(b"';\n");
6427 let result = compile_bytes(&source);
6428 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6429 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6430 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6432
6433 let mut stray = b"int a".to_vec();
6434 stray.push(0xff);
6435 stray.extend_from_slice(b" = 1;\n");
6436 let result = compile_bytes(&stray);
6437 assert!(
6438 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6439 "{:?}",
6440 result.messages
6441 );
6442 }
6443
6444 #[test]
6445 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6446 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6447 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6448 let expected = "\
6449func @add(i32, i32) -> i32, linkage(external) {
6450block0(%0: i32, %1: i32):
6451 %2 = add.nsw %0, %1
6452 return %2
6453}
6454";
6455 assert!(text.contains(expected), "{text}");
6456 }
6457
6458 #[test]
6459 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6460 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6461 assert!(!text.contains("alloca"), "{text}");
6462 assert!(!text.contains("load"), "{text}");
6463 assert!(!text.contains("store"), "{text}");
6464 }
6465
6466 #[test]
6467 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6468 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6469 let expected = "\
6470block0:
6471 %0 = alloca, size 4, align 4
6472 %1 = iconst.i32 1
6473 store %1 -> %0, align 4, tbaa !1
6474 %2 = call @g(%0) : (ptr) -> i32
6475 return %2
6476";
6477 assert_eq!(text, expected);
6478 }
6479
6480 #[test]
6481 fn a_loop_carries_what_it_changes_as_block_parameters() {
6482 let text = body(
6485 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6486 return total;\n}\n",
6487 );
6488 assert!(!text.contains("alloca"), "{text}");
6489 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6490 assert!(text.contains("jump block1("), "{text}");
6491 }
6492
6493 #[test]
6494 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6495 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6496 assert!(text.contains("icmp slt %0, %1"), "{text}");
6497 assert!(!text.contains("zext"), "{text}");
6498 }
6499
6500 #[test]
6501 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6502 let text = body("int f(int a, int b) { return a && b; }\n");
6503 let expected = "\
6504block0(%0: i32, %1: i32):
6505 %2 = iconst.i32 0
6506 %3 = icmp ne %0, %2
6507 %4 = iconst.i1 0
6508 br_if %3, block1, block2(%4)
6509
6510block1:
6511 %5 = iconst.i32 0
6512 %6 = icmp ne %1, %5
6513 jump block2(%6)
6514
6515block2(%7: i1):
6516 %8 = zext.i32 %7
6517 return %8
6518";
6519 assert_eq!(text, expected);
6520 }
6521
6522 #[test]
6523 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6524 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6525 assert!(!text.contains("block3"), "{text}");
6528 assert!(!text.contains("iconst.i32 3"), "{text}");
6529 }
6530
6531 #[test]
6532 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6533 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6534 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6535 assert!(body("int f(void) { }\n").contains("unreachable"));
6536 }
6537
6538 #[test]
6539 fn a_structure_is_copied_rather_than_held_in_a_value() {
6540 let text = body(
6541 "struct point { int x, y; };\n\
6542 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6543 );
6544 assert!(text.contains("memcpy"), "{text}");
6545 }
6546
6547 #[test]
6548 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6549 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6550 assert!(text.contains("memset"), "{text}");
6551 }
6552
6553 #[test]
6554 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6555 let text = body(
6556 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6557 default: r = 4; } return r; }\n",
6558 );
6559 let expected = "\
6560block0(%0: i32):
6561 %1 = iconst.i32 0
6562 switch %0, block1, [1 => block2, 2 => block3(%1)]
6563
6564block1:
6565 %2 = iconst.i32 4
6566 jump block4(%2)
6567
6568block2:
6569 %3 = iconst.i32 1
6570 jump block3(%3)
6571
6572block3(%4: i32):
6573 %5 = iconst.i32 2
6574 %6 = add.nsw %4, %5
6575 jump block4(%6)
6576
6577block4(%7: i32):
6578 return %7
6579";
6580 assert_eq!(text, expected);
6581 }
6582
6583 #[test]
6584 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6585 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6588 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6589 assert!(text.contains("icmp ule"), "{text}");
6590 assert!(!text.contains("switch"), "{text}");
6591 }
6592
6593 #[test]
6594 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6595 let text = body(
6596 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6597 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6598 );
6599 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6602 assert!(text.contains("block5:\n jump block7("), "{text}");
6603 assert!(text.contains("block6:\n jump block8("), "{text}");
6604 }
6605
6606 #[test]
6607 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6608 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6609 }
6610
6611 #[test]
6612 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6613 let text = body(
6618 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6619 return n; }\n",
6620 );
6621 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6624 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6625 assert!(text.contains("block4:\n jump block3("), "{text}");
6626 }
6627
6628 #[test]
6629 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6630 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6633 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6634 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6635 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6636 }
6637
6638 #[test]
6639 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6640 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6641 assert!(!text.contains("alloca"), "{text}");
6645 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6646 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6647 }
6648
6649 #[test]
6650 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6651 let text =
6652 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6653 assert!(!text.contains("alloca"), "{text}");
6654 assert!(text.contains("block1(%2: i32):"), "{text}");
6655 assert!(text.contains("jump block1(%5)"), "{text}");
6656 }
6657
6658 #[test]
6659 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6660 assert_eq!(
6663 body("int f(int x) { return x; spare: return 0; }\n"),
6664 "block0(%0: i32):\n return %0\n"
6665 );
6666 }
6667
6668 #[test]
6669 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6670 let text = body(
6671 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6672 );
6673 assert_eq!(
6676 text,
6677 "\
6678block0(%0: ptr):
6679 %1 = load.i8 %0, align 1
6680 %2 = iconst.i8 3
6681 %3 = ashr %1, %2
6682 %4 = sext.i32 %3
6683 return %4
6684"
6685 );
6686 }
6687
6688 #[test]
6689 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6690 let text =
6694 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6695 assert_eq!(
6696 text,
6697 "\
6698block0(%0: ptr, %1: i32):
6699 %2 = iconst.i32 16777215
6700 %3 = and %1, %2
6701 %4 = trunc.i16 %3
6702 store %4 -> %0, align 2
6703 %5 = iconst.i32 16
6704 %6 = lshr %3, %5
6705 %7 = trunc.i8 %6
6706 %8 = iconst.i64 2
6707 %9 = ptr_add %0, %8
6708 store %7 -> %9, align 1
6709 return
6710"
6711 );
6712 }
6713
6714 #[test]
6715 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6716 let text =
6717 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6718 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6721 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6722 }
6723
6724 #[test]
6725 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6726 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6729 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6730 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6731 }
6732
6733 #[test]
6734 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6735 let text = body(
6739 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6740 );
6741 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6742 }
6743
6744 #[test]
6745 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6746 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6749 assert!(
6750 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6751 "{text}"
6752 );
6753 }
6754
6755 #[test]
6756 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6757 let text = ir(concat!(
6762 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6763 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6764 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6765 "char s[2] = \"hi\";\n",
6766 ));
6767 assert!(
6768 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6769 "{text}"
6770 );
6771 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6772 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6773 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6776 }
6777
6778 #[test]
6779 fn a_definition_takes_a_parameter_it_left_unnamed() {
6780 let text = ir("int f(int a, int) { return a; }\n");
6784 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
6785 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
6786
6787 let text = ir("int g(int, int n) { return n; }\n");
6790 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
6791 }
6792
6793 #[test]
6794 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
6795 let text = body(concat!(
6800 "struct s { int f; int g; };\n",
6801 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
6802 "{ *d = *e = a[0] = *c; }\n",
6803 ));
6804 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
6805 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
6806 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
6807 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
6808 }
6809
6810 #[test]
6811 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
6812 let mut opts = options();
6817 opts.emit = EmitKind::Ir;
6818 let result = run(
6819 &opts,
6820 concat!(
6821 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
6822 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
6823 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
6824 "const union u c = { { \"1234\", \"567\" } };\n",
6825 ),
6826 );
6827 let text = result.text();
6828 assert_eq!(
6829 result.messages,
6830 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
6831 (5 chars into 3 available) [E0637]"]
6832 );
6833 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
6834 assert!(
6835 text.contains(
6836 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
6837 bytes \"9\\00\", zero 3 }"
6838 ),
6839 "{text}"
6840 );
6841 assert!(
6844 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
6845 "{text}"
6846 );
6847 }
6848
6849 #[test]
6850 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
6851 let text = body(concat!(
6855 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
6856 "void g(struct v *);\n",
6857 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
6858 ));
6859 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
6860 }
6861
6862 #[test]
6863 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
6864 let text = ir(concat!(
6869 "struct s { int x; };\n",
6870 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
6871 "int n = (int){ 7 };\n",
6872 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
6873 ));
6874 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
6875 assert!(text.contains("global @n : i32 = 7,"), "{text}");
6876 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
6879 }
6880
6881 #[test]
6882 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
6883 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
6887 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
6888 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
6889 }
6890
6891 #[test]
6892 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
6893 let text = ir("unsigned char foo[1][0];\n");
6897 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
6898 }
6899
6900 #[test]
6901 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
6902 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
6905 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
6906 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
6907 }
6908
6909 #[test]
6910 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
6911 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
6915 assert!(
6916 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
6917 "{text}"
6918 );
6919 }
6920
6921 #[test]
6922 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
6923 let text = body(
6928 "\
6929struct s { int a, b; };
6930struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
6931",
6932 );
6933 assert!(text.contains("block3(%7: ptr)"), "{text}");
6935 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
6936 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
6937 }
6938
6939 #[test]
6947 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
6948 let text = body("int f(int i) { return ++i ?: 10; }\n");
6949 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
6950 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6951
6952 let text = body("long f(int i) { return ++i ?: 10L; }\n");
6955 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
6956 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
6957
6958 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
6960 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
6961
6962 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
6965 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
6966 }
6967
6968 #[test]
6969 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
6970 let text = ir("\
6974struct pair { int a, b; };
6975struct pair make(int a, int b);
6976struct pair twice(struct pair p) { return make(p.a, p.b); }
6977");
6978 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
6979 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
6980 }
6981
6982 #[test]
6983 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
6984 let text = ir("\
6988struct big { double v[8]; };
6989struct big grow(struct big b);
6990struct big twice(struct big b) { return grow(grow(b)); }
6991");
6992 assert!(
6993 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
6994 "{text}"
6995 );
6996 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
6997 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7000 }
7001
7002 #[test]
7003 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7004 let text = ir("\
7009struct big { double v[8]; };
7010struct pair { int a, b; };
7011int p(const char *, ...);
7012int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7013");
7014 assert!(
7015 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7016 "{text}"
7017 );
7018 }
7019
7020 #[test]
7021 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7022 let body = body(
7025 "\
7026struct pair { int a, b; };
7027struct pair make(int a, int b);
7028int second(void) { return make(1, 2).b; }
7029",
7030 );
7031 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7032 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7033 }
7034
7035 #[test]
7036 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7037 let source = "\
7041struct hfa { float x, y, z; };
7042int take(struct hfa h);
7043int give(struct hfa h) { return take(h); }
7044";
7045 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7046 let mut opts = options();
7047 opts.emit = EmitKind::Ir;
7048 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7049 let result = run(&opts, source);
7050 assert_eq!(result.messages, Vec::<String>::new());
7051 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7052 }
7053
7054 #[test]
7055 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7056 let source = "\
7059int use(int *);
7060void f(int n) {
7061 {
7062 int a[n];
7063 use(a);
7064 }
7065 use(0);
7066}
7067";
7068 let body = body(source);
7069 assert!(body.contains("mul.nsw"), "{body}");
7070 assert!(body.contains("stacksave"), "{body}");
7071 assert!(body.contains("alloca %"), "{body}");
7072 assert!(body.contains("stackrestore"), "{body}");
7073 }
7074
7075 #[test]
7076 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7077 let source = "\
7082int use(int *);
7083int f(int n) {
7084 {
7085 int a[n];
7086 if (use(a)) goto out;
7087 use(0);
7088 }
7089out:
7090 return 0;
7091}
7092";
7093 let body = body(source);
7094 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7096 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7097 assert!(after.starts_with(" %4\n jump block"), "{body}");
7098 }
7099
7100 #[test]
7101 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7102 let source = "\
7106int use(int *);
7107int f(int n) {
7108 int a[n];
7109again:
7110 if (use(a)) goto again;
7111 return 0;
7112}
7113";
7114 let body = body(source);
7115 assert!(body.contains("stacksave"), "{body}");
7116 assert!(!body.contains("stackrestore"), "{body}");
7117 }
7118
7119 #[test]
7120 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7121 let source = "\
7126int use(int *);
7127int f(int n) {
7128again:
7129 {
7130 int a[n];
7131 if (use(a)) goto again;
7132 }
7133 return 0;
7134}
7135";
7136 let body = body(source);
7137 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7138 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7139 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7140 }
7141
7142 #[test]
7143 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
7144 let source = "\
7150int f(void);
7151void t(void) {
7152 int count = 10;
7153 for (; count--;) {
7154 int b[f()];
7155 int i;
7156 for (i = 0; i < f(); i++) {
7157 b[i] = count;
7158 }
7159 }
7160}
7161";
7162 let body = body(source);
7163 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7167 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7168 let next = after.split("\n\n").next().expect("the block the restore is in");
7171 assert!(next.contains("jump block1("), "{body}");
7172 }
7173
7174 #[test]
7175 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
7176 let source = "\
7179unsigned long f(int n) {
7180 int a[n];
7181 n = 0;
7182 return sizeof a;
7183}
7184";
7185 let body = body(source);
7186 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
7188 }
7189
7190 #[test]
7191 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
7192 let source = "\
7195int use(int);
7196int f(int x) {
7197 return ({
7198 int t = use(x);
7199 t * t;
7200 });
7201}
7202";
7203 let expected = "\
7204block0(%0: i32):
7205 %1 = call @use(%0) : (i32) -> i32
7206 %2 = mul.nsw %1, %1
7207 return %2
7208";
7209 assert_eq!(body(source), expected);
7210 }
7211
7212 #[test]
7213 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
7214 let source = "int f(int x) { return ({ return x; 0; }); }\n";
7218 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
7219 }
7220
7221 #[test]
7222 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
7223 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
7227 let expected = "\
7228block0(%0: ptr):
7229 %1 = va_arg.f64 %0
7230 %2 = va_arg.f64 %0
7231 %3 = fadd %1, %2
7232 return %3
7233";
7234 assert_eq!(body(source), expected);
7235 }
7236
7237 #[test]
7238 fn one_that_reads_a_structure_answers_where_the_object_is() {
7239 let source = "\
7253struct s { int a; long b; };
7254long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
7255";
7256 let expected = "\
7257block0(%0: ptr):
7258 %1 = alloca, size 16, align 16
7259 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
7260 memcpy %1, %2, size 16, align 8
7261 %3 = iconst.i64 8
7262 %4 = ptr_add %1, %3
7263 %5 = load.i64 %4, align 8, tbaa !1
7264 return %5
7265";
7266 assert_eq!(body(source), expected);
7267 }
7268
7269 #[test]
7273 fn the_classification_says_which_registers_the_object_arrived_in() {
7274 let source = "\
7275struct s { double a; double b; };
7276double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
7277";
7278 assert!(
7279 body(source)
7280 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
7281 "{}",
7282 body(source)
7283 );
7284
7285 let big = "\
7286struct s { long a[4]; };
7287long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
7288";
7289 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
7290 }
7291
7292 #[test]
7293 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
7294 let source = "\
7298int f(int c) {
7299 void *p = c ? &&one : &&two;
7300 goto *p;
7301one:
7302 return 1;
7303two:
7304 return 2;
7305}
7306";
7307 let expected = "\
7308block0(%0: i32):
7309 %1 = iconst.i32 0
7310 %2 = icmp ne %0, %1
7311 br_if %2, block1, block2
7312
7313block1:
7314 %3 = block_addr block3
7315 jump block4(%3)
7316
7317block2:
7318 %4 = block_addr block5
7319 jump block4(%4)
7320
7321block3:
7322 %5 = iconst.i32 1
7323 return %5
7324
7325block4(%6: ptr):
7326 indirect_br %6, block3, block5
7327
7328block5:
7329 %7 = iconst.i32 2
7330 return %7
7331";
7332 assert_eq!(body(source), expected);
7333 }
7334
7335 #[test]
7336 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
7337 let source = "void **next(void);
7340void f(void) { goto *next(); }
7341";
7342 let expected = "\
7343block0:
7344 %0 = call @next() : () -> ptr
7345 unreachable
7346";
7347 assert_eq!(body(source), expected);
7348 }
7349
7350 #[test]
7351 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
7352 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
7355 let expected = "\
7356block0:
7357 inline_asm.volatile \"mfence\", \"\", \"memory\"()
7358 return
7359";
7360 assert_eq!(body(source), expected);
7361 }
7362
7363 #[test]
7364 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
7365 let source = "\
7368int f(int x, int y) {
7369 int r;
7370 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
7371 return r + y;
7372}
7373";
7374 let expected = "\
7375block0(%0: i32, %1: i32):
7376 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
7377 %4 = add.nsw %2, %3
7378 return %4
7379";
7380 assert_eq!(body(source), expected);
7381 }
7382
7383 #[test]
7384 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
7385 let source = "\
7390struct pair { int a, b; };
7391int f(int x) {
7392 int slot = x;
7393 struct pair p = { x, x };
7394 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7395 return slot + p.a;
7396}
7397";
7398 let text = body(source);
7399 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7400 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7401 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7402 }
7403
7404 #[test]
7405 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7406 let source = "\
7411int f(int x) {
7412 int r = 7;
7413 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7414 return r;
7415away:
7416 return r;
7417}
7418";
7419 let expected = "\
7420block0(%0: i32):
7421 %1 = iconst.i32 7
7422 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7423
7424block1:
7425 return %2
7426
7427block2:
7428 return %1
7429";
7430 assert_eq!(body(source), expected);
7431 }
7432
7433 #[test]
7434 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7435 let mut opts = options();
7439 opts.emit = EmitKind::Ir;
7440 for (source, expected) in [
7441 (
7442 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7443 "output operand constraint lacks '='",
7444 ),
7445 (
7446 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7447 "lvalue required in 'asm' statement",
7448 ),
7449 (
7450 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7451 "read-only variable 'g' used as 'asm' output",
7452 ),
7453 (
7454 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7455 "input operand constraint contains '='",
7456 ),
7457 (
7458 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7459 "memory input 0 is not directly addressable",
7460 ),
7461 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7462 (
7463 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7464 "duplicate asm operand name 'a'",
7465 ),
7466 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7467 ] {
7468 let result = run(&opts, source);
7469 assert!(result.failed(), "expected this to be reported:\n{source}");
7470 assert!(
7471 result.messages.iter().any(|m| m.contains(expected)),
7472 "{expected}\n{:?}",
7473 result.messages
7474 );
7475 }
7476 }
7477
7478 #[test]
7483 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7484 let text = ir(concat!(
7485 "__asm__(\n",
7486 " \".section .rodata\\n\"\n",
7487 " \".globl first\\n\"\n",
7488 " \".balign 8\\n\"\n",
7489 " \"first:\\n\"\n",
7490 " \".long 1\\n\"\n",
7491 " \".long 2\\n\"\n",
7492 " \".globl last\\n\"\n",
7493 " \"last:\\n\"\n",
7494 " \".quad last - first\\n\");\n",
7495 "extern const int first[];\n",
7496 "extern const long last;\n",
7497 ));
7498 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7499 assert!(text.contains("global @last : i64 = 8"), "{text}");
7500 }
7501
7502 #[test]
7506 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7507 let text = ir(concat!(
7508 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7509 "extern int counter;\n",
7510 "int read(void) { return counter; }\n",
7511 ));
7512 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7513 }
7514
7515 #[test]
7518 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7519 let mut opts = options();
7520 opts.emit = EmitKind::Ir;
7521 let mut fs = MemoryFileSystem::new();
7522 fs.insert(
7523 "/main.c",
7524 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7525 );
7526 fs.insert("seed", b"hi".to_vec());
7527 let result = compile(&opts, "/main.c", &fs);
7528 assert_eq!(result.messages, Vec::<String>::new());
7529 let text = result.text();
7530 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7531 }
7532
7533 #[test]
7536 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7537 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7538 assert!(
7539 messages
7540 .iter()
7541 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7542 "{messages:?}"
7543 );
7544 }
7545
7546 #[test]
7549 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7550 for source in [
7551 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7552 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7553 ] {
7554 let messages = errors(source);
7555 assert!(
7556 messages
7557 .iter()
7558 .any(|m| m.contains("not supported yet")
7559 && m.contains("in an `asm` at file scope")),
7560 "{source}\n{messages:?}"
7561 );
7562 }
7563 }
7564
7565 #[test]
7566 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7567 let mut opts = options();
7568 opts.emit = EmitKind::Ir;
7569 for source in [
7570 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7571 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7572 ] {
7573 let result = run(&opts, source);
7574 assert!(result.failed(), "expected this to be reported:\n{source}");
7575 assert!(
7576 result.messages.iter().any(|m| m.contains("not supported yet")),
7577 "{:?}",
7578 result.messages
7579 );
7580 }
7581 }
7582
7583 fn round_trip(source: &str) -> (String, String) {
7585 let printed = ir(source);
7586 let mut opts = options();
7587 opts.emit = EmitKind::Ir;
7588 let mut fs = MemoryFileSystem::new();
7589 fs.insert("/main.ir", printed.clone().into_bytes());
7590 let result = compile_ir(&opts, "/main.ir", &fs);
7591 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
7592 (printed, result.text().to_owned())
7593 }
7594
7595 #[test]
7596 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
7597 let (printed, again) = round_trip(
7601 "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",
7602 );
7603 assert_eq!(printed, again);
7604 }
7605
7606 #[test]
7607 fn ir_that_is_not_ir_says_which_line_stopped_it() {
7608 let mut opts = options();
7609 opts.emit = EmitKind::Ir;
7610 let mut fs = MemoryFileSystem::new();
7611 let text = "\
7612; ModuleID = 'a.c'
7613; format 0
7614target triple = \"x86_64-unknown-linux-gnu\"
7615target datalayout = \"e-p:64:64-i64:64-S128\"
7616
7617func @f(), linkage(external) {
7618block0:
7619 frobnicate
7620}
7621";
7622 fs.insert("/main.ir", text.as_bytes().to_vec());
7623 let result = compile_ir(&opts, "/main.ir", &fs);
7624 assert!(result.failed());
7625 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
7626 }
7627
7628 #[test]
7629 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
7630 let mut opts = options();
7633 opts.emit = EmitKind::Ir;
7634 let mut fs = MemoryFileSystem::new();
7635 let text = "\
7636; ModuleID = 'a.c'
7637; format 0
7638target triple = \"x86_64-unknown-linux-gnu\"
7639target datalayout = \"e-p:64:64-i64:64-S128\"
7640
7641func @f(), linkage(external) {
7642block0:
7643 %0 = iconst.i32 1
7644 return %0
7645}
7646";
7647 fs.insert("/main.ir", text.as_bytes().to_vec());
7648 let result = compile_ir(&opts, "/main.ir", &fs);
7649 assert!(result.failed());
7650 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
7651 }
7652
7653 #[test]
7654 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
7655 let mut fs = MemoryFileSystem::new();
7657 fs.insert("/main.ir", Vec::new());
7658 let result = compile_ir(&options(), "/main.ir", &fs);
7659 assert!(result.failed());
7660 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
7661 }
7662
7663 #[test]
7664 fn the_printed_ir_reads_back_as_the_same_module() {
7665 let text = ir("\
7668struct point { int x, y; };
7669static const char greeting[] = \"hi\";
7670int table[4] = { 1, 2, 3 };
7671int puts(const char *);
7672double half(double x) { return x / 2.0; }
7673int f(int n) {
7674 int total = 0;
7675 for (int i = 0; i < n; i++) {
7676 if (i == 3) continue;
7677 total += table[i];
7678 }
7679 switch (n) {
7680 case 0: total = 1;
7681 case 1: total++; break;
7682 default: total = -total;
7683 }
7684 struct point p = { total, 1 };
7685 int *q = &p.y;
7686 puts(greeting);
7687 return p.x + *q;
7688}
7689int dispatch(int c) {
7690 void *p = c ? &&one : &&two;
7691 goto *p;
7692one:
7693 return 1;
7694two:
7695 return 2;
7696}
7697int assembly(int x, int *p) {
7698 int r;
7699 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
7700 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
7701 return r;
7702away:
7703 return 0;
7704}
7705");
7706 let mut names = Interner::new();
7707 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
7708 assert_eq!(rucc_ir::print(&module, &names), text);
7709 }
7710
7711 #[test]
7712 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
7713 let mut opts = options();
7717 opts.emit = EmitKind::Object;
7718 opts.save_temps = rucc_session::SaveTemps::Object;
7719 let result = run(&opts, "#define N 2\nint a[N];\n");
7720 assert_eq!(result.messages, Vec::<String>::new());
7721 let text = result.temps.preprocessed.expect("the preprocessed text");
7722 assert!(text.contains("int a[2];"), "{text}");
7723 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
7724 let asm = result.temps.assembly.expect("the assembly");
7725 assert!(asm.contains("a:"), "{asm}");
7726 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
7727 }
7728
7729 #[test]
7730 fn nothing_is_kept_unless_the_flag_asked_for_it() {
7731 let mut opts = options();
7734 opts.emit = EmitKind::Object;
7735 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
7736 }
7737
7738 #[test]
7739 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
7740 let mut opts = options();
7743 opts.emit = EmitKind::Ir;
7744 opts.save_temps = rucc_session::SaveTemps::Cwd;
7745 let result = run(&opts, "int a;\n");
7746 assert!(result.temps.preprocessed.is_some());
7747 assert_eq!(result.temps.assembly, None);
7748 }
7749}