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, SourceMap, 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(crate::phase::source_name(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 crate::phase::source_name(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 align: opts.align_functions,
363 read: &mut read,
364 },
365 );
366 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
370 if !failed {
371 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
376 for error in errors {
377 diagnostics.push(internal(&format!("invalid IR, {error}")));
378 }
379 } else if let Err(complaints) =
380 instrument(&mut lowered.module, &mut sess.interner, opts)
381 .map(|done| instrumented = done)
382 {
383 diagnostics.extend(complaints);
384 } else if let Err(complaints) = optimize(
385 &mut lowered.module,
386 &sess.interner,
387 &sess.target,
388 opts,
389 name,
390 &mut dumps,
391 &mut remarks,
392 ) {
393 diagnostics.extend(complaints);
394 } else if opts.emit == EmitKind::SafetySummary {
395 artifact = Artifact::Text(
400 rucc_safety::summarize(
401 &lowered.module,
402 &sess.interner,
403 name,
404 opts.safety.as_str(),
405 instrumented.checks,
406 instrumented.interposed,
407 instrumented.crossings,
408 )
409 .render(),
410 );
411 } else if opts.emit == EmitKind::Ir {
412 artifact =
417 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
418 } else {
419 match generate(
422 &mut lowered.module,
423 &mut sess.interner,
424 &sess.target,
425 opts,
426 &mut Recording {
427 fired: &mut fired,
428 pressure: &mut pressure,
429 lowerings: &mut lowerings,
430 },
431 &mut temps.assembly,
432 Origin { map: &sess.sources, name },
433 ) {
434 Ok(made) => artifact = made,
435 Err(complaints) => diagnostics.extend(complaints),
436 }
437 }
438 }
439 diagnostics.extend(lowered.diagnostics);
440 }
441 _ => {}
442 }
443 }
444 diagnostics.extend(checked.diagnostics);
445 }
446
447 let mut messages = Vec::with_capacity(diagnostics.len());
448 let mut errors = 0;
449 for diag in &diagnostics {
450 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
456 continue;
457 }
458 if diag.severity.is_fatal()
459 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
460 {
461 errors += 1;
462 }
463 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
464 }
465 if errors > 0 {
466 artifact = Artifact::Nothing;
468 }
469 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
472}
473
474#[must_use]
484pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
485 let mut sess = Session::new(opts.clone());
486 if opts.emit != EmitKind::Ir {
487 return failure(format!(
488 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
489 the C in front of it became",
490 opts.emit.as_str()
491 ));
492 }
493 let bytes = match fs.read(Path::new(name)) {
494 Ok(bytes) => bytes,
495 Err(e) => return failure(format!("{name}: {e}")),
496 };
497 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
498 return failure(format!("{name}: this is not text, so it is not IR"));
499 };
500
501 let module = match rucc_ir::parse(text, &mut sess.interner) {
502 Ok(module) => module,
503 Err(error) => {
504 return failure(format!("{name}:{}: {}", error.line, error.message));
505 }
506 };
507 let mut diagnostics: Vec<Diagnostic> = Vec::new();
508 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
509 for error in errors {
510 diagnostics.push(invalid(&format!("invalid IR, {error}")));
511 }
512 }
513 let mut messages = Vec::with_capacity(diagnostics.len());
514 for diag in &diagnostics {
515 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
516 }
517 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
518 let artifact = if errors > 0 {
519 Artifact::Nothing
520 } else {
521 Artifact::Text(rucc_ir::print(&module, &sess.interner))
522 };
523 Compiled {
525 artifact,
526 messages,
527 errors,
528 fired: Fired::new(),
529 pressure: Pressure::new(),
530 lowerings: Lowerings::new(),
531 dumps: Vec::new(),
532 remarks: String::new(),
533 deps: Vec::new(),
534 temps: Temps::default(),
535 }
536}
537
538fn instrument(
561 module: &mut rucc_ir::Module,
562 names: &mut Interner,
563 opts: &Options,
564) -> Result<Instrumented, Vec<Diagnostic>> {
565 if !opts.safety.instruments() {
566 return Ok(Instrumented::default());
567 }
568 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
569 checks.freed = rucc_safety::ending::checks(module, names);
577 let interposed = rucc_safety::redirect(module, names);
582 let crossings = rucc_safety::witness(module, names);
585 match rucc_ir::verify(module, names) {
586 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
587 Err(errors) => Err(errors
588 .iter()
589 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
590 .collect()),
591 }
592}
593
594#[derive(Clone, Copy, Debug, Default)]
600struct Instrumented {
601 checks: rucc_safety::Counts,
603 interposed: usize,
605 crossings: rucc_safety::Sites,
607}
608
609fn optimize(
621 module: &mut rucc_ir::Module,
622 names: &Interner,
623 target: &TargetInfo,
624 opts: &Options,
625 file: &str,
626 dumps: &mut Vec<rucc_opt::Dump>,
627 remarks: &mut String,
628) -> Result<(), Vec<Diagnostic>> {
629 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
630 settings.interposition = match opts.interposition {
636 true => replaceable(target, opts),
637 false => IrPic::Executable,
638 };
639 settings.toggles.clone_from(&opts.passes);
640 settings.fuel = opts.pass_fuel.iter().cloned().collect();
641 settings.global_fuel = opts.pass_fuel_global;
642 settings.verify |= opts.verify_each;
643 for (on, spec) in &opts.pass_gates {
644 if let Err(why) = settings.gates.add(*on, spec) {
647 return Err(vec![internal(&why)]);
648 }
649 }
650 for spec in &opts.dump_ir {
651 if let Err(why) = settings.dumps.add(spec) {
654 return Err(vec![internal(&why)]);
655 }
656 }
657 let mut wants = rucc_opt::Wants::none();
658 for spec in &opts.opt_info {
659 if let Err(why) = wants.add(spec) {
662 return Err(vec![internal(&why)]);
663 }
664 }
665 let report = rucc_opt::run(module, names, &settings);
666 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
667 dumps.extend(report.dumps);
668 match report.broke.is_empty() {
669 true => Ok(()),
670 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
671 }
672}
673
674fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
708 match (target.tuple.os().object_format(), opts.pic) {
709 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
710 _ => IrPic::Executable,
711 }
712}
713
714#[derive(Clone, Copy)]
720struct Origin<'a> {
721 map: &'a SourceMap,
723 name: &'a str,
725}
726
727fn generate(
728 module: &mut rucc_ir::Module,
729 names: &mut Interner,
730 target: &TargetInfo,
731 opts: &Options,
732 recording: &mut Recording<'_>,
733 assembly: &mut Option<String>,
734 origin: Origin<'_>,
735) -> Result<Artifact, Vec<Diagnostic>> {
736 let Some(machine) = Machine::for_target(target) else {
737 return Err(vec![unsupported(&format!(
738 "there is no back end for {} in this compiler yet, so there is nothing to generate",
739 target.tuple
740 ))]);
741 };
742 if opts.protector != Protector::None && machine.conv.guard.is_none() {
747 return Err(vec![unsupported(&format!(
748 "{} is not supported for {} yet, because the stack protector on that target is not \
749 the one this compiler writes",
750 opts.protector, target.tuple
751 ))]);
752 }
753 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
759 return Err(vec![unsupported(&format!(
760 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
761 for it there is not the note this compiler writes",
762 opts.control, target.tuple
763 ))]);
764 }
765 let profile = match machine.conv.trace {
771 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
772 None if opts.profile => {
773 return Err(vec![unsupported(&format!(
774 "-pg is not supported for {} yet, because the profiler's hook on that target is \
775 not the one this compiler calls",
776 target.tuple
777 ))]);
778 }
779 None => None,
780 };
781 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
786 return Err(vec![unsupported(&format!(
787 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
788 the room is there is not the section this compiler writes",
789 target.tuple
790 ))]);
791 }
792 let flags = pipeline::Flags {
793 frame_pointer: opts.frame_pointer,
794 red_zone: opts.red_zone,
795 stack_clash: opts.stack_clash,
796 landing: opts.control.branch(),
797 profile: match profile {
798 None => pipeline::Profile::No,
799 Some(true) => pipeline::Profile::Early,
800 Some(false) => pipeline::Profile::Late,
801 },
802 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
803 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
810 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
815 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
820 accurate: opts.cycle_accurate_model,
822 verify: opts.verify_each,
825 goal: Goal::for_size(opts.opt_level.is_size()),
830 };
831
832 if opts.safety.instruments() {
841 rucc_opt::heap::annotate(module, names);
851 rucc_safety::handover::arrange(module);
858 rucc_safety::lower(module, names);
859 if let Err(errors) = rucc_ir::verify(module, names) {
860 return Err(errors
861 .iter()
862 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
863 .collect());
864 }
865 }
866
867 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
876
877 let mut funcs = Vec::new();
878 let mut complaints = Vec::new();
879 for id in module.funcs() {
880 if module[id].is_declaration() {
881 continue;
882 }
883 match pipeline::compile_recording(
884 &mut module[id],
885 names,
886 &machine,
887 &elsewhere,
888 flags,
889 recording,
890 ) {
891 Ok(func) => funcs.push(func),
892 Err(why) => {
893 let name = names.resolve(module[id].name).to_owned();
894 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
897 let said = format!("cannot generate code for '{name}': {why}");
898 complaints.push(unsupported_at(&said, span));
899 }
900 }
901 }
902 if !complaints.is_empty() {
903 return Err(complaints);
904 }
905 let (globals, aliases) = match opts.emit {
911 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
912 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
913 rucc_asm::aliases(module, names).map_err(refused)?,
914 ),
915 _ => (rucc_asm::Globals::default(), Vec::new()),
916 };
917 let unwind = opts.unwinds();
921 match opts.emit {
922 EmitKind::Asm => {
923 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
924 .map(Artifact::Text)
925 .map_err(refused)
926 }
927 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
931 if opts.save_temps.wanted() {
932 let listing = rucc_asm::print(
933 &funcs,
934 &globals,
935 &aliases,
936 names,
937 target,
938 unwind,
939 output(opts, target),
940 );
941 *assembly = Some(listing.map_err(refused)?);
942 }
943 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
944 .map_err(refused)?;
945 let text = assembled.text;
946 let data = globals.image();
947 let info = if opts.debug_info {
951 describe(&text, &assembled.lines, origin, opts, target)
952 .map_err(|why| vec![internal(&why)])?
953 } else {
954 rucc_object::Info::default()
955 };
956 let bytes =
959 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
960 .map_err(wrote)?;
961 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
966 Ok(Artifact::Object { bytes, defines })
967 }
968 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
969 }
970}
971
972fn describe(
991 text: &rucc_object::Text,
992 lines: &[Vec<rucc_asm::Row>],
993 origin: Origin<'_>,
994 opts: &Options,
995 target: &TargetInfo,
996) -> Result<rucc_object::Info, String> {
997 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
998 let mut files: Vec<String> = Vec::new();
1002 let mut funcs = Vec::with_capacity(text.funcs.len());
1003 for (extent, rows) in text.funcs.iter().zip(lines) {
1004 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1005 for row in rows {
1006 if row.span.is_dummy() {
1007 continue;
1008 }
1009 let Some(at) = origin.map.presumed(row.span.lo) else {
1010 continue;
1011 };
1012 let name = rewrite(at.name);
1013 let found = files.iter().position(|have| *have == name);
1014 let which = match found {
1015 Some(which) => which,
1016 None => {
1017 files.push(name);
1018 files.len() - 1
1019 }
1020 };
1021 let place = rucc_debug::Row {
1022 at: row.at as u64,
1023 file: which,
1024 line: at.line,
1025 column: at.column,
1026 };
1027 match out.last() {
1037 Some(last) if last.at == place.at => {}
1038 _ => out.push(place),
1039 }
1040 }
1041 if let Some(first) = out.first_mut() {
1048 first.at = 0;
1049 }
1050 funcs.push(rucc_debug::Function {
1051 name: extent.name.clone(),
1052 len: extent.len as u64,
1053 rows: out,
1054 });
1055 }
1056 let unit = rucc_debug::Unit {
1057 name: rewrite(origin.name),
1058 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1061 producer: format!("rucc {}", crate::VERSION),
1062 files,
1063 funcs,
1064 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1065 };
1066 rucc_debug::write(&unit).map_err(|why| why.to_string())
1067}
1068
1069fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1081 let mut features = 0;
1082 if target.tuple.arch() == Arch::X86_64 {
1083 if opts.control.branch() {
1084 features |= rucc_object::Property::IBT;
1085 }
1086 if opts.control.ret() {
1087 features |= rucc_object::Property::SHSTK;
1088 }
1089 }
1090 rucc_object::Output {
1091 sections: rucc_object::Sections {
1092 functions: opts.function_sections,
1093 data: opts.data_sections,
1094 },
1095 property: rucc_object::Property { features },
1096 }
1097}
1098
1099fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1105 match why {
1106 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1107 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1108 }
1109}
1110
1111fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1118 match why {
1119 rucc_asm::Error::Thread { .. }
1120 | rucc_asm::Error::IFunc { .. }
1121 | rucc_asm::Error::Frame { .. } => {
1122 vec![unsupported(&why.to_string())]
1123 }
1124 _ => vec![internal(&why.to_string())],
1125 }
1126}
1127
1128fn unsupported(message: &str) -> Diagnostic {
1134 unsupported_at(message, Span::DUMMY)
1135}
1136
1137fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1143 Diagnostic::error(message.to_owned(), span)
1144 .with_code("E0653")
1145 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1146}
1147
1148fn invalid(message: &str) -> Diagnostic {
1150 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1151}
1152
1153fn internal(message: &str) -> Diagnostic {
1155 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1156 .with_code("E0652")
1157 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1158}
1159
1160fn failure(message: String) -> Compiled {
1163 Compiled {
1164 artifact: Artifact::Nothing,
1165 messages: vec![format!("rucc: error: {message}")],
1166 errors: 1,
1167 fired: Fired::new(),
1168 pressure: Pressure::new(),
1169 lowerings: Lowerings::new(),
1170 dumps: Vec::new(),
1171 remarks: String::new(),
1172 deps: Vec::new(),
1173 temps: Temps::default(),
1174 }
1175}
1176
1177#[cfg(test)]
1178mod tests {
1179 use rucc_session::{MemoryFileSystem, Std};
1180 use rucc_target::Triple;
1181
1182 use super::*;
1183
1184 fn options() -> Options {
1185 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1186 opts.emit = EmitKind::Tast;
1187 opts
1188 }
1189
1190 fn run(opts: &Options, source: &str) -> Compiled {
1191 let mut fs = MemoryFileSystem::new();
1192 fs.insert("/main.c", source.to_owned().into_bytes());
1193 compile(opts, "/main.c", &fs)
1194 }
1195
1196 fn freestanding() -> Options {
1200 let mut opts = options();
1201 opts.hosted = false;
1202 opts.search.push_system(rucc_session::runtime::DIR);
1203 opts
1204 }
1205
1206 fn shipped(source: &str) -> String {
1208 let result = run(&freestanding(), source);
1209 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1210 result.text().to_owned()
1211 }
1212
1213 fn tast(source: &str) -> String {
1215 let result = run(&options(), source);
1216 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1217 result.text().to_owned()
1218 }
1219
1220 #[test]
1221 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1222 let text = shipped(concat!(
1223 "#include <stdarg.h>\n",
1224 "int sum(int n, ...) {\n",
1225 " va_list ap, copy;\n",
1226 " va_start(ap, n);\n",
1227 " va_copy(copy, ap);\n",
1228 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1229 " va_end(ap);\n",
1230 " va_end(copy);\n",
1231 " return total;\n",
1232 "}\n",
1233 ));
1234 assert!(text.contains("va-start"), "{text}");
1235 assert!(text.contains("va-copy"), "{text}");
1236 assert!(text.contains("va-arg"), "{text}");
1237 assert!(text.contains("va-end"), "{text}");
1238 }
1239
1240 #[test]
1244 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1245 let text = shipped(concat!(
1246 "#define __need___va_list\n",
1247 "#include <stdarg.h>\n",
1248 "int vprint(const char *f, __gnuc_va_list ap);\n",
1249 "#ifdef va_start\n",
1250 "#error va_start should not be defined\n",
1251 "#endif\n",
1252 "#ifdef _VA_LIST_DEFINED\n",
1253 "#error va_list should not have been made\n",
1254 "#endif\n",
1255 ));
1256 assert!(text.contains("vprint"), "{text}");
1257 }
1258
1259 #[test]
1262 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1263 let text = shipped(concat!(
1264 "#define __need_size_t\n",
1265 "#include <stddef.h>\n",
1266 "#ifdef offsetof\n",
1267 "#error offsetof should not be defined yet\n",
1268 "#endif\n",
1269 "#define __need_ptrdiff_t\n",
1270 "#include <stddef.h>\n",
1271 "#include <stddef.h>\n",
1272 "size_t a;\n",
1273 "ptrdiff_t b;\n",
1274 "wchar_t c;\n",
1275 "max_align_t d;\n",
1276 "void *e = NULL;\n",
1277 "struct P { int x; long y; };\n",
1278 "size_t f = offsetof(struct P, y);\n",
1279 ));
1280 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1281 assert!(text.contains("decl #1 b : long"), "{text}");
1282 }
1283
1284 #[test]
1285 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1286 let text = shipped(concat!(
1287 "#include <limits.h>\n",
1288 "#include <float.h>\n",
1289 "int bits = CHAR_BIT;\n",
1290 "long big = LONG_MAX;\n",
1291 "int low = INT_MIN;\n",
1292 "int radix = FLT_RADIX;\n",
1293 "int digits = DBL_MANT_DIG;\n",
1294 ));
1295 assert!(text.contains("const 8 : int"), "{text}");
1296 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1297 assert!(text.contains("const 2 : int"), "{text}");
1298 assert!(text.contains("const 53 : int"), "{text}");
1299 }
1300
1301 #[test]
1305 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1306 let text = shipped(concat!(
1307 "#include <stdint.h>\n",
1308 "int64_t a = INT64_C(1);\n",
1309 "uint_least16_t b;\n",
1310 "intptr_t c;\n",
1311 "uintmax_t d = UINTMAX_MAX;\n",
1312 "int wide = sizeof(int_fast64_t);\n",
1313 ));
1314 assert!(text.contains("decl #0 a : long"), "{text}");
1315 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1316 assert!(text.contains("decl #2 c : long"), "{text}");
1317 }
1318
1319 #[test]
1330 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1331 let text = shipped(concat!(
1332 "#include <mmintrin.h>\n",
1333 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1334 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1335 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1336 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1337 "void done(void) { _mm_empty(); }\n",
1338 ));
1339 assert!(text.contains("add"), "{text}");
1340 assert!(text.contains("pack"), "{text}");
1341 assert!(text.contains("shift"), "{text}");
1342 }
1343
1344 #[test]
1349 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1350 let text = shipped(concat!(
1351 "#include <mm_malloc.h>\n",
1352 "void *get(void) { return _mm_malloc(64, 16); }\n",
1353 "void put(void *p) { _mm_free(p); }\n",
1354 ));
1355 assert!(text.contains("get"), "{text}");
1356 assert!(text.contains("put"), "{text}");
1357 }
1358
1359 #[test]
1371 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1372 let text = shipped(concat!(
1373 "#include <xmmintrin.h>\n",
1374 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1375 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1376 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1377 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1378 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1379 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1380 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1381 "void *room(void) { return _mm_malloc(64, 16); }\n",
1382 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1383 ));
1384 assert!(text.contains("add"), "{text}");
1385 assert!(text.contains("mask"), "{text}");
1386 assert!(text.contains("pick"), "{text}");
1387 assert!(text.contains("wide"), "{text}");
1388 }
1389
1390 #[test]
1397 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1398 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1399 for absent in [
1400 "_mm_sqrt_ps",
1401 "_mm_sqrt_ss",
1402 "_mm_rsqrt_ps",
1403 "_mm_rsqrt_ss",
1404 "_mm_getcsr",
1405 "_mm_setcsr",
1406 ] {
1407 let defined = text.contains(&format!("{absent}("));
1408 assert!(!defined, "{absent} is defined and the header says it is not");
1409 assert!(text.contains(absent), "{absent} is absent and unexplained");
1410 }
1411 }
1412
1413 #[test]
1414 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1415 let text = shipped(concat!(
1416 "#include <emmintrin.h>\n",
1417 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1418 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1419 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1420 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1421 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1422 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1423 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1424 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1425 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1426 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1427 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1428 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1429 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1430 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1431 ));
1432 assert!(text.contains("wide"), "{text}");
1433 assert!(text.contains("pack"), "{text}");
1434 assert!(text.contains("near"), "{text}");
1435 assert!(text.contains("half"), "{text}");
1436 }
1437
1438 #[test]
1442 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1443 let text = shipped(concat!(
1444 "#include <immintrin.h>\n",
1445 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1446 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1447 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1448 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1449 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1450 "}\n",
1451 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1452 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1453 ));
1454 assert!(text.contains("matching"), "{text}");
1455 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1456 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1457 }
1458
1459 #[test]
1463 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1464 let text = shipped(concat!(
1465 "#include <x86intrin.h>\n",
1466 "void barriers(void *p) {\n",
1467 " _mm_lfence();\n",
1468 " _mm_sfence();\n",
1469 " _mm_mfence();\n",
1470 " _mm_pause();\n",
1471 " _mm_clflush(p);\n",
1472 "}\n",
1473 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1474 ));
1475 assert!(text.contains("barriers"), "{text}");
1476 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1477 }
1478
1479 #[test]
1483 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1484 let text = shipped(concat!(
1485 "#include <immintrin.h>\n",
1486 "#include <emmintrin.h>\n",
1487 "#include <immintrin.h>\n",
1488 "#include <x86intrin.h>\n",
1489 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1490 ));
1491 assert!(text.contains("twice"), "{text}");
1492 }
1493
1494 #[test]
1498 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1499 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1500 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1501 let defined = text.contains(&format!("{absent}("));
1502 assert!(!defined, "{absent} is defined and the header says it is not");
1503 assert!(text.contains(absent), "{absent} is absent and unexplained");
1504 }
1505 }
1506
1507 #[test]
1508 fn the_three_formality_headers_still_have_to_work() {
1509 let text = shipped(concat!(
1510 "#include <stdbool.h>\n",
1511 "#include <stdalign.h>\n",
1512 "#include <iso646.h>\n",
1513 "#include <stdnoreturn.h>\n",
1514 "int t = true and not false;\n",
1515 "_Alignas(16) char buf[16];\n",
1516 "int a = alignof(long);\n",
1517 ));
1518 assert!(text.contains("decl #0 t : int"), "{text}");
1519 assert!(text.contains("const 8 : unsigned long"), "{text}");
1520 }
1521
1522 #[test]
1530 fn every_shipped_header_can_be_included_twice() {
1531 let once: String = rucc_session::runtime::names()
1532 .iter()
1533 .map(|name| format!("#include <{name}>\n"))
1534 .collect();
1535 let twice = once.repeat(2);
1536 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1537 }
1538
1539 #[test]
1540 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1541 let fs = MemoryFileSystem::new();
1542 let result = compile(&options(), "/nope.c", &fs);
1543 assert!(result.failed());
1544 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1545 assert!(result.text().is_empty());
1546 }
1547
1548 #[test]
1549 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1550 let text = tast("int x = 1;\n");
1551 let expected = "\
1552decl #0 x : int object external static defined
1553 init
1554 +0
1555 const 1 : int
1556";
1557 assert_eq!(text, expected);
1558 }
1559
1560 #[test]
1561 fn the_macros_are_expanded_before_anything_is_parsed() {
1562 let text = tast("#define N 2\nint a[N];\n");
1566 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1567 }
1568
1569 #[test]
1575 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1576 let text = tast(concat!(
1577 "#pragma pack(4)\n",
1578 "struct s { int a; };\n",
1579 "#pragma pack()\n",
1580 "int b;\n",
1581 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1582 ));
1583 assert!(text.contains("decl #0 b : int"), "{text}");
1584 assert!(text.contains("decl #1 c : int"), "{text}");
1585 }
1586
1587 #[test]
1595 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1596 tast(concat!(
1597 "struct A { char c; int i; } __attribute__((packed));\n",
1598 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1599 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1600 "struct B { char c; int i; } __attribute__((aligned));\n",
1603 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1604 "struct C { char c; int i __attribute__((packed)); };\n",
1605 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1606 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1607 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1608 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1609 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1610 "struct E { char c; _Alignas(8) int i; };\n",
1611 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1612 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1613 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1614 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1615 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1618 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1619 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1620 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1621 "struct I { [[gnu::packed]] char c; int i; };\n",
1624 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1625 "struct J { char c; [[gnu::packed]] int i; };\n",
1626 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1627 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1628 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1629 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1630 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1631 "union L { char c; int i; } __attribute__((packed));\n",
1632 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1633 "struct O { char c; int i; } __attribute__((__packed__));\n",
1637 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
1638 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
1639 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
1640 ));
1641 }
1642
1643 #[test]
1656 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
1657 let text = tast(concat!(
1658 "struct one { int x; };\n",
1659 "struct two { long y; };\n",
1660 "typedef union { struct one *a; struct two *b; void *any; }\n",
1661 " __attribute__((__transparent_union__)) arg;\n",
1662 "int takes(arg v);\n",
1663 "int f(struct one *p, struct two *q, char *c) {\n",
1664 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
1665 "}\n",
1666 "int takes(struct one *p);\n",
1668 "int (*as_a_member)(struct one *) = takes;\n",
1669 "int (*as_the_union)(arg) = takes;\n",
1670 ));
1671 assert!(text.contains("compound-literal"), "{text}");
1672 }
1673
1674 #[test]
1680 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
1681 let text = tast(concat!(
1682 "struct sockaddr { int family; };\n",
1683 "struct sockaddr_in { int family; int addr; };\n",
1684 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
1685 " addr_arg __attribute__((__transparent_union__));\n",
1686 "int bind_to(int fd, addr_arg where);\n",
1687 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
1688 ));
1689 assert!(text.contains("compound-literal"), "{text}");
1690 }
1691
1692 #[test]
1700 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
1701 let result = run(
1702 &options(),
1703 concat!(
1704 "union wider { int small; double large; } __attribute__((transparent_union));\n",
1705 "struct plain { int x; } __attribute__((transparent_union));\n",
1706 ),
1707 );
1708 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
1709 assert!(!result.failed(), "{:?}", result.messages);
1710 for message in &result.messages {
1711 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
1712 }
1713 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
1714 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
1715 }
1716
1717 #[test]
1726 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
1727 let packed = body(concat!(
1728 "struct P { char c; int v; } __attribute__((packed));\n",
1729 "int f(struct P *p) { return p->v; }\n",
1730 ));
1731 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
1732 let plain = body(concat!(
1734 "struct P { char c; int v; };\n",
1735 "int f(struct P *p) { return p->v; }\n",
1736 ));
1737 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
1738 }
1739
1740 #[test]
1747 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
1748 let stepped = body(concat!(
1749 "struct P { char c; int v[4]; } __attribute__((packed));\n",
1750 "int f(struct P *p, int i) { return p->v[i]; }\n",
1751 ));
1752 assert!(stepped.contains(", align 1,"), "{stepped}");
1753 assert!(!stepped.contains(", align 4,"), "{stepped}");
1754 let nested = body(concat!(
1755 "struct Inner { int v; };\n",
1756 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
1757 "int f(struct P *p) { return p->in.v; }\n",
1758 ));
1759 assert!(nested.contains(", align 1,"), "{nested}");
1760 assert!(!nested.contains(", align 4,"), "{nested}");
1761 }
1762
1763 #[test]
1779 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
1780 let through = body(concat!(
1781 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1782 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
1783 ));
1784 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
1785 let stepped = body(concat!(
1788 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
1789 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
1790 ));
1791 assert!(stepped.contains(", align 1,"), "{stepped}");
1792 assert!(!stepped.contains(", align 4,"), "{stepped}");
1793 let plain = body(concat!(
1796 "typedef unsigned int word;\n",
1797 "unsigned int f(const void *p) { return *(const word *)p; }\n",
1798 ));
1799 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
1800 }
1801
1802 #[test]
1813 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
1814 let prefix = concat!(
1815 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
1816 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
1817 );
1818 let loaded =
1819 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
1820 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
1821 assert!(!loaded.contains("align 16"), "{loaded}");
1822 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
1825 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
1826 let aligned =
1828 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
1829 assert!(aligned.contains("align 16"), "{aligned}");
1830 }
1831
1832 #[test]
1841 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
1842 tast(concat!(
1843 "int v __attribute__((aligned(64)));\n",
1844 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
1845 "__attribute__((aligned(32))) int w;\n",
1848 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
1849 "[[gnu::aligned(16)]] int x;\n",
1850 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
1851 "int y __attribute__((aligned(2)));\n",
1854 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
1855 "void f(void) { int a __attribute__((aligned(128)));\n",
1857 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
1858 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1861 "void g(void) __attribute__((aligned(256)));\n",
1864 "void g(void) {}\n",
1865 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
1866 ));
1867 }
1868
1869 #[test]
1873 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
1874 let text = asm(concat!(
1875 "int v __attribute__((aligned(64)));\n",
1876 "void g(void) __attribute__((aligned(256)));\n",
1877 "void g(void) {}\n",
1878 "void plain(void) {}\n",
1879 ));
1880 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
1881 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1882 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
1883 }
1884
1885 #[test]
1891 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
1892 let source = concat!(
1893 "void g(void) __attribute__((aligned(256)));\n",
1894 "void g(void) {}\n",
1895 "void small(void) __attribute__((aligned(4)));\n",
1896 "void small(void) {}\n",
1897 "void plain(void) {}\n",
1898 );
1899 let listing = |align: Option<u32>| {
1900 let mut opts = options();
1901 opts.emit = EmitKind::Asm;
1902 opts.align_functions = align;
1903 let result = run(&opts, source);
1904 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
1905 result.text().to_owned()
1906 };
1907
1908 let text = listing(Some(32));
1909 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
1910 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
1911 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
1912
1913 let text = listing(Some(8));
1916 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
1917 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
1918 }
1919
1920 #[test]
1929 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
1930 tast(concat!(
1931 "typedef int L __attribute__((aligned(2)));\n",
1932 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
1933 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
1934 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
1936 "struct T { char c; L x; };\n",
1937 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
1938 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
1939 "typedef int H __attribute__((aligned(16)));\n",
1941 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
1942 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
1943 "struct U { char c; H x; };\n",
1944 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
1945 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
1946 "typedef L M __attribute__((aligned(8)));\n",
1949 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
1950 "typedef L N;\n",
1953 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
1954 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
1956 ));
1957 let text = asm(concat!(
1958 "typedef int L __attribute__((aligned(2)));\n",
1959 "typedef int H __attribute__((aligned(16)));\n",
1960 "L low;\n",
1961 "H high;\n",
1962 ));
1963 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
1964 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
1965 }
1966
1967 #[test]
1975 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
1976 tast(concat!(
1977 "typedef int __attribute__((vector_size(16))) v4si;\n",
1978 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
1979 "typedef char __attribute__((vector_size(16))) v16qi;\n",
1980 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
1981 "typedef int __attribute__((vector_size(4))) v1si;\n",
1984 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
1985 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
1987 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
1988 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
1989 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
1990 "v4si g;\n",
1993 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
1994 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
1995 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
1998 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2000 ));
2001 }
2002
2003 #[test]
2013 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2014 tast(concat!(
2015 "typedef int __attribute__((vector_size(8))) v2si;\n",
2016 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2017 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2018 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2020 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2021 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2024 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2025 ));
2026 }
2027
2028 #[test]
2036 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2037 let result = run(
2038 &options(),
2039 concat!(
2040 "typedef int __attribute__((vector_size(16))) v4si;\n",
2041 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2042 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2043 " v4si v = { 1, 2, 3, 4 };\n",
2044 " v[0] = n;\n",
2045 " v[1] += n;\n",
2046 " v[2]++;\n",
2047 " *&v[3] = n;\n",
2048 " v4ui shifted = a >> b;\n",
2050 " shifted <<= b;\n",
2051 " *out = v + (v4si)shifted + (1 << b);\n",
2054 "}\n",
2055 "void refused(const v4si c) {\n",
2058 " c[0] = 1;\n",
2059 "}\n",
2060 ),
2061 );
2062 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2063 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2064 }
2065
2066 #[test]
2073 fn a_record_that_asks_for_the_other_byte_order_is_refused_rather_than_laid_out_in_this_one() {
2074 let opts = options();
2075 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2076 assert_eq!(
2077 run(&opts, big).messages,
2078 ["/main.c:1:36: error: 'scalar_storage_order' is not implemented yet [E0688]\n\
2079 /main.c:1:36: note: every scalar in this record would be read in the wrong byte \
2080 order"]
2081 );
2082
2083 let armoured =
2084 "struct s { int i; } __attribute__((__scalar_storage_order__(\"little-endian\")));\n";
2085 let messages = run(&opts, armoured).messages;
2086 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2087
2088 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2091 assert!(run(&opts, front).messages[0].contains("[E0688]"), "{front}");
2092 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2093 assert!(run(&opts, standard).messages[0].contains("[E0688]"), "{standard}");
2094 }
2095
2096 #[test]
2106 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2107 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2109 assert_eq!(
2110 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2111 1
2112 );
2113 assert_eq!(
2114 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2115 1
2116 );
2117 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2118 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2120 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2121 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2123 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2124 }
2125
2126 fn bit_field_byte(record: &str) -> u64 {
2128 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2129 let body = body(&source);
2130 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2131 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2132 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2133 }
2134
2135 #[test]
2141 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2142 tast(concat!(
2143 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2144 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2145 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2146 "struct b { char c; __attribute__((packed)) int i; };\n",
2147 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2148 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2149 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2150 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2151 ));
2152 }
2153
2154 #[test]
2160 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2161 tast(concat!(
2162 "#pragma pack(1)\n",
2163 "struct A { char c; int i; };\n",
2164 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2165 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2166 "#pragma pack()\n",
2167 "struct B { char c; int i; };\n",
2168 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2169 "#pragma pack(2)\n",
2170 "struct C { char c; int i; double d; };\n",
2171 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2172 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2173 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2175 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2176 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2177 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2179 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2180 "#pragma pack()\n",
2181 "#pragma pack(push, 1)\n",
2182 "struct D { char c; short s; };\n",
2183 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2184 "#pragma pack(pop)\n",
2185 "struct E { char c; short s; };\n",
2186 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2187 "struct H { char c;\n",
2189 "#pragma pack(1)\n",
2190 " int i; };\n",
2191 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2192 "#pragma pack(1)\n",
2193 "struct I { char c;\n",
2194 "#pragma pack()\n",
2195 " int i; };\n",
2196 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2197 "#pragma pack()\n",
2198 "#pragma pack(push, 8)\n",
2200 "#pragma pack(push, 1)\n",
2201 "struct P { char c; int i; };\n",
2202 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2203 "#pragma pack(pop)\n",
2204 "struct Q { char c; int i; };\n",
2205 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2206 "#pragma pack(pop)\n",
2207 "#pragma pack(16)\n",
2209 "struct R { char c; int i; };\n",
2210 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2211 "#pragma pack()\n",
2212 "#pragma pack(1)\n",
2213 "struct S { char c; int i : 5; int j : 20; };\n",
2214 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2215 "union T { char c; int i; };\n",
2216 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2217 "#pragma pack()\n",
2218 ));
2219 }
2220
2221 #[test]
2225 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2226 let result = run(
2227 &options(),
2228 concat!(
2229 "#pragma pack 4\n",
2230 "#pragma pack(pop)\n",
2231 "#pragma pack(3)\n",
2232 "#pragma pack(1) junk\n",
2233 "#pragma pack(push, 1\n",
2234 "#pragma pack(x)\n",
2235 "#pragma pack(0)\n",
2238 "#pragma pack(push)\n",
2239 "struct s { char c; int i; };\n",
2240 "#pragma pack(pop)\n",
2241 "#pragma pack(pop, foo)\n",
2242 ),
2243 );
2244 let expected = [
2245 "missing `(` after `#pragma pack` - ignored",
2246 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2247 "alignment must be a small power of two, not 3",
2248 "junk at end of `#pragma pack`",
2249 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2250 "unknown action `x` for `#pragma pack` - ignored",
2251 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2252 ];
2253 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2254 for (message, want) in result.messages.iter().zip(expected) {
2255 assert!(message.contains(want), "expected {want:?} in {message:?}");
2256 }
2257 }
2258
2259 #[test]
2266 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2267 let result = run(
2268 &options(),
2269 concat!(
2270 "#pragma pack(push, 1)\n",
2271 "#pragma pack(pop)\n",
2272 "#define API\n",
2273 "API const char version[] = \"3.53.4\";\n",
2274 "const char *get(void) { return version; }\n",
2275 ),
2276 );
2277 assert!(result.messages.is_empty(), "{:?}", result.messages);
2278 }
2279
2280 #[test]
2284 fn the_wide_integer_answers_to_all_three_of_its_names() {
2285 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2286 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2287 assert!(text.contains("decl #1 b : __int128"), "{text}");
2288 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2289 }
2290
2291 #[test]
2292 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2293 let text = tast("long f(int a, long b) { return a + b; }\n");
2297 assert!(text.contains("convert arithmetic"), "{text}");
2298 }
2299
2300 #[test]
2301 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2302 for source in [
2303 "#error stop\n",
2304 "int f(void) { return 1 + ; }\n",
2305 "int f(void) { return undeclared; }\n",
2306 ] {
2307 let result = run(&options(), source);
2308 assert!(result.failed(), "expected this to fail:\n{source}");
2309 assert!(
2310 result.text().is_empty(),
2311 "a file that did not compile wrote a tree:\n{source}"
2312 );
2313 }
2314 }
2315
2316 #[test]
2317 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2318 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2322 assert_eq!(result.errors, 1, "{:?}", result.messages);
2323 }
2324
2325 #[test]
2326 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2327 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2331 assert_eq!(result.errors, 1, "{:?}", result.messages);
2332 }
2333
2334 #[test]
2335 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2336 let source = "int f(void) { char c = 300; return c; }\n";
2337 let plain = run(&options(), source);
2338 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2339 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2340 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2341
2342 let mut opts = options();
2343 opts.warnings_are_errors = true;
2344 let strict = run(&opts, source);
2345 assert!(strict.failed());
2346 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2347 for message in &strict.messages {
2348 assert!(!message.contains("warning:"), "{message}");
2349 }
2350 }
2351
2352 #[test]
2353 fn w_drops_the_warning_before_werror_can_promote_it() {
2354 let source = "int f(void) { char c = 300; return c; }\n";
2355 let mut opts = options();
2356 opts.warnings = false;
2357 let quiet = run(&opts, source);
2358 assert_eq!(quiet.messages, Vec::<String>::new());
2359 assert_eq!(quiet.errors, 0);
2360 assert!(!quiet.text().is_empty(), "and the file still compiles");
2361
2362 opts.warnings_are_errors = true;
2365 let both = run(&opts, source);
2366 assert_eq!(both.messages, Vec::<String>::new());
2367 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2368 }
2369
2370 #[test]
2371 fn the_dialect_reaches_the_keywords_and_the_checking() {
2372 let source = "typeof(1) x;\n";
2375 let mut opts = options();
2376 opts.std = Std::C23;
2377 opts.gnu_extensions = false;
2378 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2379
2380 opts.std = Std::C17;
2381 assert!(run(&opts, source).failed());
2382 }
2383
2384 #[test]
2385 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2386 let mut opts = options();
2387 opts.emit = EmitKind::Object;
2388 let result = run(&opts, "int x = 1;\n");
2389 assert!(!result.failed(), "{:?}", result.messages);
2390 assert!(result.text().is_empty());
2391 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2394 }
2395
2396 fn mir(source: &str) -> String {
2398 let mut opts = options();
2399 opts.emit = EmitKind::MirFinal;
2400 let result = run(&opts, source);
2401 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2402 result.text().to_owned()
2403 }
2404
2405 #[test]
2411 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2412 let text = mir("int add(int a, int b) { return a + b; }\n");
2413 assert!(text.starts_with("mfunc @add {"), "{text}");
2414 assert!(text.contains("x64.add_rr_32"), "{text}");
2415 assert!(text.contains("x64.ret"), "{text}");
2416 assert!(!text.contains('%'), "{text}");
2419 }
2420
2421 #[test]
2423 fn a_function_with_no_body_produces_no_machine_function() {
2424 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2425 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2426 assert!(text.contains("mfunc @f {"), "{text}");
2427 assert!(text.contains("x64.call"), "{text}");
2428 }
2429
2430 #[test]
2432 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2433 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2434 let first = text.find("mfunc @a").expect("the first function");
2435 let second = text.find("mfunc @b").expect("the second function");
2436 assert!(first < second, "{text}");
2437 }
2438
2439 #[test]
2441 fn the_target_decides_which_convention_the_generated_code_follows() {
2442 let mut opts = options();
2443 opts.emit = EmitKind::MirFinal;
2444 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2445 assert!(linux.contains("$rdi"), "{linux}");
2446
2447 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2448 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2449 assert!(windows.contains("$rcx"), "{windows}");
2450 assert!(!windows.contains("$rdi"), "{windows}");
2451 }
2452
2453 #[test]
2461 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2462 let source = concat!(
2463 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2464 "int size(void) { return sizeof(struct S); }\n",
2465 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2466 );
2467
2468 let mut opts = options();
2469 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2470 let windows = run(&opts, source);
2471 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2472
2473 let linux = run(&options(), source);
2474 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2475 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2476
2477 let mut opts = options();
2480 opts.ms_extensions = Some(true);
2481 let asked = run(&opts, source);
2482 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2483 }
2484
2485 #[test]
2487 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2488 let mut opts = options();
2489 opts.emit = EmitKind::MirFinal;
2490 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2491 let result = run(&opts, "int f(int a) { return a; }\n");
2492 assert!(result.failed());
2493 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2494 assert!(result.text().is_empty());
2495 }
2496
2497 #[test]
2509 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2510 let mut opts = options();
2511 opts.emit = EmitKind::MirFinal;
2512 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
2513 s; s.x = 1; v[0] = s.x; }\n\
2514 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
2515 s; s.x = 1; v[0] = s.x; }\n";
2516 let result = run(&opts, source);
2517 assert!(result.failed());
2518 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2519 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2520 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2521 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2522 assert!(result.text().is_empty());
2523 }
2524
2525 #[test]
2533 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2534 let mut opts = options();
2535 opts.emit = EmitKind::MirFinal;
2536 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2537 let plain = run(&opts, source);
2538 assert!(!plain.failed(), "{:?}", plain.messages);
2539 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2540
2541 opts.stack_clash = true;
2542 let result = run(&opts, source);
2543 assert!(!result.failed(), "{:?}", result.messages);
2544 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2545 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2546 }
2547
2548 #[test]
2558 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
2559 let mut opts = options();
2560 opts.emit = EmitKind::Object;
2561 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2562 let source = concat!(
2563 "void use(void *p);\n",
2564 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
2565 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
2566 );
2567 let result = run(&opts, source);
2568 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2569 let bytes = match result.artifact {
2570 Artifact::Object { bytes, .. } => bytes,
2571 other => panic!("expected an object, got {other:?}"),
2572 };
2573 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2574
2575 let mut opts = options();
2578 opts.emit = EmitKind::Object;
2579 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2580 }
2581
2582 #[test]
2593 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
2594 let source = concat!(
2595 "void other(void *p);\n",
2596 "void takes(void (*f)(void *));\n",
2597 "void (*held)(void *);\n",
2598 "void pass(void) { takes(other); }\n",
2599 "void keep(void) { held = other; }\n",
2600 "void call(void) { other(0); }\n",
2601 );
2602 let mut opts = options();
2603 opts.emit = EmitKind::Object;
2604 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2605 let result = run(&opts, source);
2606 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
2607 let bytes = match result.artifact {
2608 Artifact::Object { bytes, .. } => bytes,
2609 other => panic!("expected an object, got {other:?}"),
2610 };
2611 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
2612
2613 let mut opts = options();
2616 opts.emit = EmitKind::Object;
2617 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
2618 }
2619
2620 #[test]
2634 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
2635 let mut opts = options();
2636 opts.emit = EmitKind::MirFinal;
2637 let source =
2638 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
2639 let result = run(&opts, source);
2640 assert!(result.failed());
2641 assert!(
2642 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
2643 "{result:?}"
2644 );
2645 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
2646 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
2647 }
2648
2649 #[test]
2651 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
2652 let mut opts = options();
2653 opts.emit = EmitKind::MirFinal;
2654 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
2655 let result = run(&opts, source);
2656 assert!(result.failed());
2657 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
2658 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
2659 assert!(!note.contains("spec/17-milestones.md"), "{note}");
2660 }
2661
2662 #[test]
2664 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
2665 let source = "int f(int a) { return a; }\n";
2666 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
2667
2668 let mut opts = options();
2669 opts.emit = EmitKind::MirFinal;
2670 opts.frame_pointer = true;
2671 let kept = run(&opts, source).text().to_owned();
2672 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
2673 }
2674
2675 fn asm(source: &str) -> String {
2677 let mut opts = options();
2678 opts.emit = EmitKind::Asm;
2679 let result = run(&opts, source);
2680 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2681 result.text().to_owned()
2682 }
2683
2684 #[test]
2691 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
2692 let text = asm("int add(int a, int b) { return a + b; }\n");
2693 assert!(text.contains("\t.globl\tadd\n"), "{text}");
2694 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
2695 assert!(text.contains("\nadd:\n"), "{text}");
2696 assert!(text.contains("\taddl\t"), "{text}");
2697 assert!(text.contains("\tret\n"), "{text}");
2698 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
2699 assert!(text.contains(".note.GNU-stack"), "{text}");
2702 }
2703
2704 #[test]
2710 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
2711 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
2712 assert!(text.contains("\tcall\t*%"), "{text}");
2713 assert!(text.contains("\tcall\tg\n"), "{text}");
2714 assert!(text.contains("%rdi"), "{text}");
2718 }
2719
2720 #[test]
2724 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
2725 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
2726 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
2727 }
2728
2729 #[test]
2738 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
2739 let arms = "return 1; return 2;";
2740 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
2741 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
2742 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
2743 assert!(
2744 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2745 "{operator}: {text}"
2746 );
2747 assert!(!text.contains("\tset"), "{operator}: {text}");
2748 assert!(!text.contains("\ttest"), "{operator}: {text}");
2749 }
2750 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
2751 for (operator, jump) in unsigned {
2752 let source =
2753 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
2754 let text = asm(&source);
2755 assert!(
2756 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
2757 "{operator}: {text}"
2758 );
2759 }
2760
2761 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
2764 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
2765 }
2766
2767 #[test]
2773 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
2774 let text = asm("int f(int a, int b) { return a < b; }\n");
2775 assert!(text.contains("\tsetl\t"), "{text}");
2776 }
2777
2778 fn optimized(source: &str) -> String {
2780 let mut opts = options();
2781 opts.emit = EmitKind::Asm;
2782 opts.opt_level = rucc_session::OptLevel::O2;
2783 let result = run(&opts, source);
2784 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2785 result.text().to_owned()
2786 }
2787
2788 #[test]
2798 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
2799 let arms: String =
2800 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
2801 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2802 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
2803 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
2804 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
2805 }
2806
2807 #[test]
2814 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
2815 let arms: String = (0..16)
2816 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
2817 .collect::<Vec<_>>()
2818 .join(" ");
2819 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
2820 assert!(text.matches("\tcmp").count() > 1, "{text}");
2821 }
2822
2823 #[test]
2831 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
2832 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
2833 assert!(text.contains("movl\t$2, %eax"), "{text}");
2834 assert!(!text.contains("cvttsd2si"), "{text}");
2835 }
2836
2837 #[test]
2845 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
2846 let text =
2847 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
2848 assert!(text.contains("movl\t$30, %eax"), "{text}");
2849 assert!(!text.contains("t(%rip)"), "{text}");
2850 }
2851
2852 #[test]
2855 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
2856 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
2857 assert!(text.contains("movl\t$98, %eax"), "{text}");
2858 }
2859
2860 #[test]
2864 fn a_table_that_is_not_read_only_keeps_its_load() {
2865 let text = optimized(
2866 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
2867 );
2868 assert!(!text.contains("movl\t$30, %eax"), "{text}");
2869 }
2870
2871 #[test]
2878 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
2879 let text = optimized(
2880 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
2881 );
2882 assert!(!text.contains("call\tlink_error"), "{text}");
2883 }
2884
2885 #[test]
2887 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
2888 let text = asm("long f(void *p) { return (long)p; }\n");
2889 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
2894 let mnemonic = line.split_whitespace().next().unwrap_or("");
2895 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
2896 }
2897 }
2898
2899 #[test]
2903 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
2904 let six = "long a, long b, long c, long d, long e, long f";
2905 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
2906
2907 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
2914 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
2915
2916 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
2920 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
2921 let eight =
2922 "double a, double b, double c, double d, double e, double f, double g, double h";
2923 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
2924 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
2925 }
2926
2927 #[test]
2930 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
2931 let six = "1, 2, 3, 4, 5, 6";
2932 let decl = "long g(long, long, long, long, long, long, long, long);\n";
2933 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
2934
2935 assert!(text.contains("\tmovq\t%"), "{text}");
2936 assert!(text.contains(", (%rsp)\n"), "{text}");
2937 assert!(text.contains(", 8(%rsp)\n"), "{text}");
2938 assert!(text.contains("\tsubq\t$"), "{text}");
2940
2941 let narrow = "int g(int, int, int, int, int, int, int);\n";
2943 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
2944 assert!(text.contains("\tmovl\t%"), "{text}");
2945 assert!(text.contains(", (%rsp)\n"), "{text}");
2946 }
2947
2948 #[test]
2951 fn a_variadic_call_counts_registers_and_not_arguments() {
2952 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
2953 let decl = "int g(int, ...);\n";
2954 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
2955
2956 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
2957 assert!(text.contains("\tmovsd\t%"), "{text}");
2958 assert!(text.contains(", (%rsp)\n"), "{text}");
2959 }
2960
2961 #[test]
2966 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
2967 let body =
2968 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
2969 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
2970
2971 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
2974 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
2975 assert!(!text.contains(", 0(%r"), "{text}");
2976 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
2979 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
2980
2981 assert!(text.contains("\tsubq\t$"), "{text}");
2983 }
2984
2985 #[test]
2988 fn va_start_writes_the_four_fields_the_psabi_describes() {
2989 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
2990 let params = "int a, int b, int c, double d";
2991 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
2992
2993 assert!(text.contains(" movl $24, "), "{text}");
2997 assert!(text.contains(" movl $64, "), "{text}");
2998 assert!(text.contains(", 8(%r"), "{text}");
3002 assert!(text.contains(", 16(%r"), "{text}");
3003 let frame: u32 = text
3004 .lines()
3005 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3006 .expect("a variadic function takes a frame for the save area");
3007 let above = |line: &str| {
3008 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3009 Some(at > frame)
3010 };
3011 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3012 }
3013
3014 #[test]
3017 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3018 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3019 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3020 let text = asm(&ints);
3021
3022 assert!(text.contains("$40, "), "{text}");
3025 assert!(text.contains(" cmpl "), "{text}");
3026 assert!(text.contains(" ja "), "{text}");
3030
3031 let arg = "__builtin_va_arg(ap, double)";
3032 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3033 assert!(text.contains("$160, "), "the last vector slot: {text}");
3034 }
3035
3036 #[test]
3039 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3040 let decl = "struct pair { long a, b; };\n";
3041 let body = "struct pair p = *q; return p.a + p.b;";
3042 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3043
3044 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3045 assert!(!text.contains("\tcall"), "{text}");
3046 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3048 }
3049
3050 #[test]
3053 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3054 let decl = "struct bytes { char a[8]; };\n";
3055 let body = "struct bytes p = *q; return p.a[0];";
3056 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3057
3058 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3060 }
3061
3062 #[test]
3065 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3066 let decl = "struct wide { long a, b, c; };\n";
3067 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3068
3069 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3070 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3075 }
3076
3077 #[test]
3080 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3081 let decl = "struct huge { char a[4096]; };\n";
3082 let mut opts = options();
3083 opts.emit = EmitKind::Asm;
3084 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3085 let result = run(&opts, &source);
3086 assert!(!result.failed(), "{:?}", result.messages);
3087 let text = result.text();
3088 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
3089 assert!(text.contains("4096"), "the size travels: {text}");
3092 }
3093
3094 #[test]
3101 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
3102 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
3103 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
3104
3105 let copy = text.find("call\tmemcpy").expect("the copy");
3106 let call = text.find("call\ttake").expect("the call");
3107 assert!(copy < call, "the copy comes first: {text}");
3108 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
3113 assert!(text.contains("$4096, %edx"), "the size: {text}");
3114 }
3115
3116 #[test]
3119 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
3120 let six = "long a, long b, long c, long d, long e, long f";
3121 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
3122 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
3123
3124 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3128 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3129 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3130 }
3131
3132 #[test]
3134 fn the_target_decides_how_the_assembly_is_spelled() {
3135 let mut opts = options();
3136 opts.emit = EmitKind::Asm;
3137 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3138 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3139 assert!(text.contains("__TEXT,__text"), "{text}");
3140 assert!(text.contains("\n_f:\n"), "{text}");
3141 assert!(!text.contains(".note.GNU-stack"), "{text}");
3142 }
3143
3144 fn obj(source: &str) -> Vec<u8> {
3146 let mut opts = options();
3147 opts.emit = EmitKind::Object;
3148 let result = run(&opts, source);
3149 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3150 match result.artifact {
3151 Artifact::Object { bytes, .. } => bytes,
3152 other => panic!("expected an object, got {other:?}"),
3153 }
3154 }
3155
3156 #[test]
3162 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3163 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3164 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3165 let text = asm("int add(int a, int b) { return a + b; }\n");
3166 assert!(
3167 text.contains("\taddl\t"),
3168 "and the listing of it is the same instructions:\n{text}"
3169 );
3170 }
3171
3172 #[test]
3174 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3175 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3176 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3177 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3178 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3179 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3182 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3183 assert!(!text.contains(".globl\thidden"), "{text}");
3184 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3187 }
3188
3189 #[test]
3196 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3197 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3198 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3199 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3200
3201 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3204 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3205
3206 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3209 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3210
3211 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3213 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3214 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3215 }
3216
3217 #[test]
3219 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3220 let text = asm("const char *f(void) { return \"hi\"; }\n");
3221 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3222 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3223 let label = text
3224 .lines()
3225 .find(|line| line.starts_with(".Lstr"))
3226 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3227 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3228 }
3229
3230 #[test]
3232 fn an_address_in_an_initializer_is_left_to_the_linker() {
3233 let source = "int counter;\nint *p = &counter;\n";
3234 let text = asm(source);
3235 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3236 let bytes = obj(source);
3239 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3240 }
3241
3242 #[test]
3251 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3252 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3255 struct m { void (*x)(void); void (*y)(void); };\n\
3256 const struct m t = { a, b };\n");
3257 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3258 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3259
3260 let text =
3263 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3264 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3265
3266 let text = asm("const int fixed = 7;\n");
3268 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3269 }
3270
3271 #[test]
3278 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3279 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3280 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3283 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3284 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3287 assert!(text.contains("%fs:0"), "{text}");
3288 }
3289
3290 #[test]
3296 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3297 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3298 assert!(text.contains("movq\t%fs:0, "), "{text}");
3299 assert!(!text.contains("GOTTPOFF"), "{text}");
3301 }
3302
3303 #[test]
3314 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3315 for (locality, wanted) in
3316 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3317 {
3318 let source =
3319 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3320 let text = asm(&source);
3321 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3322 }
3323 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3325 assert!(text.contains("\tprefetcht0\t"), "{text}");
3326 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3329 assert!(text.contains("\tprefetcht0\t"), "{text}");
3330 assert!(!text.contains("prefetchw"), "{text}");
3331 }
3332
3333 #[test]
3344 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3345 let text = asm("void stop(void) { __builtin_trap(); }\n");
3346 assert!(text.contains("\tud2\n"), "{text}");
3347 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3348
3349 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3350 assert!(text.contains("\tud2\n"), "{text}");
3351 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3352 }
3353
3354 #[test]
3366 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3367 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3368 assert!(!text.contains("assume_aligned"), "{text}");
3369 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3370
3371 let source = "unsigned long width(void);\n\
3372 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3373 let text = asm(source);
3374 assert!(!text.contains("assume_aligned"), "{text}");
3375 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3376 }
3377
3378 #[test]
3388 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3389 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3390 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3391 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3392 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3393
3394 let walk = |depth: u32| {
3395 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3396 asm(&source).matches("movq\t(%r").count()
3397 };
3398 assert_eq!(walk(1), 1, "one link is one load");
3399 assert_eq!(walk(3), 3, "three links are three loads");
3400 }
3401
3402 #[test]
3412 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3413 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3414 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3415 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3416 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3417
3418 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3419 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3420 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3421 }
3422
3423 #[test]
3434 fn a_depth_that_is_not_a_small_constant_is_refused() {
3435 let mut opts = options();
3436 opts.emit = EmitKind::Ir;
3437 for source in [
3438 "void *up(int n) { return __builtin_return_address(n); }\n",
3439 "void *up(void) { return __builtin_frame_address(1000); }\n",
3440 ] {
3441 let messages = run(&opts, source).messages;
3442 let named = messages.iter().any(|m| m.contains("E0705"));
3443 assert!(named, "expected a refusal in {messages:?}");
3444 }
3445 }
3446
3447 #[test]
3459 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3460 let text =
3461 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3462 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
3463 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
3464 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3465
3466 let plain = concat!(
3469 "extern void *alloca(__SIZE_TYPE__);\n",
3470 "void use(void *p);\n",
3471 "void f(unsigned long n) { use(alloca(n)); }\n",
3472 );
3473 let text = asm(plain);
3474 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
3475 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
3476
3477 let own = concat!(
3480 "static void *alloca(unsigned long n) { return 0; }\n",
3481 "void *f(unsigned long n) { return alloca(n); }\n",
3482 );
3483 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3484 }
3485
3486 #[test]
3496 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3497 let inner = "{ use(__builtin_alloca(n)); }";
3498 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3499 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3500 let text = asm(&source);
3501 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
3505 let taking = line.contains("subq");
3506 let leaving = line.contains("%rbp");
3507 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
3508 }
3509 }
3510 }
3511
3512 #[test]
3514 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
3515 let source = "int callee(void); int g(void) { return callee(); }\n";
3519 let bytes = obj(source);
3520 assert!(
3521 bytes.windows(7).any(|w| w == b"callee\0"),
3522 "the object has to name the callee for the linker to find it"
3523 );
3524 let text = asm(source);
3525 assert!(text.contains("\tcall\tcallee\n"), "{text}");
3526 }
3527
3528 #[test]
3534 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
3535 let mut opts = options();
3536 opts.emit = EmitKind::Executable;
3538 let result = run(&opts, "int main(void) { return 0; }\n");
3539 assert_eq!(result.messages, Vec::<String>::new());
3540 match result.artifact {
3541 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
3542 other => panic!("expected an object, got {other:?}"),
3543 }
3544 }
3545
3546 #[test]
3548 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
3549 let mut opts = options();
3550 opts.emit = EmitKind::Object;
3551 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3552 let result = run(&opts, "int f(void) { return 0; }\n");
3553 assert!(result.failed(), "an object nobody can read is worse than a message");
3554 assert!(
3555 result.messages.iter().any(|m| m.contains("no object writer")),
3556 "{:?}",
3557 result.messages
3558 );
3559 }
3560
3561 fn ir(source: &str) -> String {
3563 let mut opts = options();
3564 opts.emit = EmitKind::Ir;
3565 let result = run(&opts, source);
3566 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3567 result.text().to_owned()
3568 }
3569
3570 fn errors(source: &str) -> Vec<String> {
3572 let mut opts = options();
3573 opts.emit = EmitKind::Ir;
3574 let result = run(&opts, source);
3575 assert!(result.failed(), "expected this to be refused:\n{source}");
3576 result.messages
3577 }
3578
3579 fn body(source: &str) -> String {
3581 let text = ir(source);
3582 let (_, rest) = text.split_once("{\n").expect("a function definition");
3583 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
3584 body.to_owned()
3585 }
3586
3587 #[test]
3595 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
3596 let source = "inline int f(int x) { return x + 1; }\n";
3597 let with = |flag: bool| {
3598 let mut opts = options();
3599 opts.emit = EmitKind::Ir;
3600 opts.gnu89_inline = flag;
3601 let result = run(&opts, source);
3602 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3603 result.text().to_owned()
3604 };
3605
3606 assert!(!with(false).contains("block0"), "no body: {}", with(false));
3609
3610 assert!(with(true).contains("block0"), "a body: {}", with(true));
3613 }
3614
3615 #[test]
3622 fn an_access_through_a_type_names_the_type_it_went_through() {
3623 let source = "\
3624struct s { int a; float b; };\n\
3625union u { int i; float f; };\n\
3626int scalar(int *p) { return *p; }\n\
3627float member(struct s *p) { p->a = 1; return p->b; }\n\
3628int element(int *a, long i) { return a[i]; }\n\
3629float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
3630 let text = ir(source);
3631 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
3632 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
3633 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
3634 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
3637 assert_eq!(named, 6, "six accesses: {text}");
3638 }
3639
3640 #[test]
3647 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
3648 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
3649 let mut opts = options();
3650 opts.emit = EmitKind::Ir;
3651 opts.strict_aliasing = false;
3652 let result = run(&opts, source);
3653 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3654 let text = result.text().to_owned();
3655 assert!(!text.contains("tbaa"), "not even the root: {text}");
3656 }
3657
3658 #[test]
3666 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
3667 let mut opts = options();
3668 opts.emit = EmitKind::Ir;
3669 opts.std = Std::C89;
3670 let compiled = |source: &str| {
3671 let result = run(&opts, source);
3672 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3673 result.text().to_owned()
3674 };
3675
3676 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
3677 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
3678 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
3679
3680 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
3682 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
3683 }
3684
3685 #[test]
3693 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
3694 let mut opts = options();
3695 opts.emit = EmitKind::Ir;
3696 opts.std = Std::C89;
3697 let compiled = |source: &str| {
3698 let result = run(&opts, source);
3699 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
3700 result.text().to_owned()
3701 };
3702
3703 let text = compiled("int f(void) { return g(); }\n");
3705 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
3706 assert!(text.contains("i32"), "and it gives back an int: {text}");
3707
3708 let text = compiled("int f(char c) { return g(c); }\n");
3711 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
3712
3713 let mut opts = options();
3716 opts.std = Std::C89;
3717 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
3718 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
3719 }
3720
3721 #[test]
3731 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
3732 let mut opts = options();
3733 opts.emit = EmitKind::Ir;
3734 opts.std = Std::C89;
3735 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
3736 .text()
3737 .to_owned();
3738 assert!(text.contains("func @f()"), "the caller is there: {text}");
3739 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
3740 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
3741 }
3742
3743 #[test]
3751 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
3752 let mut opts = options();
3753 opts.emit = EmitKind::Ir;
3754 opts.std = Std::C89;
3755 let compiled = |source: &str| run(&opts, source).text().to_owned();
3756
3757 let text = compiled("f (c) unsigned char c; { return c; }\n");
3758 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
3759 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
3760 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
3761
3762 let text = compiled("f (s) short s; { return s; }\n");
3764 assert!(text.contains("trunc.i16"), "cut down: {text}");
3765 assert!(text.contains("sext.i32"), "and read back signed: {text}");
3766
3767 let text = compiled("f (x) float x; { return x * 2; }\n");
3770 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
3771 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
3772
3773 let text = compiled("int f(unsigned char c) { return c; }\n");
3776 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
3777 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
3778 }
3779
3780 #[test]
3789 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
3790 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3792 let cases = [
3793 ("static counted;\n", ["", "error", "warning", "error"]),
3794 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
3795 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
3796 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
3797 (
3798 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
3799 ["warning", "error", "warning", "error"],
3800 ),
3801 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
3802 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
3803 ];
3804
3805 for (source, wanted) in cases {
3806 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3807 let mut opts = options();
3808 opts.std = std;
3809 opts.permissive = permissive;
3810 let said = run(&opts, source).messages.join("\n");
3811 let severity = if said.contains(": error: ") {
3812 "error"
3813 } else if said.contains(": warning: ") {
3814 "warning"
3815 } else {
3816 ""
3817 };
3818 let how = if permissive { " -fpermissive" } else { "" };
3819 assert_eq!(
3820 severity,
3821 wanted,
3822 "under -std={}{how}, {source} was answered with `{said}`",
3823 std.as_str()
3824 );
3825 if wanted.is_empty() {
3826 assert!(said.is_empty(), "nothing to say, but said `{said}`");
3827 }
3828 }
3829 }
3830 }
3831
3832 #[test]
3841 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
3842 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
3843 let cases = [
3844 (
3845 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
3846 "first argument to 'va_arg' not of type 'va_list'",
3847 ["error", "error", "error", "error"],
3848 ),
3849 (
3850 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
3851 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
3852 ["warning", "error", "warning", "error"],
3853 ),
3854 (
3855 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
3856 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
3857 cast",
3858 ["warning", "error", "warning", "error"],
3859 ),
3860 (
3861 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
3862 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
3863 ["warning", "error", "warning", "error"],
3864 ),
3865 ];
3866
3867 for (source, message, wanted) in cases {
3868 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
3869 let mut opts = options();
3870 opts.std = std;
3871 opts.permissive = permissive;
3872 let said = run(&opts, source).messages.join("\n");
3873 let how = if permissive { " -fpermissive" } else { "" };
3874 assert!(
3875 said.contains(&format!(": {wanted}: {message}")),
3876 "under -std={}{how}, {source} was answered with `{said}`",
3877 std.as_str()
3878 );
3879 }
3880 }
3881 }
3882
3883 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
3885 let mut opts = options();
3886 opts.emit = EmitKind::Ir;
3887 opts.safety = tier;
3888 let result = run(&opts, source);
3889 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3890 result.text().to_owned()
3891 }
3892
3893 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
3894
3895 fn padded_ir(padding: Padding, source: &str) -> String {
3897 let mut opts = options();
3898 opts.emit = EmitKind::Ir;
3899 opts.safety = rucc_session::Safety::Detect;
3900 opts.padding = padding;
3901 let result = run(&opts, source);
3902 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3903 result.text().to_owned()
3904 }
3905
3906 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
3907 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
3908
3909 #[test]
3910 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
3911 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3915 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3916 }
3917
3918 #[test]
3919 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
3920 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
3923 assert!(!text.contains("owns"), "{text}");
3924 }
3925
3926 #[test]
3927 fn a_member_of_a_union_owns_nothing_after_it() {
3928 let text = padded_ir(
3932 Padding::Ignored,
3933 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
3934 );
3935 assert!(!text.contains("owns"), "{text}");
3936 }
3937
3938 #[test]
3939 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
3940 let text = padded_ir(
3945 Padding::Ignored,
3946 "struct inner { char c; };\n\
3947 struct outer { struct inner in; int x; };\n\
3948 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
3949 );
3950 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
3951 }
3952
3953 #[test]
3954 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
3955 let text = ir(READS_THROUGH_A_POINTER);
3959 assert!(!text.contains("check_"), "{text}");
3960 assert!(!text.contains("cap_of"), "{text}");
3961 }
3962
3963 #[test]
3964 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
3965 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3966 assert!(text.contains("cap_of"), "{text}");
3967 assert!(text.contains("check_bounds"), "{text}");
3968 assert!(text.contains("check_live"), "{text}");
3969 assert!(text.contains("check_deriv"), "{text}");
3971 assert!(text.contains("check_type"), "{text}");
3973 }
3974
3975 #[test]
3976 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
3977 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3981 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
3982 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
3983 }
3984 }
3985
3986 fn summary(tier: rucc_session::Safety, source: &str) -> String {
3988 let mut opts = options();
3989 opts.emit = EmitKind::SafetySummary;
3990 opts.safety = tier;
3991 let result = run(&opts, source);
3992 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3993 result.text().to_owned()
3994 }
3995
3996 #[test]
3997 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
3998 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
3999 assert!(text.contains("\"tier\": \"detect\""), "{text}");
4000 assert!(
4002 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
4003 "{text}"
4004 );
4005 assert!(
4006 text.contains(
4007 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
4008 ),
4009 "{text}"
4010 );
4011 }
4012
4013 #[test]
4014 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
4015 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
4019 assert!(text.contains("\"tier\": \"off\""), "{text}");
4020 assert!(
4021 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
4022 "{text}"
4023 );
4024 }
4025
4026 #[test]
4027 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
4028 let text = summary(
4029 rucc_session::Safety::Detect,
4030 "void *memcpy(void *, const void *, unsigned long);\n\
4031 int puts(const char *);\n\
4032 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
4033 );
4034 assert!(text.contains("\"interposed\": 1"), "{text}");
4035 assert!(text.contains("\"puts\""), "{text}");
4036 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
4040 }
4041
4042 #[test]
4043 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
4044 let text = summary(
4049 rucc_session::Safety::Detect,
4050 "void *memcpy(void *, const void *, unsigned long);\n\
4051 int puts(const char *);\n\
4052 void *table[2] = { (void *)memcpy, (void *)puts };\n\
4053 void *f(int i) { return table[i]; }\n",
4054 );
4055 assert!(text.contains("\"interposed\": 1"), "{text}");
4056 assert!(text.contains("\"puts\""), "{text}");
4057 assert!(!text.contains("\"memcpy\""), "{text}");
4058 }
4059
4060 #[test]
4061 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
4062 let text = summary(
4066 rucc_session::Safety::Detect,
4067 "void *notes_open(void);\n\
4068 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
4069 );
4070 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
4071 assert!(text.contains("\"notes_open\""), "{text}");
4072 }
4073
4074 #[test]
4075 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
4076 let text = summary(
4079 rucc_session::Safety::Detect,
4080 "static int len(const char *p) { return p ? 1 : 0; }\n\
4081 int f(void) { return len(\"x\"); }\n",
4082 );
4083 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
4084 }
4085
4086 fn granules(source: &str) -> String {
4088 let mut opts = options();
4089 opts.emit = EmitKind::TypeGranules;
4090 let result = run(&opts, source);
4091 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4092 result.text().to_owned()
4093 }
4094
4095 #[test]
4096 fn the_granule_report_names_every_record_and_both_keyings() {
4097 let text = granules(
4098 "struct hot { char *p; int a; int b; };\n\
4099 int f(struct hot *h) { return h->a; }\n",
4100 );
4101 assert!(text.contains("struct hot"), "{text}");
4102 assert!(text.contains("every type distinct"), "{text}");
4105 assert!(text.contains("every pointer one type"), "{text}");
4106 assert!(text.contains("budget"), "{text}");
4107 }
4108
4109 #[test]
4110 fn a_record_nothing_uses_is_still_measured() {
4111 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
4114 assert!(text.contains("struct unused"), "{text}");
4115 }
4116
4117 #[test]
4118 fn the_granule_report_stops_before_anything_is_lowered() {
4119 let text = granules(
4123 "struct wide { long double d; };\n\
4124 long double f(long double x) { return x * x; }\n",
4125 );
4126 assert!(text.contains("struct wide"), "{text}");
4127 }
4128
4129 #[test]
4130 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
4131 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
4134 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
4135 }
4136
4137 #[test]
4138 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
4139 let text = summary(
4140 rucc_session::Safety::Detect,
4141 "unsigned long f(int *p) { return (unsigned long) p; }\n",
4142 );
4143 assert!(text.contains("\"exposed\": 1"), "{text}");
4144 }
4145
4146 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
4148 let mut opts = options();
4149 opts.emit = EmitKind::Asm;
4150 opts.safety = tier;
4151 let result = run(&opts, source);
4152 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4153 result.text().to_owned()
4154 }
4155
4156 #[test]
4157 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
4158 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4159 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
4160 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
4161 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
4162 assert!(text.contains("\tcall\t__rucc_check_type\n"), "{text}");
4163 assert!(text.contains("\tcall\t__rucc_check_init\n"), "{text}");
4164 }
4165
4166 #[test]
4167 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
4168 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4172 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4173 assert_eq!(text.matches(§ion).count(), 5, "{text}");
4174 for index in 0..5 {
4175 let name = format!("__rucc_safety_desc_{index}");
4176 assert!(text.contains(&format!("{name}:\n")), "{text}");
4179 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4180 }
4181 assert!(!text.contains("__rucc_safety_desc_5"), "{text}");
4182 }
4183
4184 #[test]
4192 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4193 let text = ir(concat!(
4194 "int g;\n",
4195 "int a = __builtin_constant_p(1);\n",
4196 "int b = __builtin_constant_p(g);\n",
4197 "int c = __builtin_constant_p(\"abc\");\n",
4198 "int d = __builtin_constant_p(&g);\n",
4199 "int e = __builtin_constant_p(1.5);\n",
4200 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4201 ));
4202 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4203 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4204 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4205 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4206 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4207 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4208 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4209
4210 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4214 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4215 }
4216
4217 #[test]
4226 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4227 let text = body("void f(void) { __builtin_abort(); }\n");
4228 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4229
4230 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4233 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4234 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4235 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4236 }
4237
4238 #[test]
4251 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4252 let text = ir(concat!(
4253 "char d[8];\n",
4254 "void f(const char *s, unsigned long n) {\n",
4255 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4256 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4257 " __builtin___memset_chk(d, 0, n, 8);\n",
4258 "}\n",
4259 ));
4260 assert!(text.contains("call @__memcpy_chk("), "{text}");
4261 assert!(text.contains("call @__strcpy_chk("), "{text}");
4262 assert!(text.contains("call @__memset_chk("), "{text}");
4263 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4264 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4265 }
4266
4267 #[test]
4275 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4276 let text = ir(concat!(
4277 "extern char *p;\n",
4278 "char d[8];\n",
4279 "void f(const char *s, unsigned long n) {\n",
4280 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4281 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4282 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4283 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4284 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4285 "}\n",
4286 ));
4287
4288 assert!(
4290 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4291 "{text}"
4292 );
4293
4294 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4297 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4298 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4299
4300 assert!(text.contains("call @__sprintf_chk("), "{text}");
4303
4304 let asm = asm(concat!(
4307 "void f(char *p, const char *s, unsigned long n) {\n",
4308 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4309 "}\n",
4310 ));
4311 assert!(asm.contains("call\tmemcpy"), "{asm}");
4312 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4313 }
4314
4315 #[test]
4323 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4324 let text = ir(concat!(
4325 "char d[64];\n",
4326 "int f(const char *fmt, ...) {\n",
4327 " __builtin_va_list ap;\n",
4328 " __builtin_va_start(ap, fmt);\n",
4329 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4330 " __builtin_va_end(ap);\n",
4331 " return n;\n",
4332 "}\n",
4333 ));
4334 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4335 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4336 }
4337
4338 #[test]
4349 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4350 let text = body(concat!(
4351 "long long llabs(long long);\n",
4352 "long long f(long long x) { return llabs(x); }\n",
4353 ));
4354 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4355 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4356 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4357 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4358 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4359
4360 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4363 assert!(text.contains("iconst.i32 31"), "{text}");
4364 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4365 assert!(text.contains("iconst.i64 63"), "{text}");
4366
4367 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4370 assert!(!text.contains("call"), "{text}");
4371
4372 let text = ir(concat!(
4374 "long long llabs(long long b);\n",
4375 "long long g(long long x) { return llabs(x); }\n",
4376 "long long llabs(long long b) { return 7; }\n",
4377 ));
4378 assert!(!text.contains("call @llabs"), "{text}");
4379 }
4380
4381 #[test]
4388 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4389 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4390 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4391
4392 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4395 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4396 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4397 }
4398
4399 #[test]
4405 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4406 for (name, ty, width) in [
4407 ("__builtin_bswap16", "unsigned short", "i16"),
4408 ("__builtin_bswap32", "unsigned", "i32"),
4409 ("__builtin_bswap64", "unsigned long long", "i64"),
4410 ] {
4411 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4412 let text = body(&source);
4413 assert_eq!(
4414 text,
4415 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4416 "{name}"
4417 );
4418 }
4419 }
4420
4421 #[test]
4428 fn the_bit_counts_are_instructions_and_not_calls() {
4429 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4430 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4431
4432 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4433 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4434
4435 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4436 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4437 }
4438
4439 #[test]
4448 fn the_bit_counts_ask_about_the_width_their_name_says() {
4449 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4450 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4451 assert!(text.contains("%1 = ctlz %0"), "{text}");
4452 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4453
4454 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4457 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4458 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4459
4460 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4461 assert!(text.contains("%1 = ctpop %0"), "{text}");
4462 assert!(!text.contains("call"), "{text}");
4463 }
4464
4465 #[test]
4470 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4471 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4472 assert!(text.contains("%1 = ctpop %0"), "{text}");
4473 assert!(text.contains("iconst.i32 1"), "{text}");
4474 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4475 }
4476
4477 #[test]
4483 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4484 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4485 assert!(text.contains("%1 = cttz %0"), "{text}");
4486 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4487 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4488 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4489 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4490 assert!(!text.contains("br_if"), "no branch: {text}");
4491 }
4492
4493 #[test]
4503 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
4504 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
4505 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4506 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
4507 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
4508 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
4509 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
4510 assert!(text.contains("%7 = ctlz %6"), "{text}");
4511 assert!(!text.contains("call"), "{text}");
4512 assert!(!text.contains("br_if"), "no branch: {text}");
4513 }
4514
4515 #[test]
4521 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
4522 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
4523 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
4524 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4525 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
4526
4527 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
4528 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
4529
4530 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
4533 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
4534 }
4535
4536 #[test]
4544 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
4545 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
4546 assert!(text.contains("iconst.i64 63"), "{text}");
4547 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4548 assert!(!text.contains("call"), "{text}");
4549
4550 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
4551 assert!(text.contains("iconst.i64 63"), "{text}");
4552 assert!(!text.contains("call"), "{text}");
4553 }
4554
4555 #[test]
4563 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
4564 let text =
4565 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
4566 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4567 assert!(!text.contains("store"), "nothing is written: {text}");
4568 assert!(!text.contains("call"), "{text}");
4569
4570 let text =
4573 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
4574 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
4575 assert!(!text.contains("store"), "{text}");
4576
4577 let text = body(concat!(
4580 "int g(void);\n",
4581 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
4582 ));
4583 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
4584 }
4585
4586 #[test]
4596 fn an_overflow_check_is_arithmetic_and_not_a_call() {
4597 let text =
4598 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4599 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
4600 assert!(text.contains("store %3 -> %2"), "{text}");
4601 assert!(!text.contains("call"), "{text}");
4602
4603 let text =
4604 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
4605 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
4606
4607 let text =
4608 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
4609 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
4610
4611 let text = body(
4614 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
4615 );
4616 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
4617 }
4618
4619 #[test]
4627 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
4628 let text = body(
4629 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
4630 );
4631 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
4632 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
4633 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
4634
4635 let text = body(
4638 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
4639 );
4640 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
4641 assert!(!text.contains("sext."), "{text}");
4642 assert!(!text.contains("zext.i64"), "{text}");
4644 }
4645
4646 #[test]
4654 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
4655 let text =
4656 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
4657 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
4658 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
4659 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
4660 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
4661 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
4662 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
4663 }
4664
4665 #[test]
4672 fn a_call_needing_more_than_the_widest_type_still_compiles() {
4673 for name in ["add", "sub", "mul"] {
4674 let source = format!(
4675 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
4676 return __builtin_{name}_overflow(a, b, r);\n}}\n"
4677 );
4678 let mut opts = options();
4679 opts.emit = EmitKind::MirFinal;
4680 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
4681 }
4682 }
4683
4684 #[test]
4687 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
4688 let messages =
4689 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
4690 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4691
4692 let messages =
4693 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
4694 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
4695 }
4696
4697 #[test]
4708 fn an_ordered_access_is_ordered_in_the_ir() {
4709 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
4710 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
4711
4712 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
4713 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
4714
4715 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4716 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
4717
4718 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4719 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
4720
4721 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
4724 assert!(text.contains("trunc.i8 %1"), "{text}");
4725 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
4726 }
4727
4728 #[test]
4737 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
4738 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
4739 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
4740 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
4741
4742 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
4743 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
4744 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
4745
4746 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
4747 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
4748 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
4749 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
4750 }
4751
4752 #[test]
4762 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
4763 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
4764 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
4765
4766 for weaker in ["1", "2", "3", "4"] {
4767 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
4768 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
4769 }
4770 }
4771
4772 #[test]
4782 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
4783 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
4784 let source = format!("void f(void) {{ {name}(); }}\n");
4785 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
4786 let text = body(&source);
4787 assert!(text.contains("fence seq_cst"), "{name}: {text}");
4788 }
4789
4790 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
4791 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4792 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
4793 }
4794
4795 #[test]
4801 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
4802 let text =
4805 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
4806 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4807 assert!(text.contains("return %3"), "the value it found: {text}");
4808
4809 let text =
4810 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
4811 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
4812 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
4813
4814 let text = body(
4817 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
4818 );
4819 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4820 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
4821 assert!(text.contains("br_if %5, block2, block1"), "{text}");
4822 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
4823
4824 let text = body(
4827 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
4828 );
4829 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
4830 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
4831 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
4832 }
4833
4834 #[test]
4841 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
4842 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4843 for (ty, suffix, reg) in widths {
4844 let source = format!(
4845 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
4846 );
4847 let text = asm(&source);
4848 assert!(text.contains("\tlock\n"), "{ty}: {text}");
4849 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
4850 assert!(text.contains("sete\t"), "{ty}: {text}");
4851 }
4852 let source =
4853 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
4854 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4855
4856 for order in ["0", "2", "3", "4", "5"] {
4860 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
4861 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
4862 let text = asm(&source);
4863 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
4864 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
4865 }
4866 }
4867
4868 #[test]
4880 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
4881 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
4882 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4883 assert!(text.contains("return %2"), "the value that was there: {text}");
4884
4885 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
4886 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
4887 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
4888
4889 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
4890 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4891 assert!(text.contains("%3 = sub %2, %1"), "{text}");
4892
4893 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
4895 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
4896
4897 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
4900 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
4901
4902 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
4903 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
4904
4905 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
4908 assert!(text.contains("release"), "{text}");
4909 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
4910
4911 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
4915 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
4916 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4917
4918 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
4921 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
4922
4923 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
4924 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
4925 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
4926
4927 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
4930 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
4931 assert!(text.contains("%3 = and %2, %1"), "{text}");
4932 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
4933 assert!(text.contains("%5 = xor %3, %4"), "{text}");
4934 }
4935
4936 #[test]
4947 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
4948 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
4949 for (ty, suffix, reg) in widths {
4950 for (name, call, insn) in [
4951 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
4952 ("or", "__sync_fetch_and_or(p, v)", "or"),
4953 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
4954 ] {
4955 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
4956 let text = asm(&source);
4957 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
4958 assert!(
4959 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
4960 "{ty} {name}: {text}"
4961 );
4962 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
4963 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
4965 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
4966 }
4967 }
4968 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
4969 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
4970
4971 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
4975 assert!(text.contains("cmpxchgl\t"), "{text}");
4976 assert!(text.contains("andl\t"), "{text}");
4977 assert!(text.contains("notl\t"), "{text}");
4978 }
4979
4980 #[test]
4989 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
4990 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
4991 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
4992 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
4993
4994 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
4995 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
4996 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
4997
4998 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
5001 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5002 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
5003 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
5004 }
5005
5006 #[test]
5017 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
5018 for pointer in ["char", "int", "void"] {
5019 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
5020 let text = body(&source);
5021 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
5022 assert!(
5023 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
5024 "{pointer}: {text}"
5025 );
5026 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
5027
5028 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
5029 let text = body(&source);
5030 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
5031 }
5032
5033 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
5036 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
5037 assert!(text.contains("setne\t"), "{text}");
5038 }
5039
5040 #[test]
5048 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
5049 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
5050 for (ty, suffix, reg) in widths {
5051 let source =
5052 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
5053 let text = asm(&source);
5054 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5055 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5056
5057 let source =
5058 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
5059 let text = asm(&source);
5060 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5061 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
5062 }
5063 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
5064 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
5065
5066 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
5069 let text = asm(source);
5070 assert!(text.contains("negl\t"), "{text}");
5071 assert!(text.contains("xaddl\t"), "{text}");
5072
5073 for order in ["0", "2", "3", "4", "5"] {
5076 let source =
5077 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
5078 let text = asm(&source);
5079 assert!(text.contains("xaddl\t"), "{order}: {text}");
5080 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5081 }
5082
5083 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5087 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
5088 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
5095 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
5096 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
5097 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5098 }
5099
5100 #[test]
5112 fn the_lock_free_questions_are_answered_as_constants() {
5113 for size in ["1", "2", "4", "8"] {
5114 let source =
5115 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
5116 let text = asm(&source);
5117 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
5118 assert!(!text.contains("call"), "and is not a call: {text}");
5119 }
5120 for size in ["3", "16", "sizeof(long double)"] {
5121 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
5122 let text = asm(&source);
5123 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
5124 assert!(!text.contains("call"), "and is not a call either: {text}");
5125 }
5126
5127 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
5131 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
5132 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
5133 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
5134 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
5135 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
5136 }
5137
5138 #[test]
5150 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
5151 let mut opts = options();
5152 opts.emit = EmitKind::Ir;
5153
5154 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
5155 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
5156 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
5157
5158 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
5159 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
5160 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
5161
5162 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
5163 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
5164 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
5165 }
5166
5167 #[test]
5179 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5180 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5181 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5182 assert!(text.contains("shrq"), "with the value halved first: {text}");
5183 assert!(text.contains("addsd"), "and doubled after: {text}");
5184 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5185
5186 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5187 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5188 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5189 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5190 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5191 }
5192
5193 #[test]
5204 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5205 let taken = concat!(
5206 "static long long llabs(long long b) { return 7; }\n",
5207 "long long f(long long x) { return llabs(x); }\n",
5208 );
5209 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5210
5211 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5212 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5213
5214 let plain = concat!(
5215 "long long llabs(long long b);\n",
5216 "long long f(long long x) { return llabs(x); }\n",
5217 );
5218 let mut opts = options();
5219 opts.emit = EmitKind::Ir;
5220 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5221
5222 opts.builtins = false;
5223 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5224
5225 opts.builtins = true;
5226 opts.no_builtin = vec!["llabs".to_owned()];
5227 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5228 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5229 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5230
5231 opts.no_builtin = Vec::new();
5234 opts.builtins = false;
5235 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5236 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5237 }
5238
5239 #[test]
5252 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5253 let text = ir(concat!(
5254 "long a = __builtin_expect(7, 1);\n",
5255 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5256 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5257 ));
5258 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5259 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5260 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5261 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5262
5263 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5266 assert!(text.contains("sext"), "{text}");
5267
5268 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5272 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5273 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5274 assert_eq!(body(source), one);
5275
5276 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5281 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5282 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5283 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5284 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5285 }
5286
5287 #[test]
5299 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5300 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5301 let text = ir(promised);
5302 assert!(text.contains(" unreachable_hint\n"), "{text}");
5303 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5304
5305 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5309 assert!(after.contains("return"), "{after}");
5310
5311 let text = asm(promised);
5314 let mine = text.split_once("\nf:\n").expect("a definition").1;
5315 let mine = mine.split_once("\t.size").expect("a definition").0;
5316 let plain = asm("int f(int x) { if (x) return 1; }\n");
5317 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5318 let plain = plain.split_once("\t.size").expect("a definition").0;
5319 assert_eq!(mine, plain);
5320 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5323 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5324 assert!(!mine.contains("ud2"), "{mine}");
5325 }
5326
5327 #[test]
5334 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5335 let mut opts = options();
5336 opts.emit = EmitKind::Ir;
5337 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5338 assert!(
5339 messages.iter().any(|m| m.contains("__builtin_abort")),
5340 "expected the written name in {messages:?}"
5341 );
5342 }
5343
5344 #[test]
5352 fn a_builtin_nothing_lowers_is_refused_by_name() {
5353 let mut opts = options();
5354 opts.emit = EmitKind::Ir;
5355 let builtin = "__atomic_signal_fence";
5356 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5357 let messages = run(&opts, &source).messages;
5358 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5359 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5360 }
5361
5362 #[test]
5371 fn what_is_refused_is_the_call_and_not_the_name() {
5372 let text = ir(concat!(
5373 "void __atomic_signal_fence(int order) { (void)order; }\n",
5374 "void f(void) { __atomic_signal_fence(5); }\n",
5375 ));
5376 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5377 }
5378
5379 #[test]
5388 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5389 let text = ir(concat!(
5390 "struct S { char a[8]; int n; char b[12]; };\n",
5391 "char g[32];\n",
5392 "struct S gs;\n",
5393 "unsigned long whole = __builtin_object_size(g, 0);\n",
5394 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5395 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5396 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5397 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5398 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5399 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5400 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5401 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5402 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5403 ));
5404 for (name, size) in [
5405 ("whole", 32),
5406 ("moved", 28),
5407 ("back", 4),
5408 ("outer", 24),
5409 ("inner", 8),
5410 ("scalar", 4),
5411 ("after", 16),
5412 ("into", 10),
5413 ("text", 6),
5414 ("dyn", 12),
5415 ] {
5416 let said = format!("global @{name} : i64 = {size},");
5417 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5418 }
5419 }
5420
5421 #[test]
5429 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5430 let text = body(concat!(
5431 "struct S { char a[8]; int n; char b[12]; };\n",
5432 "unsigned long f(void) {\n",
5433 " char loc[20];\n",
5434 " struct S ls;\n",
5435 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5436 "}\n",
5437 ));
5438 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5439 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5440 }
5441
5442 #[test]
5452 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5453 let text = ir(concat!(
5454 "struct T { int n; char f[]; };\n",
5455 "extern char *p;\n",
5456 "extern struct T *t;\n",
5457 "unsigned long largest = __builtin_object_size(p, 0);\n",
5458 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5459 "unsigned long least = __builtin_object_size(p, 2);\n",
5460 "unsigned long tight = __builtin_object_size(p, 3);\n",
5461 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5462 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5463 ));
5464 for name in ["largest", "nearest", "flex"] {
5465 let said = format!("global @{name} : i64 = -1,");
5469 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5470 }
5471 for name in ["least", "tight"] {
5472 let said = format!("global @{name} : i64 = 0,");
5473 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5474 }
5475 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5476 }
5477
5478 #[test]
5485 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5486 let text = body(concat!(
5487 "extern char *side(void);\n",
5488 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5489 ));
5490 assert!(!text.contains("call"), "nothing is called: {text}");
5491 }
5492
5493 #[test]
5498 fn a_kind_that_is_not_one_of_the_four_is_refused() {
5499 for source in [
5500 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
5501 + "{ return __builtin_object_size(p, k); }\n",
5502 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
5503 .to_owned(),
5504 "extern char *p;\nunsigned long f(void) ".to_owned()
5505 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
5506 ] {
5507 let messages = errors(&source);
5508 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
5509 assert!(named, "expected a complaint about the kind in {messages:?}");
5510 }
5511 }
5512
5513 #[test]
5519 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
5520 let text = ir(concat!(
5521 "void *buf[5];\n",
5522 "int f(void) {\n",
5523 " if (__builtin_setjmp(buf)) return 2;\n",
5524 " return 1;\n",
5525 "}\n",
5526 "void g(void) { __builtin_longjmp(buf, 1); }\n",
5527 ));
5528 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
5529 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
5530 assert!(!text.contains("call @"), "neither of them is a call: {text}");
5531 }
5532
5533 #[test]
5541 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
5542 let text = ir(concat!(
5543 "void *buf[5];\n",
5544 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
5545 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
5546 ));
5547 let (saves, plain) = text.split_once("func @g").expect("both functions");
5548 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
5549 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
5550 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
5551 }
5552
5553 #[test]
5562 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
5563 let text =
5564 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5565 let body = text.split_once("\nf:\n").expect("the function").1;
5566 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
5567 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
5568 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
5569 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
5570 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
5571 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
5572 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
5573 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
5574 }
5575
5576 #[test]
5584 fn a_save_destroys_every_register_the_allocator_hands_out() {
5585 let text =
5586 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
5587 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
5588 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
5589 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
5590 }
5591 }
5592
5593 #[test]
5600 fn the_restore_puts_the_frame_back_before_it_jumps() {
5601 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
5602 let mut opts = options();
5603 opts.emit = EmitKind::Asm;
5604 opts.opt_level = level;
5605 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
5606 let result = run(&opts, source);
5607 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5608 let text = result.text().to_owned();
5609 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
5610 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
5611 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
5612 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
5613 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
5614 }
5615 }
5616
5617 #[test]
5623 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
5624 for source in [
5625 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
5626 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
5627 ] {
5628 let messages = errors(source);
5629 let named = messages.iter().any(|m| m.contains("E0710"));
5630 assert!(named, "expected a complaint about the value in {messages:?}");
5631 }
5632 }
5633
5634 #[test]
5639 fn a_static_function_nothing_refers_to_is_not_emitted() {
5640 let text = ir("static int dropped(void) { return 1; }\n\
5641 static int kept(void) { return 2; }\n\
5642 int main(void) { return kept(); }\n");
5643 assert!(text.contains("func @kept"), "{text}");
5644 assert!(!text.contains("dropped"), "{text}");
5645 }
5646
5647 #[test]
5653 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
5654 let text = ir("static int ping(void);\n\
5655 static int pong(void) { return ping(); }\n\
5656 static int ping(void) { return pong(); }\n\
5657 int main(void) { return 0; }\n");
5658 assert!(!text.contains("ping"), "{text}");
5659 assert!(!text.contains("pong"), "{text}");
5660 }
5661
5662 #[test]
5668 fn naming_a_static_function_anywhere_keeps_it() {
5669 let text = ir("static int by_address(void) { return 1; }\n\
5670 static int in_an_image(void) { return 2; }\n\
5671 static int deeper(void) { return 3; }\n\
5672 static int reaches_deeper(void) { return deeper(); }\n\
5673 static int (*table[1])(void) = {in_an_image};\n\
5674 int main(void) {\n\
5675 int (*p)(void) = by_address;\n\
5676 return p() + table[0]() + reaches_deeper();\n\
5677 }\n");
5678 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
5679 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
5680 }
5681 }
5682
5683 #[test]
5689 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
5690 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
5691 let source = format!(
5692 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
5693 int main(void) {{ return 0; }}\n"
5694 );
5695 let text = ir(&source);
5696 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
5697 }
5698 }
5699
5700 #[test]
5703 fn a_function_anything_could_call_is_emitted_without_being_called() {
5704 let text =
5705 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
5706 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
5707 }
5708
5709 #[test]
5716 fn a_classification_c_has_an_operator_for_is_that_operator() {
5717 for (builtin, operator) in [
5718 ("__builtin_isgreater", "binary >"),
5719 ("__builtin_isgreaterequal", "binary >="),
5720 ("__builtin_isless", "binary <"),
5721 ("__builtin_islessequal", "binary <="),
5722 ] {
5723 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
5724 let text = tast(&source);
5725 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
5726 }
5727 }
5728
5729 #[test]
5738 fn the_classification_builtins_are_comparisons_and_not_calls() {
5739 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
5740 assert_eq!(
5741 text,
5742 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
5743 %2\n return %3\n"
5744 );
5745
5746 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
5748 assert!(text.contains("fcmp one %0, %1"), "{text}");
5749
5750 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
5751 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5752
5753 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
5754 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
5755 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
5756 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5757 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5758 assert!(text.contains("%5 = or %3, %4"), "{text}");
5759
5760 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
5763 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
5764 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
5765 assert!(text.contains("%5 = and %3, %4"), "{text}");
5766
5767 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
5768 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5769 assert!(text.contains("icmp slt %1, %2"), "{text}");
5770
5771 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
5774 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
5775
5776 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
5779 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5780 }
5781
5782 #[test]
5789 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
5790 let text = ir(concat!(
5791 "int a = __builtin_isinff(1e300);\n",
5792 "int b = __builtin_isinf(1e300);\n",
5793 "int c = __builtin_isnan(0.0);\n",
5797 "int d = __builtin_signbit(-0.0);\n",
5798 "int e = __builtin_islessgreater(1.0, 2.0);\n",
5799 ));
5800 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5801 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5802 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5803 assert!(text.contains("global @d : i32 = 1,"), "{text}");
5804 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5805 }
5806
5807 #[test]
5809 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
5810 let mut opts = options();
5811 opts.emit = EmitKind::Ir;
5812 let source = concat!(
5813 "int a(int x) { return __builtin_isnan(x); }\n",
5814 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
5815 "int c(double x) { return __builtin_isnan(x, x); }\n",
5816 );
5817 let messages = run(&opts, source).messages;
5818 assert_eq!(
5819 messages,
5820 [
5821 "/main.c:1:23: error: non-floating-point argument in call to function \
5822 '__builtin_isnan' [E0685]",
5823 "/main.c:2:30: error: non-floating-point arguments in call to function \
5824 '__builtin_isunordered' [E0685]",
5825 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
5826 ]
5827 );
5828 }
5829
5830 #[test]
5839 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
5840 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
5841 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
5845 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
5846 assert!(text.contains("%3 = and %1, %2"), "{text}");
5847 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
5848 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
5849 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
5850 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
5851 assert!(text.contains("%8 = and %6, %7"), "{text}");
5852
5853 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
5857 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
5858 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
5859
5860 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
5861 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
5862 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
5863 assert!(text.contains("%7 = sub %5, %6"), "{text}");
5864
5865 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
5866 assert!(text.contains("fcmp uno %0, %0"), "{text}");
5867 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
5868 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
5872 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
5873 assert!(!text.contains("call"), "{text}");
5874
5875 let text = body(concat!(
5878 "double g(void);\n",
5879 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
5880 ));
5881 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
5882 }
5883
5884 #[test]
5891 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
5892 let text = ir(concat!(
5893 "int a = __builtin_isnormal(1.0);\n",
5894 "int b = __builtin_isnormal(0.0);\n",
5895 "int c = __builtin_isnormal(1.0 / 0.0);\n",
5896 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
5897 "int e = __builtin_isinf_sign(1.0);\n",
5898 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
5899 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
5900 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
5901 ));
5902 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5903 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5904 assert!(text.contains("global @c : i32 = 0,"), "{text}");
5905 assert!(text.contains("global @d : i32 = -1,"), "{text}");
5906 assert!(text.contains("global @e : i32 = 0,"), "{text}");
5907 assert!(text.contains("global @g : i32 = 4,"), "{text}");
5908 assert!(text.contains("global @h : i32 = 2,"), "{text}");
5909 assert!(text.contains("global @i : i32 = 1,"), "{text}");
5910 }
5911
5912 #[test]
5918 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
5919 let mut opts = options();
5920 opts.emit = EmitKind::Ir;
5921 let source = concat!(
5922 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
5923 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
5924 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
5925 );
5926 let messages = run(&opts, source).messages;
5927 assert_eq!(
5928 messages,
5929 [
5930 "/main.c:1:60: error: non-const integer argument 3 in call to function \
5931 '__builtin_fpclassify' [E0687]",
5932 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
5933 [E0511]",
5934 "/main.c:3:23: error: non-floating-point argument in call to function \
5935 '__builtin_fpclassify' [E0685]",
5936 ]
5937 );
5938 }
5939
5940 #[test]
5948 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
5949 let text = ir(concat!(
5950 "double a = __builtin_inf();\n",
5951 "float b = __builtin_huge_valf();\n",
5952 "long double c = __builtin_infl();\n",
5953 "double d = __builtin_huge_val();\n",
5954 ));
5955 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
5956 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
5957 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
5958 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
5959 assert!(!text.contains("call"), "{text}");
5960 }
5961
5962 #[test]
5971 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
5972 let text = ir(concat!(
5973 "double a = __builtin_nan(\"\");\n",
5974 "double b = __builtin_nan(\"0x1\");\n",
5975 "double c = __builtin_nan(\"010\");\n",
5977 "double d = __builtin_nans(\"\");\n",
5978 "double e = __builtin_nans(\"0x1\");\n",
5979 "float f = __builtin_nanf(\"0x1\");\n",
5980 "float g = __builtin_nansf(\"\");\n",
5981 "long double h = __builtin_nansl(\"\");\n",
5982 ));
5983 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
5984 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
5985 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
5986 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
5987 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
5988 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
5989 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
5990 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
5991
5992 let text = ir(concat!(
5995 "double f(const char *p) { return __builtin_nan(p); }\n",
5996 "double g(void) { return __builtin_nans(\"1x\"); }\n",
5997 ));
5998 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
5999 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
6000 }
6001
6002 #[test]
6010 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
6011 let text = ir(concat!(
6012 "unsigned long a = __builtin_strlen(\"hello\");\n",
6013 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
6014 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
6015 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
6016 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
6017 ));
6018 assert!(text.contains("global @a : i64 = 5,"), "{text}");
6019 assert!(text.contains("global @b : i64 = 1,"), "{text}");
6020 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6021 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6022 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6023 assert!(!text.contains("call"), "{text}");
6024
6025 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
6027 assert!(text.contains("call @strlen("), "{text}");
6028 }
6029
6030 #[test]
6037 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
6038 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
6039 assert!(text.contains("bitcast.i64 %0"), "{text}");
6040 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6041 assert!(text.contains("and %1, %2"), "{text}");
6042 assert!(text.contains("bitcast.f64 %3"), "{text}");
6043 assert!(!text.contains("call"), "{text}");
6044
6045 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
6046 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6047 assert!(text.contains("%8 = or %4, %7"), "{text}");
6048 assert!(!text.contains("call"), "{text}");
6049
6050 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
6053 assert!(text.contains("bitcast.i80 %0"), "{text}");
6054 assert!(text.contains("bitcast.f80"), "{text}");
6055
6056 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
6059 assert!(text.contains("fpext.f64 %0"), "{text}");
6060 assert!(text.contains("bitcast.i64 %1"), "{text}");
6061 }
6062
6063 #[test]
6072 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
6073 let text =
6074 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
6075 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6076 assert!(!text.contains("call"), "{text}");
6077
6078 let text =
6079 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
6080 assert!(text.contains("bitcast.i32 %0"), "{text}");
6081 assert!(!text.contains("call"), "{text}");
6082
6083 let text = body(concat!(
6084 "double copysign(double x, double y);\n",
6085 "double f(double x, double y) { return copysign(x, y); }\n",
6086 ));
6087 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6088 assert!(!text.contains("call"), "{text}");
6089
6090 let text = body(concat!(
6091 "float copysignf(float x, float y);\n",
6092 "float f(float x, float y) { return copysignf(x, y); }\n",
6093 ));
6094 assert!(!text.contains("call"), "{text}");
6095
6096 let text = ir(concat!(
6100 "long double fabsl(long double x);\n",
6101 "long double f(long double x) { return fabsl(x); }\n",
6102 ));
6103 assert!(text.contains("call @fabsl"), "{text}");
6104 }
6105
6106 #[test]
6114 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
6115 let taken = concat!(
6116 "static double fabs(double b) { return 7; }\n",
6117 "double f(double x) { return fabs(x); }\n",
6118 );
6119 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
6120
6121 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
6122 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
6123
6124 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
6125 let mut opts = options();
6126 opts.emit = EmitKind::Ir;
6127 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
6128
6129 opts.builtins = false;
6130 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
6131
6132 opts.builtins = true;
6133 opts.no_builtin = vec!["fabs".to_owned()];
6134 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
6135 let one = concat!(
6136 "double copysign(double a, double b);\n",
6137 "double f(double x) { return copysign(x, 1.0); }\n",
6138 );
6139 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
6140
6141 opts.no_builtin = Vec::new();
6143 opts.builtins = false;
6144 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
6145 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
6146 }
6147
6148 #[test]
6157 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
6158 let text = ir(concat!(
6159 "double a = __builtin_fabs(-3.5);\n",
6160 "double b = __builtin_copysign(1.0, -0.0);\n",
6161 "double c = __builtin_copysign(0.0, -2.0);\n",
6162 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
6164 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
6165 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
6166 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
6167 "long double i = __builtin_fabsl(-__builtin_infl());\n",
6168 ));
6169 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6170 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6171 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6172 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6173 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6174 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6175 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6176 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6177 }
6178
6179 #[test]
6187 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6188 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6189 assert!(!text.contains("call"), "{text}");
6190 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6191 assert!(!text.contains("call"), "{text}");
6192
6193 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6196 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6197 assert!(!text.contains("call"), "{text}");
6198 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6199 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6200
6201 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6204 assert_eq!(written, text, "the name and the operator are the same thing");
6205
6206 let text = body(concat!(
6208 "double creal(_Complex double z);\n",
6209 "double f(_Complex double z) { return creal(z); }\n",
6210 ));
6211 assert!(!text.contains("call"), "{text}");
6212 let text = body(concat!(
6213 "_Complex float conjf(_Complex float z);\n",
6214 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6215 ));
6216 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6217 assert!(!text.contains("call"), "{text}");
6218
6219 let taken = concat!(
6222 "static double creal(_Complex double z) { return 7; }\n",
6223 "double f(_Complex double z) { return creal(z); }\n",
6224 );
6225 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6226 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6227 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6228 let plain = concat!(
6229 "double cimag(_Complex double z);\n",
6230 "double f(_Complex double z) { return cimag(z); }\n",
6231 );
6232 let mut opts = options();
6233 opts.emit = EmitKind::Ir;
6234 opts.builtins = false;
6235 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6236 opts.builtins = true;
6237 opts.no_builtin = vec!["cimag".to_owned()];
6238 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6239
6240 let text = ir(concat!(
6242 "double a = __builtin_creal(1.5 + 2.5i);\n",
6243 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6244 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6245 ));
6246 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6247 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6248 assert!(
6249 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6250 "the conjugate of a constant is the constant with the second half negated: {text}"
6251 );
6252 assert!(!text.contains("call"), "{text}");
6253 }
6254
6255 #[test]
6263 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6264 let text = ir(concat!(
6265 "double a = __builtin_ceil(1.5);\n",
6266 "double b = __builtin_floor(1.5);\n",
6267 "double c = __builtin_trunc(-1.5);\n",
6268 "double d = __builtin_round(2.5);\n",
6271 "double e = __builtin_ceil(-0.5);\n",
6273 "double f = __builtin_fmax(1.0, 2.0);\n",
6274 "double g = __builtin_fmin(1.0, 2.0);\n",
6275 "float h = __builtin_ceilf(1.25f);\n",
6276 "double ceil(double x);\n",
6279 "double i = ceil(2.25);\n",
6280 ));
6281 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6282 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6283 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6284 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6285 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6286 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6287 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6288 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6289 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6290 assert!(!text.contains("call"), "{text}");
6291 }
6292
6293 #[test]
6301 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6302 let text = ir(concat!(
6303 "double f(double x) { return __builtin_ceil(x); }\n",
6304 "float g(float x) { return __builtin_floorf(x); }\n",
6305 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6306 ));
6307 assert!(text.contains("call @ceil("), "{text}");
6308 assert!(text.contains("call @floorf("), "{text}");
6309 assert!(text.contains("call @fmax("), "{text}");
6310
6311 let text = ir(concat!(
6315 "double f(void) { return __builtin_rint(2.5); }\n",
6316 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6317 ));
6318 assert!(text.contains("call @rint("), "{text}");
6319 assert!(text.contains("call @nearbyint("), "{text}");
6320
6321 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6324 assert!(text.contains("call @fmin("), "{text}");
6325
6326 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6329 let mut opts = options();
6330 opts.emit = EmitKind::Ir;
6331 opts.no_builtin = vec!["ceil".to_owned()];
6332 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6333 }
6334
6335 #[test]
6342 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6343 let text = ir(concat!(
6344 "constexpr int side = 4;\n",
6345 "constexpr int wider = side + 1;\n",
6346 "constexpr double half = 1.5;\n",
6347 "struct point { int x; int y; };\n",
6348 "constexpr struct point origin = { 5, 6 };\n",
6349 "int square[side * side];\n",
6350 "int rectangle[wider];\n",
6351 "int rounded[(int)half * 2];\n",
6352 "int across[origin.y];\n",
6353 "enum named { four = side };\n",
6354 "int e = four;\n",
6355 ));
6356 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6357 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6358 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6359 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6360 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6361
6362 let mut opts = options();
6365 opts.emit = EmitKind::Ir;
6366 let konst = "const int n = 1;\nint a[n];\n";
6367 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6368 assert_eq!(run(&opts, konst).messages, [message]);
6369
6370 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6372 assert_eq!(run(&opts, subscript).messages, [message]);
6373
6374 let address = "constexpr int c = 3;\nint *p = &c;\n";
6376 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6377 pointer target type [E0514]";
6378 assert_eq!(run(&opts, address).messages, [warning]);
6379 }
6380
6381 #[test]
6390 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
6391 let mut opts = options();
6392 opts.emit = EmitKind::Ir;
6393
6394 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
6395 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
6396 assert_eq!(run(&opts, alone).messages, [message]);
6397
6398 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
6400 assert_eq!(run(&opts, first).messages, [message]);
6401
6402 let negative = "struct F { int a[-1]; };\n";
6405 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
6406 assert_eq!(run(&opts, negative).messages, [refused]);
6407
6408 let flexible = "struct G { int a[]; };\n";
6411 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
6412 members [E0554]";
6413 assert_eq!(run(&opts, flexible).messages, [named]);
6414 }
6415
6416 #[test]
6431 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
6432 let mut opts = options();
6433 opts.emit = EmitKind::Ir;
6434 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
6435
6436 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
6438 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
6439
6440 let plain = concat!(
6443 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
6444 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
6445 );
6446 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
6447
6448 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
6451 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
6452 [E0514]";
6453 assert_eq!(run(&opts, &dropping).messages, [warning]);
6454
6455 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
6458 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
6459 incompatible return type 'const unsigned int (*)[4]' [E0512]";
6460 assert_eq!(run(&opts, wrong).messages, [error]);
6461 }
6462
6463 #[test]
6472 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
6473 let mut opts = options();
6476 opts.std = Std::C17;
6477 let source = concat!(
6478 "int add(a, b)\n",
6479 "int a;\n",
6480 "int b;\n",
6481 "{ return a + b; }\n",
6482 "int promoted(c)\n",
6483 "char c;\n",
6484 "{ return c; }\n",
6485 "int narrow(char);\n",
6486 "int narrow(c)\n",
6487 "char c;\n",
6488 "{ return c; }\n",
6489 "int first(a)\n",
6490 "int a[4];\n",
6491 "{ return a[0]; }\n",
6492 );
6493 let result = run(&opts, source);
6494 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6495 let text = result.text();
6496 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6497 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6498 assert!(text.contains("c : char object automatic defined"), "{text}");
6500 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
6501 assert!(text.contains("first : int(int *) function external defined"), "{text}");
6503 }
6504
6505 #[test]
6512 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
6513 let mut opts = options();
6514 opts.std = Std::C17;
6515 for (source, message) in [
6516 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
6517 (
6518 "int f(a)\nint a;\nint b;\n{ return a; }\n",
6519 "3:5: error: declaration for parameter 'b' but no such parameter",
6520 ),
6521 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
6522 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
6523 (
6524 "int f(a)\nstatic int a;\n{ return a; }\n",
6525 "2:12: error: storage class specified for parameter 'a'",
6526 ),
6527 (
6528 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
6529 "2:7: error: argument 'a' doesn't match prototype",
6530 ),
6531 ] {
6532 let result = run(&opts, source);
6533 assert!(result.failed(), "expected this to fail:\n{source}");
6534 assert!(result.messages[0].contains(message), "{:?}", result.messages);
6535 }
6536
6537 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
6540 let mut older = options();
6541 older.std = Std::C89;
6542 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
6543 let result = run(&opts, implicit);
6544 assert!(
6545 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
6546 "{:?}",
6547 result.messages
6548 );
6549
6550 let mut newer = options();
6554 newer.std = Std::C23;
6555 let plain = "int f(a)\nint a;\n{ return a; }\n";
6556 let result = run(&newer, plain);
6557 assert!(!result.failed(), "{:?}", result.messages);
6558 assert_eq!(
6559 result.messages,
6560 ["/main.c:1:5: warning: old-style function definition [E0412]"]
6561 );
6562 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
6563 }
6564
6565 #[test]
6572 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
6573 let array = "int a[8] = { [3] 7 };\n";
6574 let member = "struct s { int x; } v = { x: 7 };\n";
6575 for source in [array, member] {
6576 let result = run(&options(), source);
6577 assert!(!result.failed(), "{:?}", result.messages);
6578 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
6579 }
6580
6581 let mut asked = options();
6582 asked.pedantic = true;
6583 assert_eq!(
6584 run(&asked, array).messages,
6585 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
6586 );
6587 assert_eq!(
6588 run(&asked, member).messages,
6589 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
6590 );
6591 }
6592
6593 #[test]
6600 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
6601 let text = ir(concat!(
6602 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
6603 "struct brim { char buf[9223372036854775807L]; };\n",
6604 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
6605 "unsigned long h = sizeof(struct huge_struct);\n",
6606 "unsigned long b = sizeof(struct brim);\n",
6607 "unsigned long y = sizeof(struct bitty);\n",
6608 ));
6609 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
6610 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
6611 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
6612
6613 let mut opts = options();
6614 opts.emit = EmitKind::Ir;
6615 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
6616 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
6617 assert_eq!(run(&opts, over).messages, [message]);
6618 let array = "struct wide { short buf[1L << 62]; };\n";
6619 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
6620 maximum object size '9223372036854775807' [E0537]";
6621 assert_eq!(run(&opts, array).messages[0], message);
6622 }
6623
6624 fn compile_bytes(source: &[u8]) -> Compiled {
6629 let mut opts = options();
6630 opts.emit = EmitKind::Ir;
6631 let mut fs = MemoryFileSystem::new();
6632 fs.insert("/main.c", source.to_vec());
6633 compile(&opts, "/main.c", &fs)
6634 }
6635
6636 #[test]
6643 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
6644 let mut source = b"char s[] = \"a".to_vec();
6645 source.push(0xff);
6646 source.extend_from_slice(b"b\";\nchar c = '");
6647 source.push(0xff);
6648 source.extend_from_slice(b"';\n");
6649 let result = compile_bytes(&source);
6650 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
6651 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
6652 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
6654
6655 let mut stray = b"int a".to_vec();
6656 stray.push(0xff);
6657 stray.extend_from_slice(b" = 1;\n");
6658 let result = compile_bytes(&stray);
6659 assert!(
6660 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
6661 "{:?}",
6662 result.messages
6663 );
6664 }
6665
6666 #[test]
6667 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
6668 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
6669 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
6670 let expected = "\
6671func @add(i32, i32) -> i32, linkage(external) {
6672block0(%0: i32, %1: i32):
6673 %2 = add.nsw %0, %1
6674 return %2
6675}
6676";
6677 assert!(text.contains(expected), "{text}");
6678 }
6679
6680 #[test]
6681 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
6682 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
6683 assert!(!text.contains("alloca"), "{text}");
6684 assert!(!text.contains("load"), "{text}");
6685 assert!(!text.contains("store"), "{text}");
6686 }
6687
6688 #[test]
6689 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
6690 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
6691 let expected = "\
6692block0:
6693 %0 = alloca, size 4, align 4
6694 %1 = iconst.i32 1
6695 store %1 -> %0, align 4, tbaa !1
6696 %2 = call @g(%0) : (ptr) -> i32
6697 return %2
6698";
6699 assert_eq!(text, expected);
6700 }
6701
6702 #[test]
6703 fn a_loop_carries_what_it_changes_as_block_parameters() {
6704 let text = body(
6707 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
6708 return total;\n}\n",
6709 );
6710 assert!(!text.contains("alloca"), "{text}");
6711 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
6712 assert!(text.contains("jump block1("), "{text}");
6713 }
6714
6715 #[test]
6716 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
6717 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
6718 assert!(text.contains("icmp slt %0, %1"), "{text}");
6719 assert!(!text.contains("zext"), "{text}");
6720 }
6721
6722 #[test]
6723 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
6724 let text = body("int f(int a, int b) { return a && b; }\n");
6725 let expected = "\
6726block0(%0: i32, %1: i32):
6727 %2 = iconst.i32 0
6728 %3 = icmp ne %0, %2
6729 %4 = iconst.i1 0
6730 br_if %3, block1, block2(%4)
6731
6732block1:
6733 %5 = iconst.i32 0
6734 %6 = icmp ne %1, %5
6735 jump block2(%6)
6736
6737block2(%7: i1):
6738 %8 = zext.i32 %7
6739 return %8
6740";
6741 assert_eq!(text, expected);
6742 }
6743
6744 #[test]
6745 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
6746 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
6747 assert!(!text.contains("block3"), "{text}");
6750 assert!(!text.contains("iconst.i32 3"), "{text}");
6751 }
6752
6753 #[test]
6754 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
6755 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
6756 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
6757 assert!(body("int f(void) { }\n").contains("unreachable"));
6758 }
6759
6760 #[test]
6761 fn a_structure_is_copied_rather_than_held_in_a_value() {
6762 let text = body(
6763 "struct point { int x, y; };\n\
6764 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
6765 );
6766 assert!(text.contains("memcpy"), "{text}");
6767 }
6768
6769 #[test]
6770 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
6771 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
6772 assert!(text.contains("memset"), "{text}");
6773 }
6774
6775 #[test]
6776 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
6777 let text = body(
6778 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
6779 default: r = 4; } return r; }\n",
6780 );
6781 let expected = "\
6782block0(%0: i32):
6783 %1 = iconst.i32 0
6784 switch %0, block1, [1 => block2, 2 => block3(%1)]
6785
6786block1:
6787 %2 = iconst.i32 4
6788 jump block4(%2)
6789
6790block2:
6791 %3 = iconst.i32 1
6792 jump block3(%3)
6793
6794block3(%4: i32):
6795 %5 = iconst.i32 2
6796 %6 = add.nsw %4, %5
6797 jump block4(%6)
6798
6799block4(%7: i32):
6800 return %7
6801";
6802 assert_eq!(text, expected);
6803 }
6804
6805 #[test]
6806 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
6807 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
6810 assert!(text.contains("%2 = sub %0, %1"), "{text}");
6811 assert!(text.contains("icmp ule"), "{text}");
6812 assert!(!text.contains("switch"), "{text}");
6813 }
6814
6815 #[test]
6816 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
6817 let text = body(
6818 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
6819 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
6820 );
6821 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
6824 assert!(text.contains("block5:\n jump block7("), "{text}");
6825 assert!(text.contains("block6:\n jump block8("), "{text}");
6826 }
6827
6828 #[test]
6829 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
6830 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
6831 }
6832
6833 #[test]
6834 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
6835 let text = body(
6840 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
6841 return n; }\n",
6842 );
6843 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
6846 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
6847 assert!(text.contains("block4:\n jump block3("), "{text}");
6848 }
6849
6850 #[test]
6851 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
6852 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
6855 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
6856 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
6857 assert!(text.contains("br_if %6, block2, block3"), "{text}");
6858 }
6859
6860 #[test]
6861 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
6862 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
6863 assert!(!text.contains("alloca"), "{text}");
6867 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
6868 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
6869 }
6870
6871 #[test]
6872 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
6873 let text =
6874 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
6875 assert!(!text.contains("alloca"), "{text}");
6876 assert!(text.contains("block1(%2: i32):"), "{text}");
6877 assert!(text.contains("jump block1(%5)"), "{text}");
6878 }
6879
6880 #[test]
6881 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
6882 assert_eq!(
6885 body("int f(int x) { return x; spare: return 0; }\n"),
6886 "block0(%0: i32):\n return %0\n"
6887 );
6888 }
6889
6890 #[test]
6891 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
6892 let text = body(
6893 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
6894 );
6895 assert_eq!(
6898 text,
6899 "\
6900block0(%0: ptr):
6901 %1 = load.i8 %0, align 1
6902 %2 = iconst.i8 3
6903 %3 = ashr %1, %2
6904 %4 = sext.i32 %3
6905 return %4
6906"
6907 );
6908 }
6909
6910 #[test]
6911 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
6912 let text =
6916 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
6917 assert_eq!(
6918 text,
6919 "\
6920block0(%0: ptr, %1: i32):
6921 %2 = iconst.i32 16777215
6922 %3 = and %1, %2
6923 %4 = trunc.i16 %3
6924 store %4 -> %0, align 2
6925 %5 = iconst.i32 16
6926 %6 = lshr %3, %5
6927 %7 = trunc.i8 %6
6928 %8 = iconst.i64 2
6929 %9 = ptr_add %0, %8
6930 store %7 -> %9, align 1
6931 return
6932"
6933 );
6934 }
6935
6936 #[test]
6937 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
6938 let text =
6939 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
6940 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
6943 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
6944 }
6945
6946 #[test]
6947 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
6948 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
6951 assert_eq!(text.matches("ashr").count(), 0, "{text}");
6952 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
6953 }
6954
6955 #[test]
6956 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
6957 let text = body(
6961 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
6962 );
6963 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
6964 }
6965
6966 #[test]
6967 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
6968 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
6971 assert!(
6972 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
6973 "{text}"
6974 );
6975 }
6976
6977 #[test]
6978 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
6979 let text = ir(concat!(
6984 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
6985 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
6986 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
6987 "char s[2] = \"hi\";\n",
6988 ));
6989 assert!(
6990 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
6991 "{text}"
6992 );
6993 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
6994 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
6995 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
6998 }
6999
7000 #[test]
7001 fn a_definition_takes_a_parameter_it_left_unnamed() {
7002 let text = ir("int f(int a, int) { return a; }\n");
7006 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
7007 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
7008
7009 let text = ir("int g(int, int n) { return n; }\n");
7012 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
7013 }
7014
7015 #[test]
7016 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
7017 let text = body(concat!(
7022 "struct s { int f; int g; };\n",
7023 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
7024 "{ *d = *e = a[0] = *c; }\n",
7025 ));
7026 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
7027 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
7028 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
7029 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
7030 }
7031
7032 #[test]
7033 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
7034 let mut opts = options();
7039 opts.emit = EmitKind::Ir;
7040 let result = run(
7041 &opts,
7042 concat!(
7043 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
7044 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
7045 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
7046 "const union u c = { { \"1234\", \"567\" } };\n",
7047 ),
7048 );
7049 let text = result.text();
7050 assert_eq!(
7051 result.messages,
7052 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
7053 (5 chars into 3 available) [E0637]"]
7054 );
7055 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
7056 assert!(
7057 text.contains(
7058 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
7059 bytes \"9\\00\", zero 3 }"
7060 ),
7061 "{text}"
7062 );
7063 assert!(
7066 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
7067 "{text}"
7068 );
7069 }
7070
7071 #[test]
7072 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
7073 let text = body(concat!(
7077 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
7078 "void g(struct v *);\n",
7079 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
7080 ));
7081 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
7082 }
7083
7084 #[test]
7085 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
7086 let text = ir(concat!(
7091 "struct s { int x; };\n",
7092 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
7093 "int n = (int){ 7 };\n",
7094 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
7095 ));
7096 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
7097 assert!(text.contains("global @n : i32 = 7,"), "{text}");
7098 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
7101 }
7102
7103 #[test]
7104 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
7105 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
7109 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
7110 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
7111 }
7112
7113 #[test]
7114 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
7115 let text = ir("unsigned char foo[1][0];\n");
7119 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
7120 }
7121
7122 #[test]
7123 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
7124 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
7127 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
7128 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
7129 }
7130
7131 #[test]
7132 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
7133 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
7137 assert!(
7138 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
7139 "{text}"
7140 );
7141 }
7142
7143 #[test]
7144 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
7145 let text = body(
7150 "\
7151struct s { int a, b; };
7152struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
7153",
7154 );
7155 assert!(text.contains("block3(%7: ptr)"), "{text}");
7157 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
7158 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
7159 }
7160
7161 #[test]
7169 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
7170 let text = body("int f(int i) { return ++i ?: 10; }\n");
7171 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
7172 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7173
7174 let text = body("long f(int i) { return ++i ?: 10L; }\n");
7177 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
7178 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7179
7180 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
7182 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
7183
7184 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
7187 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
7188 }
7189
7190 #[test]
7191 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
7192 let text = ir("\
7196struct pair { int a, b; };
7197struct pair make(int a, int b);
7198struct pair twice(struct pair p) { return make(p.a, p.b); }
7199");
7200 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
7201 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
7202 }
7203
7204 #[test]
7205 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7206 let text = ir("\
7210struct big { double v[8]; };
7211struct big grow(struct big b);
7212struct big twice(struct big b) { return grow(grow(b)); }
7213");
7214 assert!(
7215 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7216 "{text}"
7217 );
7218 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7219 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7222 }
7223
7224 #[test]
7225 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7226 let text = ir("\
7231struct big { double v[8]; };
7232struct pair { int a, b; };
7233int p(const char *, ...);
7234int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7235");
7236 assert!(
7237 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7238 "{text}"
7239 );
7240 }
7241
7242 #[test]
7243 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7244 let body = body(
7247 "\
7248struct pair { int a, b; };
7249struct pair make(int a, int b);
7250int second(void) { return make(1, 2).b; }
7251",
7252 );
7253 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7254 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7255 }
7256
7257 #[test]
7258 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7259 let source = "\
7263struct hfa { float x, y, z; };
7264int take(struct hfa h);
7265int give(struct hfa h) { return take(h); }
7266";
7267 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7268 let mut opts = options();
7269 opts.emit = EmitKind::Ir;
7270 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7271 let result = run(&opts, source);
7272 assert_eq!(result.messages, Vec::<String>::new());
7273 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7274 }
7275
7276 #[test]
7277 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7278 let source = "\
7281int use(int *);
7282void f(int n) {
7283 {
7284 int a[n];
7285 use(a);
7286 }
7287 use(0);
7288}
7289";
7290 let body = body(source);
7291 assert!(body.contains("mul.nsw"), "{body}");
7292 assert!(body.contains("stacksave"), "{body}");
7293 assert!(body.contains("alloca %"), "{body}");
7294 assert!(body.contains("stackrestore"), "{body}");
7295 }
7296
7297 #[test]
7298 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7299 let source = "\
7304int use(int *);
7305int f(int n) {
7306 {
7307 int a[n];
7308 if (use(a)) goto out;
7309 use(0);
7310 }
7311out:
7312 return 0;
7313}
7314";
7315 let body = body(source);
7316 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7318 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7319 assert!(after.starts_with(" %4\n jump block"), "{body}");
7320 }
7321
7322 #[test]
7323 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7324 let source = "\
7328int use(int *);
7329int f(int n) {
7330 int a[n];
7331again:
7332 if (use(a)) goto again;
7333 return 0;
7334}
7335";
7336 let body = body(source);
7337 assert!(body.contains("stacksave"), "{body}");
7338 assert!(!body.contains("stackrestore"), "{body}");
7339 }
7340
7341 #[test]
7342 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7343 let source = "\
7348int use(int *);
7349int f(int n) {
7350again:
7351 {
7352 int a[n];
7353 if (use(a)) goto again;
7354 }
7355 return 0;
7356}
7357";
7358 let body = body(source);
7359 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7360 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7361 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7362 }
7363
7364 #[test]
7365 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
7366 let source = "\
7372int f(void);
7373void t(void) {
7374 int count = 10;
7375 for (; count--;) {
7376 int b[f()];
7377 int i;
7378 for (i = 0; i < f(); i++) {
7379 b[i] = count;
7380 }
7381 }
7382}
7383";
7384 let body = body(source);
7385 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7389 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7390 let next = after.split("\n\n").next().expect("the block the restore is in");
7393 assert!(next.contains("jump block1("), "{body}");
7394 }
7395
7396 #[test]
7397 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
7398 let source = "\
7401unsigned long f(int n) {
7402 int a[n];
7403 n = 0;
7404 return sizeof a;
7405}
7406";
7407 let body = body(source);
7408 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
7410 }
7411
7412 #[test]
7413 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
7414 let source = "\
7417int use(int);
7418int f(int x) {
7419 return ({
7420 int t = use(x);
7421 t * t;
7422 });
7423}
7424";
7425 let expected = "\
7426block0(%0: i32):
7427 %1 = call @use(%0) : (i32) -> i32
7428 %2 = mul.nsw %1, %1
7429 return %2
7430";
7431 assert_eq!(body(source), expected);
7432 }
7433
7434 #[test]
7435 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
7436 let source = "\
7440struct pair { int a, b; };
7441void bail(void);
7442int f(struct pair p) {
7443 return (bail(), p).b;
7444}
7445";
7446 let expected = "\
7447block0(%0: i64):
7448 %1 = alloca, size 8, align 4
7449 store %0 -> %1, align 4
7450 call @bail() : ()
7451 %2 = iconst.i64 4
7452 %3 = ptr_add %1, %2
7453 %4 = load.i32 %3, align 4, tbaa !1
7454 return %4
7455";
7456 assert_eq!(body(source), expected);
7457 }
7458
7459 #[test]
7460 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
7461 let source = "int f(int x) { return ({ return x; 0; }); }\n";
7465 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
7466 }
7467
7468 #[test]
7469 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
7470 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
7474 let expected = "\
7475block0(%0: ptr):
7476 %1 = va_arg.f64 %0
7477 %2 = va_arg.f64 %0
7478 %3 = fadd %1, %2
7479 return %3
7480";
7481 assert_eq!(body(source), expected);
7482 }
7483
7484 #[test]
7485 fn one_that_reads_a_structure_answers_where_the_object_is() {
7486 let source = "\
7500struct s { int a; long b; };
7501long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
7502";
7503 let expected = "\
7504block0(%0: ptr):
7505 %1 = alloca, size 16, align 16
7506 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
7507 memcpy %1, %2, size 16, align 8
7508 %3 = iconst.i64 8
7509 %4 = ptr_add %1, %3
7510 %5 = load.i64 %4, align 8, tbaa !1
7511 return %5
7512";
7513 assert_eq!(body(source), expected);
7514 }
7515
7516 #[test]
7520 fn the_classification_says_which_registers_the_object_arrived_in() {
7521 let source = "\
7522struct s { double a; double b; };
7523double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
7524";
7525 assert!(
7526 body(source)
7527 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
7528 "{}",
7529 body(source)
7530 );
7531
7532 let big = "\
7533struct s { long a[4]; };
7534long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
7535";
7536 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
7537 }
7538
7539 #[test]
7540 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
7541 let source = "\
7545int f(int c) {
7546 void *p = c ? &&one : &&two;
7547 goto *p;
7548one:
7549 return 1;
7550two:
7551 return 2;
7552}
7553";
7554 let expected = "\
7555block0(%0: i32):
7556 %1 = iconst.i32 0
7557 %2 = icmp ne %0, %1
7558 br_if %2, block1, block2
7559
7560block1:
7561 %3 = block_addr block3
7562 jump block4(%3)
7563
7564block2:
7565 %4 = block_addr block5
7566 jump block4(%4)
7567
7568block3:
7569 %5 = iconst.i32 1
7570 return %5
7571
7572block4(%6: ptr):
7573 indirect_br %6, block3, block5
7574
7575block5:
7576 %7 = iconst.i32 2
7577 return %7
7578";
7579 assert_eq!(body(source), expected);
7580 }
7581
7582 fn dispatch(labels: usize) -> String {
7585 let mask = labels - 1;
7586 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
7587 for index in 0..labels {
7588 source.push_str(&format!(" &&a{index},"));
7589 }
7590 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
7591 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
7592 for index in 0..labels {
7593 let step = match index % 4 {
7594 0 => "w += x;",
7595 1 => "x += y;",
7596 2 => "y += z;",
7597 _ => "z += w;",
7598 };
7599 source.push_str(&format!("a{index}:\n\t{step}\n"));
7600 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
7601 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
7602 }
7603 source.push_str("}\n");
7604 source
7605 }
7606
7607 fn in_front_of_the_jump(text: &str) -> usize {
7609 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
7610 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
7611 }
7612
7613 #[test]
7623 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
7624 let small = in_front_of_the_jump(&asm(&dispatch(4)));
7625 let large = in_front_of_the_jump(&asm(&dispatch(32)));
7626 assert_eq!(small, large, "eight times the labels and the same values in hand");
7627 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
7628 }
7629
7630 fn crowded(labels: usize) -> String {
7633 const VALUES: usize = 24;
7634 let mask = labels - 1;
7635 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
7636 for index in 0..labels {
7637 source.push_str(&format!(" &&a{index},"));
7638 }
7639 source.push_str(" };\n\t");
7640 for value in 0..VALUES {
7641 source.push_str(&format!("int v{value} = n + {value}; "));
7642 }
7643 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
7644 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
7645 for index in 0..labels {
7646 let (to, from) = (index % VALUES, (index + 1) % VALUES);
7647 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
7648 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
7649 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
7650 }
7651 source.push_str("}\n");
7652 source
7653 }
7654
7655 fn the_frame(text: &str) -> u64 {
7657 text.lines()
7658 .find_map(|line| {
7659 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
7660 size.parse().ok()
7661 })
7662 .expect("a function that opens a frame")
7663 }
7664
7665 #[test]
7674 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
7675 let small = the_frame(&asm(&crowded(16)));
7676 let large = the_frame(&asm(&crowded(64)));
7677 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
7678 }
7679
7680 #[test]
7681 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
7682 let source = "void **next(void);
7685void f(void) { goto *next(); }
7686";
7687 let expected = "\
7688block0:
7689 %0 = call @next() : () -> ptr
7690 unreachable
7691";
7692 assert_eq!(body(source), expected);
7693 }
7694
7695 #[test]
7696 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
7697 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
7700 let expected = "\
7701block0:
7702 inline_asm.volatile \"mfence\", \"\", \"memory\"()
7703 return
7704";
7705 assert_eq!(body(source), expected);
7706 }
7707
7708 #[test]
7709 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
7710 let source = "\
7713int f(int x, int y) {
7714 int r;
7715 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
7716 return r + y;
7717}
7718";
7719 let expected = "\
7720block0(%0: i32, %1: i32):
7721 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
7722 %4 = add.nsw %2, %3
7723 return %4
7724";
7725 assert_eq!(body(source), expected);
7726 }
7727
7728 #[test]
7729 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
7730 let source = "\
7735struct pair { int a, b; };
7736int f(int x) {
7737 int slot = x;
7738 struct pair p = { x, x };
7739 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
7740 return slot + p.a;
7741}
7742";
7743 let text = body(source);
7744 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
7745 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
7746 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
7747 }
7748
7749 #[test]
7750 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
7751 let source = "\
7756int f(int x) {
7757 int r = 7;
7758 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
7759 return r;
7760away:
7761 return r;
7762}
7763";
7764 let expected = "\
7765block0(%0: i32):
7766 %1 = iconst.i32 7
7767 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
7768
7769block1:
7770 return %2
7771
7772block2:
7773 return %1
7774";
7775 assert_eq!(body(source), expected);
7776 }
7777
7778 #[test]
7779 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
7780 let mut opts = options();
7784 opts.emit = EmitKind::Ir;
7785 for (source, expected) in [
7786 (
7787 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
7788 "output operand constraint lacks '='",
7789 ),
7790 (
7791 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
7792 "lvalue required in 'asm' statement",
7793 ),
7794 (
7795 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
7796 "read-only variable 'g' used as 'asm' output",
7797 ),
7798 (
7799 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
7800 "input operand constraint contains '='",
7801 ),
7802 (
7803 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
7804 "memory input 0 is not directly addressable",
7805 ),
7806 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
7807 (
7808 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
7809 "duplicate asm operand name 'a'",
7810 ),
7811 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
7812 ] {
7813 let result = run(&opts, source);
7814 assert!(result.failed(), "expected this to be reported:\n{source}");
7815 assert!(
7816 result.messages.iter().any(|m| m.contains(expected)),
7817 "{expected}\n{:?}",
7818 result.messages
7819 );
7820 }
7821 }
7822
7823 #[test]
7828 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
7829 let text = ir(concat!(
7830 "__asm__(\n",
7831 " \".section .rodata\\n\"\n",
7832 " \".globl first\\n\"\n",
7833 " \".balign 8\\n\"\n",
7834 " \"first:\\n\"\n",
7835 " \".long 1\\n\"\n",
7836 " \".long 2\\n\"\n",
7837 " \".globl last\\n\"\n",
7838 " \"last:\\n\"\n",
7839 " \".quad last - first\\n\");\n",
7840 "extern const int first[];\n",
7841 "extern const long last;\n",
7842 ));
7843 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
7844 assert!(text.contains("global @last : i64 = 8"), "{text}");
7845 }
7846
7847 #[test]
7851 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
7852 let text = ir(concat!(
7853 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
7854 "extern int counter;\n",
7855 "int read(void) { return counter; }\n",
7856 ));
7857 assert!(text.contains("global @counter : i32 = 7"), "{text}");
7858 }
7859
7860 #[test]
7865 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
7866 let text = ir(concat!(
7867 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
7868 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
7869 "extern unsigned char stuff[];\n",
7870 "int read(void) { return stuff[0]; }\n",
7871 ));
7872 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
7873 let named = text.find("global @stuff : i8 = 42").expect(&text);
7874 assert!(under < named, "the bytes under no label come first: {text}");
7875 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
7876 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
7881 assert!(named < after, "{text}");
7882 }
7883
7884 #[test]
7889 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
7890 let text = ir(concat!(
7891 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
7892 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
7893 "extern unsigned char stuff[];\n",
7894 "int read(void) { return stuff[0]; }\n",
7895 ));
7896 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
7900 }
7901
7902 #[test]
7907 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
7908 let text = ir(concat!(
7909 "void base(void) {}\n",
7910 "__asm__(\".weak one\\n.set one, base\");\n",
7911 "__asm__(\".globl two\\n.set two, base\");\n",
7912 "__asm__(\".set three, base\");\n",
7913 "void three(void) {}\n",
7914 ));
7915 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
7916 assert!(text.contains("alias @two = @base"), "{text}");
7917 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
7918 assert!(text.contains("func @three"), "{text}");
7919 }
7920
7921 #[test]
7926 fn a_set_of_a_name_this_file_does_not_define_says_so() {
7927 let messages = errors("__asm__(\".set here, elsewhere\");\n");
7928 assert!(
7929 messages
7930 .iter()
7931 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
7932 && m.contains("E0697")),
7933 "{messages:?}"
7934 );
7935 }
7936
7937 #[test]
7940 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
7941 let mut opts = options();
7942 opts.emit = EmitKind::Ir;
7943 let mut fs = MemoryFileSystem::new();
7944 fs.insert(
7945 "/main.c",
7946 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
7947 );
7948 fs.insert("seed", b"hi".to_vec());
7949 let result = compile(&opts, "/main.c", &fs);
7950 assert_eq!(result.messages, Vec::<String>::new());
7951 let text = result.text();
7952 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
7953 }
7954
7955 #[test]
7958 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
7959 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
7960 assert!(
7961 messages
7962 .iter()
7963 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
7964 "{messages:?}"
7965 );
7966 }
7967
7968 #[test]
7971 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
7972 for source in [
7973 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
7974 "__asm__(\".data\\n.set alias, 4\\n\");\n",
7975 ] {
7976 let messages = errors(source);
7977 assert!(
7978 messages
7979 .iter()
7980 .any(|m| m.contains("not supported yet")
7981 && m.contains("in an `asm` at file scope")),
7982 "{source}\n{messages:?}"
7983 );
7984 }
7985 }
7986
7987 #[test]
7988 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
7989 let mut opts = options();
7990 opts.emit = EmitKind::Ir;
7991 for source in [
7992 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
7993 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
7994 ] {
7995 let result = run(&opts, source);
7996 assert!(result.failed(), "expected this to be reported:\n{source}");
7997 assert!(
7998 result.messages.iter().any(|m| m.contains("not supported yet")),
7999 "{:?}",
8000 result.messages
8001 );
8002 }
8003 }
8004
8005 fn round_trip(source: &str) -> (String, String) {
8007 let printed = ir(source);
8008 let mut opts = options();
8009 opts.emit = EmitKind::Ir;
8010 let mut fs = MemoryFileSystem::new();
8011 fs.insert("/main.ir", printed.clone().into_bytes());
8012 let result = compile_ir(&opts, "/main.ir", &fs);
8013 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
8014 (printed, result.text().to_owned())
8015 }
8016
8017 #[test]
8018 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
8019 let (printed, again) = round_trip(
8023 "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",
8024 );
8025 assert_eq!(printed, again);
8026 }
8027
8028 #[test]
8029 fn ir_that_is_not_ir_says_which_line_stopped_it() {
8030 let mut opts = options();
8031 opts.emit = EmitKind::Ir;
8032 let mut fs = MemoryFileSystem::new();
8033 let text = "\
8034; ModuleID = 'a.c'
8035; format 0
8036target triple = \"x86_64-unknown-linux-gnu\"
8037target datalayout = \"e-p:64:64-i64:64-S128\"
8038
8039func @f(), linkage(external) {
8040block0:
8041 frobnicate
8042}
8043";
8044 fs.insert("/main.ir", text.as_bytes().to_vec());
8045 let result = compile_ir(&opts, "/main.ir", &fs);
8046 assert!(result.failed());
8047 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
8048 }
8049
8050 #[test]
8051 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
8052 let mut opts = options();
8055 opts.emit = EmitKind::Ir;
8056 let mut fs = MemoryFileSystem::new();
8057 let text = "\
8058; ModuleID = 'a.c'
8059; format 0
8060target triple = \"x86_64-unknown-linux-gnu\"
8061target datalayout = \"e-p:64:64-i64:64-S128\"
8062
8063func @f(), linkage(external) {
8064block0:
8065 %0 = iconst.i32 1
8066 return %0
8067}
8068";
8069 fs.insert("/main.ir", text.as_bytes().to_vec());
8070 let result = compile_ir(&opts, "/main.ir", &fs);
8071 assert!(result.failed());
8072 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
8073 }
8074
8075 #[test]
8076 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
8077 let mut fs = MemoryFileSystem::new();
8079 fs.insert("/main.ir", Vec::new());
8080 let result = compile_ir(&options(), "/main.ir", &fs);
8081 assert!(result.failed());
8082 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
8083 }
8084
8085 #[test]
8086 fn the_printed_ir_reads_back_as_the_same_module() {
8087 let text = ir("\
8090struct point { int x, y; };
8091static const char greeting[] = \"hi\";
8092int table[4] = { 1, 2, 3 };
8093int puts(const char *);
8094double half(double x) { return x / 2.0; }
8095int f(int n) {
8096 int total = 0;
8097 for (int i = 0; i < n; i++) {
8098 if (i == 3) continue;
8099 total += table[i];
8100 }
8101 switch (n) {
8102 case 0: total = 1;
8103 case 1: total++; break;
8104 default: total = -total;
8105 }
8106 struct point p = { total, 1 };
8107 int *q = &p.y;
8108 puts(greeting);
8109 return p.x + *q;
8110}
8111int dispatch(int c) {
8112 void *p = c ? &&one : &&two;
8113 goto *p;
8114one:
8115 return 1;
8116two:
8117 return 2;
8118}
8119int assembly(int x, int *p) {
8120 int r;
8121 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
8122 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
8123 return r;
8124away:
8125 return 0;
8126}
8127");
8128 let mut names = Interner::new();
8129 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
8130 assert_eq!(rucc_ir::print(&module, &names), text);
8131 }
8132
8133 #[test]
8134 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
8135 let mut opts = options();
8139 opts.emit = EmitKind::Object;
8140 opts.save_temps = rucc_session::SaveTemps::Object;
8141 let result = run(&opts, "#define N 2\nint a[N];\n");
8142 assert_eq!(result.messages, Vec::<String>::new());
8143 let text = result.temps.preprocessed.expect("the preprocessed text");
8144 assert!(text.contains("int a[2];"), "{text}");
8145 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
8146 let asm = result.temps.assembly.expect("the assembly");
8147 assert!(asm.contains("a:"), "{asm}");
8148 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
8149 }
8150
8151 #[test]
8152 fn nothing_is_kept_unless_the_flag_asked_for_it() {
8153 let mut opts = options();
8156 opts.emit = EmitKind::Object;
8157 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
8158 }
8159
8160 #[test]
8161 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
8162 let mut opts = options();
8165 opts.emit = EmitKind::Ir;
8166 opts.save_temps = rucc_session::SaveTemps::Cwd;
8167 let result = run(&opts, "int a;\n");
8168 assert!(result.temps.preprocessed.is_some());
8169 assert_eq!(result.temps.assembly, None);
8170 }
8171}